Unlock LUKS volume with a YubiKey

Update: The dracut configuration has been updated and now udev consistently recognizes the YubiKey in the initramfs.

Unlocking LUKS encrypted drives with a YubiKey has been supported since systemd 248. In Debian, systemd>=250 is required, as the feature has not been enabled in prior versions. This tutorial is geared towards Yubikeys, but it should work with slight modifications with any other FIDO2 token.

YubiKey series 5 and later should support the hmac-secret extension. You can make sure your Yubikey supports the needed hmac-secret extension by querying it with ykman:

$ ykman --diagnose 2>&1 | grep hmac-secret

Backup your LUKS header

In case you mess anything up, you would need a backup of your LUKS header. Remember to save your backup to some external storage, so you can actually access it if anything goes sideways.

# cryptsetup luksHeaderBackup /dev/nvme0n1p3 --header-backup-file /media/guyru/E474-2D80/luks_backup.bin

Set FIDO2 PIN

We would like to set a FIDO2 PIN for the Yubikey, so unlocking the encrypted drive would require both the physical Yubikey and the PIN. You can set the PIN using:

$ ykman fido access change-pin

Enroll the Yubikey

Start by verifying that systemd-cryptenroll can see and can use your YubiKey:

$ systemd-cryptenroll --fido2-device=list
PATH         MANUFACTURER PRODUCT
/dev/hidraw0 Yubico       YubiKey FIDO+CCID

Now, enroll the Yubikey, replacing /dev/nvme0n1p3 with the block device of the LUKS encrypted drive.

$ sudo systemd-cryptenroll /dev/nvme0n1p3 --fido2-device=auto  --fido2-with-client-pin=yes
🔐 Please enter current passphrase for disk /dev/nvme0n1p3: (no echo)
Initializing FIDO2 credential on security token.
👆 (Hint: This might require confirmation of user presence on security token.)
🔐 Please enter security token PIN: (no echo)
Generating secret key on FIDO2 security token.
👆 In order to allow secret key generation, please confirm presence on security token.
New FIDO2 token enrolled as key slot 0.

Modify /etc/crypttab

We need to modify /etc/crypttab in order to tell cryptsetup to unlock the device using the YubiKey. Add fido2-device=auto in the options field of the crypttab entry for your device. For example:

nvme0n1p3_crypt UUID=307a6bef-5599-4963-8ce0-d9e999026c1a none luks,discard,fido2-device=auto

Switch to dracut

Debian’s default initramfs generator, update-initramfs of the initramfs-tools is using the old cryptsetup for mounting encrypted drives. However, cryptsetup doesn’t recognize the fido2-device option. Running update-initramfs will fail with the following error:

$ sudo update-initramfs -u
update-initramfs: Generating /boot/initrd.img-5.15.0-3-amd64
cryptsetup: WARNING: nvme0n1p3_crypt: ignoring unknown option 'fido2-device'

This is unfortunate. The simplest solution is to switch to dracut, a more modern initramfs generator, which among other things relies on systemd to activate encrypted volumes. This solves the issue of the unknown fido2-device.

Before installing dracut, I would highly recommend creating a copy of the existing initramfs in the boot partition in case something goes wrong.

$ sudo apt install dracut

Dracut includes systemd-cryptsetup by default. systemd-cryptsetup depends on libfido for unlocking devices using FIDO2 tokens. At least in Debian, systemd-cryptsetup dynamically loads libfido2.so (as opposed to being dynamically linked), which causes dracut not to have libfido2.so in the initramfs. This causes systemd-cryptsetup to issue the following error upon boot:

FIDO2 tokens not supported on this build. 

We fix it by manually adding libfido2.so to the initramfs. Of course, we also need to include libfido2’s dependencies as well. Dracut has a mechanism for automatically adding dependencies for executables, but it doesn’t work on libraries. As a workaround, instead of adding libfido2 directly, we will add an executable that depends on libfido2, which will add libfido2 and its dependencies to the initramfs. We will usefido2-token from the fido2-tools package for this trick.

$ sudo apt install fido2-tools
$ cat << EOF | sudo tee /etc/dracut.conf.d/11-fido2.conf
## Spaces in the quotes are critical.
# install_optional_items+=" /usr/lib/x86_64-linux-gnu/libfido2.so.* "

## Ugly workround because the line above doesn't fetch
## dependencies of libfido2.so
install_items+=" /usr/bin/fido2-token "

# Required detecting the fido2 key
install_items+=" /usr/lib/udev/rules.d/60-fido-id.rules /usr/lib/udev/fido_id "
EOF

Now, recreate the initramfs images:

$ sudo dracut -f

Last remarks

At this point, we are done. Reboot you’re machine and it will prompt you for your YubiKey and allow you to unlock your LUKS encrypted root patition with it. If you don’t have your YubiKey, it will give the following prompt:

Security token not present for unlocking volume root (nvme0n1p3_crypt), please plug it in.

After around 30 seconds, it would time out and display the following message:

Timed out waiting for security device, aborting security device based authentication attempt.

Afterwards, it would allow you to unlock the partition using a password (or a recovery key).

In case you run into any trouble, append rd.break=initqueue to the kernel command line, and dracut will enter a shell before attempting to mount the partitions. You can manually mount the drive using the following command:

# /usr/lib/systemd/systemd-cryptsetup attach root /dev/nvme0n1p3

Exit the emergency shell, and the system will continue its normal boot.

28 thoughts on “Unlock LUKS volume with a YubiKey”

  1. Great howto, thanks…

    With some trial and error (comparing the contents of working and non-working initrds), I determined that the only missing library that the /usr/bin/fido2-token trick adds to the initrd is libz.

    So the installation of the fido2-tools package and the fido2-token binary can be skipped and the /etc/dracut.conf.d/11-fido2.conf can be reduced to this:

    install_optional_items+=” /usr/lib/x86_64-linux-gnu/libz.so.* “

  2. I know these instructions were for Debian, but I gave it a shot in Ubuntu 22.04 and it pretty much worked with one exception: if I reboot without my Yubikey being connected, I’m not prompted to connect it and there is no timeout that prompts me for a regular LUKS passphrase. Did I miss a step, or is this expected behavior with Ubuntu 22.04?

  3. Here is output from “systemd –version” on my Ubuntu 22.04 system:

    $ systemd –version
    systemd 249 (249.11-0ubuntu3.4)
    +PAM +AUDIT +SELINUX +APPARMOR +IMA +SMACK +SECCOMP +GCRYPT +GNUTLS +OPENSSL +ACL +BLKID +CURL +ELFUTILS +FIDO2 +IDN2 -IDN +IPTC +KMOD +LIBCRYPTSETUP -LIBFDISK +PCRE2 -PWQUALITY -P11KIT -QRENCODE +BZIP2 +LZ4 +XZ +ZLIB +ZSTD -XKBCOMMON +UTMP +SYSVINIT default-hierarchy=unified

    I don’t know if Canonical has any plans to update to a newer version, or if they plan to backport any upstream changes. There have been tons of problems reported with FDIO2 and systemd-related implementations for unlocking during boot, this seems to be a workaround to get it working. Thanks for your guide, it was very helpful.

  4. I am trying on Ubuntu 22.04.01 jammy, with no success! When I reboot, with Yubikey connected, I am not prompted to connect Yubikey ….

    Any idea? Thanks

    hrouhan@hrz:~$ sudo dracut -f
    dracut: Executing: /usr/bin/dracut -f
    dracut: dracut module ‘bootchart’ will not be installed, because command ‘/sbin/bootchartd’ could not be found!
    dracut: dracut module ‘modsign’ will not be installed, because command ‘keyctl’ could not be found!
    dracut: dracut module ‘rngd’ will not be installed, because command ‘rngd’ could not be found!
    dracut: dracut module ‘plymouth’ will not be installed, because command ‘plymouth-set-default-theme’ could not be found!
    dracut: dracut module ‘btrfs’ will not be installed, because command ‘btrfs’ could not be found!
    dracut: dracut module ‘dmraid’ will not be installed, because command ‘dmraid’ could not be found!
    dracut: dracut module ‘nvmf’ will not be installed, because command ‘nvme’ could not be found!
    dracut: dracut module ‘biosdevname’ will not be installed, because command ‘biosdevname’ could not be found!
    dracut: dracut module ‘masterkey’ will not be installed, because command ‘keyctl’ could not be found!
    dracut: dracut module ‘modsign’ will not be installed, because command ‘keyctl’ could not be found!
    dracut: dracut module ‘rngd’ will not be installed, because command ‘rngd’ could not be found!
    dracut: dracut module ‘btrfs’ will not be installed, because command ‘btrfs’ could not be found!
    dracut: dracut module ‘dmraid’ will not be installed, because command ‘dmraid’ could not be found!
    dracut: dracut module ‘nvmf’ will not be installed, because command ‘nvme’ could not be found!
    dracut: dracut module ‘masterkey’ will not be installed, because command ‘keyctl’ could not be found!
    dracut: *** Including module: bash ***
    dracut: *** Including module: dash ***
    dracut: *** Including module: systemd ***
    dracut: *** Including module: systemd-initrd ***
    dracut: *** Including module: console-setup ***
    dracut: *** Including module: crypt ***
    dracut: *** Including module: dm ***
    dracut: Skipping udev rule: 10-dm.rules
    dracut: Skipping udev rule: 13-dm-disk.rules
    dracut: Skipping udev rule: 64-device-mapper.rules
    dracut: *** Including module: kernel-modules ***
    dracut: *** Including module: kernel-modules-extra ***
    dracut: *** Including module: lvm ***
    dracut: Skipping udev rule: 11-dm-lvm.rules
    dracut: Skipping udev rule: 69-dm-lvm-metad.rules
    dracut: Skipping udev rule: 64-device-mapper.rules
    dracut: *** Including module: overlay-root ***
    dracut: *** Including module: rootfs-block ***
    dracut: *** Including module: terminfo ***
    dracut: *** Including module: udev-rules ***
    dracut: Skipping udev rule: 40-redhat.rules
    dracut: Skipping udev rule: 91-permissions.rules
    dracut: Skipping udev rule: 80-drivers-modprobe.rules
    dracut: Skipping udev rule: 70-persistent-net.rules
    dracut: *** Including module: dracut-systemd ***
    dracut: *** Including module: usrmount ***
    dracut: *** Including module: base ***
    dracut: *** Including module: fs-lib ***
    dracut: *** Including module: shutdown ***
    dracut: *** Including modules done ***
    dracut: *** Installing kernel module dependencies ***
    dracut: *** Installing kernel module dependencies done ***
    dracut: *** Resolving executable dependencies ***
    dracut: *** Resolving executable dependencies done ***
    dracut: *** Hardlinking files ***
    Mode: real
    Files: 627
    Linked: 2 files
    Compared: 0 xattrs
    Compared: 25 files
    Saved: 738 B
    Duration: 0.002411 seconds
    dracut: *** Hardlinking files done ***
    dracut: *** Generating early-microcode cpio image ***
    dracut: *** Constructing GenuineIntel.bin ***
    dracut: *** Store current command line parameters ***
    dracut: Stored kernel commandline:
    dracut: rd.luks.uuid=luks-b18dacce-a670-48e6-aa0a-46da3538c4b7
    dracut: rd.lvm.lv=vgubuntu/root rd.lvm.lv=vgubuntu/swap_1
    dracut: root=/dev/mapper/vgubuntu-root rootfstype=ext4 rootflags=rw,relatime,errors=remount-ro
    dracut: *** Stripping files ***
    dracut: *** Stripping files done ***
    dracut: *** Creating image file ‘/boot/initrd.img-5.15.0-50-generic’ ***
    dracut: *** Creating initramfs image file ‘/boot/initrd.img-5.15.0-50-generic’ done ***

  5. Have you checked that the required files binaries, libraries and the udev rules are included in the initramfs image?

  6. Thanks for great howto, worked perfectly fine on Debian testing. Only thing I would add is when you use different brand than Yubikey (like mine GoTrust Idem Key), you may need to use fido2-token command instead of ykman.

    Checking hmac-secret support:
    $ fido2-token -I /dev/hidraw4 | grep hmac-secret
    extension strings: hmac-secret

    Setting PIN:
    $ fido2-token -S /dev/hidraw4

  7. If the standard Debian splash screens are used, there will be no prompts, just the usual input box for the luks password. To see the prompts, press ESC. It took me a few tries to figure out that the correct order is to enter the pin, , then touch the YubiKey. That works for me.

  8. I can confirm that this works with the library comment of David Härdeman works on Kubuntu 22.10.

  9. To check that the required files binaries, libraries and the udev rules are included in the initramfs image, use “sudo dracut -fv &> dracut.output” and then look for the files in the output.

    Mine has these files but fails to prompt for a fido2 device or password, exactly as hrouhan described. Tested on bullseye (Debian 11), bookworm (aka testing, will be Debian 12), and sid (aka unstable).

    The “rd.break=initqueue” kernel parameter is the only way the system is bootable at this point. The emergency shell can see /dev/nvme0n1p3, so I’m not sure why I’m not getting prompted for the password. I’ve tried removing the “quite” parameter from the kernel, which output the detection of my USB devices, did not help with unlocking nor give me any clues as to what’s going on.

    My latest test was using:
    – Debian sid (unstable)
    – systemd 252 (252.6-1)
    – dracut 059-3

  10. Thanks, instructions worked great on Mint.

    One issue, maybe everyone else knows this (new to Yubikey). I wasn’t able to enroll more than one Yubikey. When I added a second, boot always required the full pass phrase and didn’t allow yubikey at all.

    When I wiped the backup key enrollment from luks, it worked again as expected.

  11. That’s a limitation in the current implementation of systemd-cryptsetup. It has no way to differentiate between the keys at this point.

  12. Same issue as Vansid with Kubuntu 2204.

    systemd version data
    systemd 249 (249.11-0ubuntu3.9)
    +PAM +AUDIT +SELINUX +APPARMOR +IMA +SMACK +SECCOMP +GCRYPT +GNUTLS +OPENSSL +ACL +BLKID +CURL +ELFUTILS +FIDO2 +IDN2 -IDN +IPTC +KMOD +LIBCRYPTSETUP +LIBFDISK +PCRE2 -PWQUALITY -P11KIT -QRENCODE +BZIP2 +LZ4 +XZ +ZLIB +ZSTD -XKBCOMMON +UTMP +SYSVINIT default-hierarchy=unified

  13. When trying this out on debian 12 i have the following problem: I am still being prompted for the LUKS passphrase. And after I enter my passphrase the boot process stops after a few seconds and then the yubico key flashes – when I then signal my presence by touching it the boot process completes. Any idea what is going on here?

  14. I used the rd.break=initqueue on my debian12 install to find out why i wasn’t asked for my fido2 pin. Turns out that within the initrd systemd-cryptsetup ignores the fido2-device=auto option, even when invoked on the command line. “udevadm info” tells me that the hidraw0 device should be tagged as a security device, so I guess udev works correctly. I got all the necessary libraries in my initrd. I do not get any errors or warnings regarding any missing resources. systemd-cryptsetup invariably asks me for a passphrase when run in the initrd, but when i run the same command ( /usr/lib/systemd/systemd-cryptsetup attach luks-5f81fb0a-2396-4ffc-b953-42427265eaa4 /dev/disk/by-uuid/5f81fb0a-2396-4ffc-b953-42427265eaa4 – fido2-device=auto ) when booted up an logged in it dutifully uses my Yubikey to decrypt the device in question.

  15. Tried this, but sadly whenever I boot with the dracut made initrd, the system hangs after loading the kernel.

  16. I finally made it! Wow, after weeks of trying everything I could think of.
    In the end I only needed one single line of dracut config:

    add_dracutmodules+=” fido2 ”

  17. Btw is there also a way to get a mini-ssh going in Dracut? Like my dropbear didn’t work anymore so i had to return to initramfs as i boot remote from time to time

  18. Can you give details about which distribution you use exactly?

    The Debian 12 dracut is currently v059 (which has fido2 dracutmodule available)

    For Ubuntu focal (20.04) jammy (22.04) the version is v049 and v051 respectively, only Ubuntu lunar (23.04) has v059 (has fido2 available)

    So the solution by Matthias Martin may only work for latest dracut versions.

    For me there is still the problem that it only shows a passphrase prompt and not using fido2

  19. Ok, a slight update since last time. The libz library issue is fixed in all Debian versions from stable (Bookworm) onwards, that was this bug (reported by me, fixed by the maintainer):
    https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1016727

    To support FIDO2 with dracut, the steps I needed to take (on Bookworm) were:
    1. echo “hostonly=yes” > /etc/dracut.conf.d/10-hostonly.conf
    2. edit /etc/crypttab so that this line:
    sda3_crypt UUID= none luks,discard
    instead read:
    luks- UUID= none luks,discard,fido2-device=auto
    3. regenerate the initramfs (“dracut -f /boot/initrd.imd-blabla-amd64”)

    Step 1 is necessary to ensure that the fido2 module is included
    Step 2 is necessary to stop systemd from complaining that it’s missing a crypt device (dracut will give the device the luks- name, ignoring the crypttab name)

    Dracut is the same in later Debian versions (testing/unstable), so I expect the steps would work there as well…

  20. Guy, thank for this great howto !
    And David, thank for all these details and bug reporting/fixing.
    Works like a charm on a fresh install of Debian 12.2 with a Nitrokey 3A NFC.

    Here is a summary of all command needed to set this up on a fresh install (my Luks volume is /dev/vda5):

    #
    # Switch from initramfs-tools to Dracut to manage initrd
    #
    nano /etc/default/grub
    # Change the line with GRUB_CMDLINE_LINUX_DEFAULT like this:
    # GRUB_CMDLINE_LINUX_DEFAULT=”quiet rd.auto=1″
    update-grub
    nano /etc/crypttab
    # Change the line:
    # vda5_crypt UUID=165e9c6c-6277-49b1-ac51-94158b504964 none luks,discard
    # to:
    # luks-165e9c6c-6277-49b1-ac51-94158b504964 UUID=165e9c6c-6277-49b1-ac51-94158b504964 none luks,discard
    apt install dracut
    apt purge cryptsetup-initramfs && apt autoremove –purge
    dracut -f
    # Test that the switch to dracut is working by rebooting 😀

    #
    # Enroll your FIDO2 device to unlock Luks volume
    #
    apt install fido2-tools
    # Plug your FIDO2 device
    systemd-cryptenroll –fido2-device=list
    # Check your FIDO2 device is listed
    systemd-cryptenroll –fido2-device=auto /dev/vda5
    nano /etc/crypttab
    # Change the line:
    # luks-165e9c6c-6277-49b1-ac51-94158b504964 UUID=165e9c6c-6277-49b1-ac51-94158b504964 none luks,discard
    # to:
    # luks-165e9c6c-6277-49b1-ac51-94158b504964 UUID=165e9c6c-6277-49b1-ac51-94158b504964 none luks,discard,fido2-device=auto
    echo “hostonly=yes” > /etc/dracut.conf.d/10-hostonly.conf
    dracut -f
    # Test if everything works as expected ! 😀

  21. I have been trying to make this work under Ubuntu 22.04 (upgraded from 20.04, so had to upgrade to luks2 from luks1) following various things that seemed to work in the discussion here and there are a some things that seem to be biting me:

    Cannot do the multiple FIDO2 keys Using Yubikey 5’s. I am assuming the last boot didn’t prompt for the FIDO2 key when the previous attempt did prompt due to trying to use more than one Yubikey as per comments on this thread.
    With just one key registered, it prompts for both the Yubikey 5 and the existing passphrase before trying #1. Not 100% sure why, but I have some strong leads on what has gone wrong, which I elaborate on below.
    Might be related to #2, but when I list the keys on the Yubikey 5 itself, I do not see a FIDO2 key for the luks2 partition. I am not sure how this could work as I don’t see options to setup the header data for the FIDO2 slot on the key. Say I setup with Google, it allows for an identifier, Username, and Display Name with these filled out, but I can change it. I see none of this when registering the Yubikey for luks2. Even doing ssh, it collects / generates these fields and then ssh is happy and I have a nice ID I named myself so I can easily tell what that key is for and which key and thing it is associated with. So I can do MFA with my Yubikey with ssh just fine using FIDO2, but it seems you can’t even provide the field(s) make a proper luks2 specific entry on the Yubikey. I am thinking this is why I don’t see an entry and it is prompting me for the passphrase as if the Yubikey doesn’t have it, it has to fall back to the passphrase.
    With just one Yubikey registered, there didn’t seem to be a timeout to fall back to the passphrase once I got it to work at all after using dracut. As mentioned in #3, I think it is only falling back to the passphrase possibly/likely because the actual Yubikey key never gets a proper entry created on it and so this method fails.

    Unless someone can tell me what I can do differently to make this work, it seems for Ubuntu 22.04 at least this code is not in a working state. Just the part of apparently not having an entry on the Yubikey 5 itself and not sure of how I could add an identifier to let me and the program know, says to me this code is not complete and could not work. Also, the whole thing with using more than one physical key, that is how you are supposed to do everything with these FIDO2 keys. Even with SSH for example, I can just use the -i option and specify the private portion of the key stored on the hard drive for the backup physical key I have and immediately press that backup Yubikey into service with ssh. luks2 has slots for the private portion for multiple keys already, but yet apparently it cannot handle multiple keys.

Leave a Reply

Your email address will not be published. Required fields are marked *