How Fingerprint Readers Work

A few months ago, I got a ThinkPad and switched to Ubuntu, from macOS. It’s amazing: the keyboard is so tactile, software updates are so fast, and having the kernel and all the system software be documented and configurable is so cool.

One thing I wanted to know, though, was whether my new laptop was as secure as the MacBook I was leaving. A lot of Linux features are comparable: full-disk encryption with LVM and LUKS is like FileVault; both use secure boot and support 1Password; and both allow biometric unlock with a fingerprint.

Out of curiosity, I took a closer look at the fingerprint reader to see how it works under the hood. People have done a lot of work on “top-side” attacks: developing ways to spoof fingerprints using a laser printer and hot glue, and figuring out how readers can build in liveness detection to prevent that. However, there’s much less information on “bottom-side” security — how the reader coordinates with the operating system during the unlock process, and what cryptography is used to make that protocol secure. I decided to investigate how the bottom-side protocol works on Linux; this write-up is what I learned.

To get started, I found the ThinkPad’s fingerprint reader listed in lsusb as a “Synaptics, Inc. Prometheus MIS Touch Fingerprint Reader” (06cb:00bd). It’s apparently common for fingerprint readers to present themselves as USB devices — they’re wired into the USB hub internally and speak the USB protocol to the operating system.1

On the Ubuntu side, there’s an fprintd service that handles authentication: the lock screen calls out to fprintd when a user tries to authenticate.2 It’s part of the libfprint project, which of course is open source. I read through the code and some of the history on GitLab; it looks like the driver for this reader was actually contributed by Synaptics as part of Lenovo’s effort to offer official Linux support for their laptops — neat!

In any case, Wireshark can decode USB traffic, so we can use it to record an authentication conversation and compare it against the source code. Here’s what we see:

Request 01
0000   a7 fe 01 11 00                                    .....

Response 01
0000   00 00 fe 01 13 01 00                              .......

This looks like a pretty simple initialization exchange:

Okay! After initialization comes this sequence of messages:

Request 02: ID_USER_IN_ORDER
0000   a7 fe 02 e1 21 01 01 1e 46 50 31 2d 32 30 32 31   ....!...FP1-2021
0010   30 37 32 34 2d 37 2d 34 44 46 42 32 31 31 36 2d   0724-7-4DFB2116-
0020   62 74 69 64 6f 72                                 btidor

Response 02: ID_READY
0000   00 00 fe 02 62 00                                 ....b.

Async Read Request
0000   a8                                                .....

Async Read Response: ID_OK
0000   00 00 fe 03 64 21 43 0a 01 46 50 31 2d 32 30 32   ....d!C..FP1-202
0010   31 30 37 32 34 2d 37 2d 34 44 46 42 32 31 31 36   10724-7-4DFB2116
0020   2d 62 74 69 64 6f 72                              -btidor

What’s going on here? This is a “match-on-chip” fingerprint reader, which means fingerprint images are enrolled into the reader’s internal flash storage and the matching algorithm is run locally on the reader itself. That’s some pretty strong isolation: biometric data never touches the main CPU, so it won’t be compromised even if the laptop is infected with malware. Since a fingerprint is like a password you can never ever change, I think the match-on-chip design is a pretty good idea.

That means, in order to authenticate a user:

  1. The laptop gives the reader a list of the user’s previously-enrolled fingerprint IDs: that’s the ID_USER_IN_ORDER with the argument FP1-20210724-7-4DFB2116-btidor3
  2. The reader acknowledges the request with ID_READY
  3. When the reader detects a matching fingerprint, it sets the interrupt flag, causing the laptop to poll the reader4
  4. The reader responds to the poll with an ID_OK message containing the ID of the matched fingerprint
  5. Upon receiving the ID_OK message, the laptop unlocks

One problem: this protocol is completely unauthenticated! That means it should be possible to spoof: if we can create a USB device that mimics the real reader, it can issue the unlock command whenever it wants, without waiting for the real fingerprint to be presented, and unlock the laptop! Using an Arduino Nano, I tried to build a device to test this theory.

I learned that the Arduino Nano 33 BLE is based on Arm’s Mbed OS operating system, and the Mbed OS APIs can be called directly from Arduino code. There’s a USBDevice API, and Arm publishes code samples for emulating a keyboard, a flash drive, and a serial port that I used as a starting point. The code would sometimes lock up the CPU, so it was really helpful to connect a USB-to-TTL cable to the Serial1 pins for debugging output and crash dumps.

The first piece of information needed to clone the reader is its serial number. That’s good: since the serial number isn’t readily accessible, it’s unlikely someone can make my laptop unlock just by walking up and plugging in a dongle. Because I already have an account on the computer, I was able to run lsusb and read off the serial number. Otherwise, I would have had to open the case and physically attach to the reader, which seemed a bit more difficult.

Unfortunately for security, as long as we have the serial number, we don’t need to open the case to get fprintd to talk to our spoofing device. As it turns out, fprintd doesn’t remember which port the reader is “supposed” to appear on, and instead will connect to the last USB peripheral it recognizes as a fingerprint reader, in the order returned by g_usb_context_enumerate. As long as we can make sure our device gets enumerated last, we can plug it into a regular external USB-C port and fprintd will treat it as the real reader.

Linux appears to enumerate USB peripherals using a breadth-first search, so all we need to do is plug our spoofing device into a hub and plug the hub into an external port — then our device will be enumerated last.

With the serial number and the enumeration order taken care of, the code to make the laptop unlock is pretty simple. When our spoofing device receives an ID_USER_IN_ORDER command, it blindly responds with ID_OK:

    id_user = (cmd_id_user_t*)(msg->payload);
    resp = {
        .header_id = BMKT_MESSAGE_HEADER_ID,
        .seq_num = msg->seq_num,
        .msg_id = BMKT_RSP_ID_OK,
        .payload_len = 3 + id_user->uid_len,
    resp.payload[0] = 0x2b;  // double match_result;
    resp.payload[1] = 0x38;
    resp.payload[2] = 0x01;  // uint8_t finger_id;
    memcpy(&resp.payload[3], // uint8_t user_id[];
        id_user->uid, id_user->uid_len);

(I’ve posted the full code to GitHub; it’s linked at the end of this write-up.)

This is kind of surprising! We usually imagine that biometric security boils down to the difficulty of reproducing a physical fingerprint. But in this case, just knowing the fingerprint reader’s six-byte serial number is enough to bypass it — arguably a lower bar!

Is this bad? I asked the Ubuntu security team and they said no: fingerprint readers should only be used in tightly-controlled environments, like at a workstation with a staffed guard, where these sorts of physical attacks can be mitigated.

At the same time, biometrics are seeing increasing adoption among consumers and businesses, with Windows Hello and Touch ID now standard offerings on Windows and Mac, respectively. In a world where biometric unlock is ubiquitous, I think we need a clearer definition of what it means for these products to be secure.

To propose a concrete example: suppose a company uses biometric unlock and an employee’s laptop is stolen. They learn that the laptop contains sensitive information, but fortunately it was locked, had a strong password set, and was protected with full-disk encryption. Should they worry the data will be misused? Should they have to file a data breach notification?5

With Ubuntu and a Synaptics reader, the lost-laptop scenario is not ideal. If the thief knows what to do, they can masquerade as the fingerprint reader and politely ask the operating system to unlock, and it will.6

Historically, this is unsurprising. Until recently, anyone with physical access to a laptop could get essentially everything: they could copy all the data on disk, carry out a cold boot attack to read the contents of memory, and so on. Laptops were like the sort of company intranet where just connecting to the network granted you full access.

We’re quickly moving towards a world where that’s no longer true, both for company intranets and for laptops. The emerging “zero-trust” standard in computer networking mandates that all communications between all components be encrypted and authenticated, which prevents even attackers with full physical access to the network from doing much damage. What we’d really like is for laptop motherboards to work the same way.

I want to point this out because it’s a future that’s almost within reach. Full-disk encryption is now widely available, meaning that data to be stored on disk is encrypted before it leaves the CPU. Intel has released Total Memory Encryption with their latest generation of processors, allowing their CPUs to also encrypt data on its way to and from RAM.7 The missing piece, really, is the security system: the fingerprint reader (and the security chip or TPM).

??? Biometric Reader ??? Transport Layer Security Full-Disk Encryption Memory Encryption Network Memory Disk CPU Security Chip

So how do platforms stack up? As usual, Apple is doing great. On a MacBook with Touch ID, the channel between the fingerprint reader and the security chip is encrypted and authenticated; the security chip guards the disk encryption keys itself and is physically built into the CPU; and this is all documented in their Platform Security guide, which makes the clearest and strongest security commitments of any manufacturer I’ve seen.

Windows accommodates readers from a more diverse set of vendors, and they make few if any concrete security promises. However, Microsoft does vet readers for accuracy through the Windows Hello certification program.

Interestingly, here’s a Wireshark capture from the same Synaptics reader running under Windows Hello:

0000   44 00 00 00 16 03 03 00 4b 01 00 00 47 03 03 39   D.......K...G..9
0010   0d c7 16 70 fe 6d 33 ef 3f f5 b5 f1 b6 12 d3 1c   ...p.m3.?.......
0020   2a 3d 5c 72 7f 3c d4 37 47 f1 fe a4 18 00 45 07   *=\r.<.7G.....E.
0030   00 00 00 00 00 00 00 00 0c c0 05 c0 2e 00 3d 00   ..............=.
0040   8d 00 a8 00 a9 00 00 0a 00 04 00 02 00 17 00 0b   ................
0050   00 02 01 00                                       ....

Skipping past the initial padding, we see 16 03 03 .. .. 01— a TLS 1.2 Client Hello!8 As it turns out, our reader supports an alternate protocol where the authentication exchange is run over TLS messages framed inside USB. The reader presents a certificate and the laptop checks it before continuing with the exchange. That’s maybe a little overkill, but it is encrypted and authenticated, which is great!9

In short:


  1. The Framework Laptop uses a similar fingerprint reader from Goodix, and they have a wonderful replacement guide showing what it looks like. ↩︎

  2. The pam_fprintd PAM module, used by sudo and console login, similarly delegates authentication to the fprintd service. ↩︎

  3. Users' fingerprint IDs are stored on disk in /var/lib/fprint/$USER, a directory only accessible to root↩︎

  4. In the USB protocol, messages always come in request/response pairs initiated by the host. Interrupts work by having the host continually poll the peripheral’s interrupt endpoint, which returns data if the peripheral has something to say. Since the size of interrupt messages is limited, our reader uses the interrupt endpoint to trigger a normal, “bulk” request/response to retrieve the ID_OK message. ↩︎

  5. Remember the time NASA lost laptops containing 10,000 employees' SSNs and the codes to the International Space Station? (This is my canonical example of why endpoint security matters.) That was in 2012, and their remediation was to roll out full-disk encryption. Would that be enough today?, ↩︎

  6. As discussed, this requires the thief to remove a few screws and open the bottom cover to get the reader’s serial number. By the way, the ThinkPad’s bottom cover tamper detection feature won’t save us: opening the cover causes the laptop to require the BIOS password on next boot, but it won’t trigger a shutdown and it won’t warn the user until then. ↩︎

  7. To be fair, AMD has been shipping processors with similar full-memory encryption since at least 2017. Cloudflare did an evaluation and found that the performance penalty for this feature was negligible, up to 5% on a memory bandwidth-intensive benchmark and under 0.5% on more ordinary workloads. This is possible because the processors have dedicated AES circuitry on the path to RAM (so cool!), which makes encryption fast and allows them to prevent anyone, even the kernel, from seeing the encryption key., ↩︎

  8. Technically, a TLS 1.2 Client Hello should start with 16 03 01, i.e. it masquerades as a TLS 1.0 Client Hello. I guess Synaptics doesn’t have to deal with forwards-incompatible middleboxes… (For reference, the TLS-based protocol is a Synaptics-specific product called “SecureLink,” not a more general Windows feature.) ↩︎

  9. It’s not quite an apples-to-apples comparison because Windows allows the fingerprint to be used instead of a password even on boot. They try to make this secure by storing the encryption keys in the TPM and running Synaptics' driver in an SGX enclave. Unfortunately, the communication between the CPU and the TPM is neither encrypted nor authenticated, which causes some problems. ↩︎