Setting up Yubikey GPG with LUKS and Dracut

Tuesday 18 August 2026 ยท 30 mins read ยท Viewed 4 times

Table of contents ๐Ÿ”—

Introduction ๐Ÿ”—

Full disk encryption with LUKS is the most common setup to protect your data at rest. However, there is drawback: storing the secret is not convenient. Either you have to remember a secret password, or you have to store a secret key somewhere.

To avoid entering their password twice, most people will setup a secret key, but store it alongside the initramfs, in the boot/EFI partition! This is a big vulnerability as the key can be easily stolen if the attacker make the simple effort to read the boot/EFI partition. It defeats the purpose of LUKS.

In this article, I will show you how to properly setup LUKS with a Yubikey (or any GPG smartcard) and use it with Dracut.

The in-depth explanation ๐Ÿ”—

The boot process ๐Ÿ”—

Let's talk quickly about the Linux boot process. Linux boot process is:

  1. Load the EFI Partition.
  2. Load Grand Unified Bootload (GRUB).
  3. Load the Kernel (/boot/vmlinuz-).
  4. Load the initramfs (/boot/initramfs-.img).
  5. Call the init system (/sbin/init).
  6. Boot SystemD and the services.

The "play" of LUKS is around the step 4 and 5.

The initramfs and Dracut ๐Ÿ”—

The initramfs is a lightweight Linux OS that is invoked during the boot process to setup:

  • The kernel modules
  • The firmware and microcode loading
  • The root filesystem mounting (and various other mounts)

Or, it can also do... nothing. Dracut is the software used to configure the initramfs and instruct what to do to boot the system. Some people uses for network booting, diskless booting, ... and in our case, decrypting the root filesystem before mounting it.

LUKS, GPG and Yubikey ๐Ÿ”—

Before going too much in details about Dracut modules, let's just explain what are the roles of LUKS, GPG and Yubikey.

LUKS encrypts the root filesystem with a symmetric key. That symmetric key is generated using a cryptographic pseudo-random number generator (CPRNG).

To store the symmetric key, we can use PGP (Pretty Good Privacy) to encrypt the symmetric key with a GnuPG public (asymmetric) key. And the private key dedicated to decrypt the encrypted key is stored on the Yubikey, the secure medium.

CPRNG(Cryptographic PRNG)Symmetric KeyRoot FilesystemLUKSEncrypted Symmetric KeyGnuPG Public Key(asymmetric)GnuPG Private Key(asymmetric)Yubikey(secure medium) generatesused byencryptsencrypted usinggeneratesstoreddecrypted usingrecovers
LUKS, GPG and Yubikey

To summarize:

  • The LUKS encrypted key is stored in the boot partition.
  • The GPG public key is embedded in the initramfs.
  • The GPG private key is stored in the Yubikey.

No secret is persisted and exposed.

To do the "play" indicated above (LUKS key recovery and root filesystem decryption), Dracut modules are used.

Dracut modules, the final pieces ๐Ÿ”—

The dracut modules dedicated LUKS aren't very complex and some people has already remade them even though the solution already exists (probably because it isn't very much documented). The flow is simple:

Build phase

  • Embed cryptsetup and linux kernel modules required to mount the root filesystem.
  • Embed gpg and the gpg-agent. Make sure gpg has been compiled with smartcard and libusb support.
    • Also embed scdaemon to manage smartcards.
    • Also embed the user's GPG public key, to identify which private key to use.
  • Load the kernel modules to also display the prompt.

Runtime phase

After loading and parsing all the kernel command line parameters, and upon reaching the crypt module cmdline hook, the following happens:

  • Is the key found?
    • If yes:
      • Is smartcard supported? - If yes, call gpg --card-status to healthcheck then prompt the user to enter the PIN of the Yubikey (using gpg --pinentry-mode=loopback --decrypt).
      • Else, prompt the user to enter the GPG passphrase (using gpg --decrypt)
    • Else:
      • Prompt the user to enter the LUKS passphrase (using cryptsetup luksOpen)

Upon decrypting the root filesystem, cryptsetup will mount the root filesystem to /dev/mapper/<name>, in which the user has set the root= parameter to this path, permitting the system to boot.

That's it! All of this is explained at:

The guide ๐Ÿ”—

System preparation ๐Ÿ”—

Assuming you have booted on the USB drive, the following sections will be about how to setup LUKS. You can skip it if you already have LUKS setup.

Disk preparation ๐Ÿ”—

We'll setup the disk as follows:

1Device Location     Label        Mountpoint  Size   Filesystem  Usage
2/dev/nvme0n1
3 โ”œโ”€โ”€ /dev/nvme0n1p1 [EFI]        /boot/efi   1 GB   fat32       Bootloader
4 โ””โ”€โ”€ /dev/nvme0n1p2 [BOOTX]      /boot       1 GB   ext4        Bootloader support files, kernel and initramfs
5 โ””โ”€โ”€ /dev/nvme0n1p3 [ROOT]       (root)      ->END  luks        Encrypted root device, mapped to the name 'cryptroot'
6      โ””โ”€โ”€  /dev/mapper/cryptroot /           ->END  ext4        Root filesystem

Disk partition setup ๐Ÿ”—

Enter fdisk and setup GPT label ๐Ÿ”—
root@usblinux:/
1fdisk /dev/nvme0n1
root@usblinux:/ # fdisk
 1Welcome to fdisk (util-linux 2.38.1).
 2Changes will remain in memory only, until you decide to write them.
 3Be careful before using the write command.
 4
 5Device does not contain a recognized partition table.
 6Created a new DOS disklabel with disk identifier 0x81391dbc.
 7
 8Command (m for help): g
 9
10Created a new GPT disklabel (GUID: 3E57DFCE-CDD9-6F42-8418-F0B6B4A08294).
Create the EFI System Partition (ESP) ๐Ÿ”—
root@usblinux:/ # fdisk
1Command (m for help): n
2
3Partition number (1-128, default 1): โ†ต
4First sector (2048-1953525134, default 2048): โ†ต
5Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-1953525134, default 1953523711): +1G
6
7Created a new partition 1 of type 'Linux filesystem' and of size 1 GiB.

Add the EFI System property:

root@usblinux:/ # fdisk
1Command (m for help): t
2
3Selected partition 1
4Partition type or alias (type L to list all): 1
5Changed type of partition 'Linux filesystem' to 'EFI System'.
Create the linux boot partition ๐Ÿ”—
root@usblinux:/ # fdisk
1Command (m for help): n
2
3Partition number (2-128, default 2): โ†ต
4First sector (2099200-1953525134, default 2099200): โ†ต
5Last sector, +/-sectors or +/-size{K,M,G,T,P} (2099200-1953525134, default 1953523711): +1G
6
7Created a new partition 2 of type 'Linux filesystem' and of size 1 GiB.

Add the Linux Extended Boot property:

root@usblinux:/ # fdisk
1Command (m for help): t
2
3Partition number (1-2, default 2): โ†ต
4Partition type or alias (type L to list all): 142
5Changed type of partition 'Linux filesystem' to 'Linux Extended Boot'.
Create the LUKS partition ๐Ÿ”—
root@usblinux:/ # fdisk
1Command (m for help): n
2
3Partition number (3-128, default 3): โ†ต
4First sector (2099200-1953525134, default 2099200): โ†ต
5Last sector, +/-sectors or +/-size{K,M,G,T,P} (2099200-1953525134, default 1953523711): โ†ต
6
7Created a new partition 3 of type 'Linux filesystem' and of size 930.5 GiB.

Add the Linux Root (x86-64) property:

root@usblinux:/ # fdisk
1Command (m for help): t
2
3Partition number (1-3, default 3): โ†ต
4Partition type or alias (type L to list all): 23
5Changed type of partition 'Linux filesystem' to 'Linux Root (x86-64)'.
Update the partition table ๐Ÿ”—
root@usblinux:/ # fdisk
1Command (m for help): w
2The partition table has been altered.
3Calling ioctl() to re-read partition table.
4Syncing disks.

Basic LUKS setup ๐Ÿ”—

Set up phassphrase encrypted volume ๐Ÿ”—

As an initial setup, it's better to setup a passphrase encrypted volume as a fallback, as losing a key file would be catastrophic. Since we are setting up secret keys, we recommend setting up a long passphrase.

root@usblinux:/
1cryptsetup luksFormat --key-size 512 /dev/nvme0n1p3
root@usblinux:/ # cryptsetup
1WARNING!
2========
3This will overwrite data on /dev/nvme0n1p3 irrevocably.
4
5Are you sure? (Type 'yes' in capital letters):
6YES
7Enter passphrase for /dev/nvme0n1p3:

Format the Filesystems ๐Ÿ”—

root@usblinux:/
1mkfs.vfat -F32 /dev/nvme0n1p1
2mkfs.ext4 -L boot /dev/nvme0n1p2
3cryptsetup luksOpen /dev/nvme0n1p3 cryptroot
4mkfs.ext4 -L rootfs /dev/mapper/cryptroot

For the next steps, mount as follows:

root@usblinux:/
1mount /dev/mapper/cryptroot /mnt/rootfs
2mount /dev/nvme0n1p2 /mnt/rootfs/boot
3mount /dev/nvme0n1p1 /mnt/rootfs/boot/efi
4
5mount --bind /dev /mnt/rootfs/dev
6mount --bind /proc /mnt/rootfs/proc
7mount --bind /sys /mnt/rootfs/sys

At this point, you should continue the Linux installation.

If you're done, continue to the next section.

Set up the encrypted LUKS key ๐Ÿ”—

Set up GPG keys on the Yubikey ๐Ÿ”—

Check if the YubiKey is detected:

root@usblinux:/
1gpg --card-status

Then enter the generation menu:

root@usblinux:/
1gpg --card-edit

Generate a new key:

root@usblinux:/ # gpg --card-edit
 1Command> generate
 2
 3Make off-card backup of encryption key? (Y/n) n
 4
 5gpg: 3 Admin PIN attempts remaining before card is permanently locked
 6
 7Admin PIN
 8
 9PIN
10
11Please specify how long the key should be valid.
12         0 = key does not expire
13         <n>  = key expires in n days
14         <n>w = key expires in n weeks
15         <n>m = key expires in n months
16         <n>y = key expires in n years
17Key is valid for? (0) 0
18
19Key does not expire at all.
20Is this correct? (y/N) y
21
22You need a user ID to identify your key; the software constructs the user ID
23from the Real Name, Comment and Email Address in this form:
24"Heinrich Heine (Der Dichter) <heinrichh@duesseldorf.de>"
25
26Real name: John Doe
27Email address: john@example.com
28Comment: tester
29You selected this USER-ID:
30    "John Doe (tester) <john@example.com>"
31
32Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O

At this point, the key is stored on the Yubikey. Exit the generation menu with exit.

Create a keyfile and add it to the LUKS volume ๐Ÿ”—

Create the key file:

root@usblinux:/
1dd bs=8388608 count=1 if=/dev/urandom of=/run/luks.key
2
3# 1+0 records in
4# 1+0 records out
5# 8388608 bytes (8.4 MB, 8.0 MiB) copied, 0.0211386 s, 397 MB/

Register the key file with the LUKS volume:

root@usblinux:/
1cryptsetup luksAddKey /dev/nvme0n1p3 /run/luks.key

Encrypt the key file with the Yubikey using GPG ๐Ÿ”—

You must encrypt the key file with the Yubikey and with a passphrase. It will allow you to recover the key file if the Yubikey is lost.

root@usblinux:/
1gpg --encrypt \
2  --recipient john@example.com \
3  --cipher-algo aes256 \
4  --armor \
5  --symmetric \
6  --output /run/luks.key.gpg \
7  /run/luks.key

Move the encrypted key file to the EFI partition:

root@usblinux:/
1mv /run/luks.key.gpg /mnt/rootfs/boot/luks.key.gpg

Since we worked in the /run directory, no cleartext data has been persisted. We are persisting the encrypted key file in the EFI partition.

During the boot process, in the initramfs, we will load the encrypted key file, the private key from the Yubikey and decrypt it to use it to unlock the LUKS volume.

The dracut module responsible for LUKS uses FIFOs to avoid leaking the key file.

Setting up dracut and the initramfs ๐Ÿ”—

To use the correct private key, the GPG agent needs to load a public key. Export the public key from the Yubikey:

root@usblinux:/
1gpg --export -a john@example.com > /mnt/rootfs/etc/dracut.conf.d/crypt-public-key.gpg

Add the following content to the /mnt/rootfs/etc/dracut.conf.d/crypt.conf file:

root@usblinux:/mnt/rootfs/etc/dracut.conf.d/crypt.conf
1add_dracutmodules+=" crypt crypt-gpg dm rootfs-block "
2# The leading and trailing space are important.

Since we use GRUB, to simplify the initramfs generation, we embed the kernel command line parameters in the initramfs. Get the block device UUID using blkid:

root@usblinux:/
1blkid -s UUID
2
3# /dev/nvme0n1p1: UUID="029D-E51D"
4# /dev/nvme0n1p2: UUID="bb2c5038-021d-4189-97aa-82f9c51ebfa1"
5# /dev/nvme0n1p3: UUID="9ddc623a-aad5-4739-94ba-62898308437a"

Then append the following parameters to the /mnt/rootfs/etc/dracut.conf.d/crypt.conf file:

root@usblinux:/mnt/rootfs/etc/dracut.conf.d/crypt.conf
1kernel_cmdline+=" root=/dev/mapper/cryptroot rd.luks.uuid=9ddc623a-aad5-4739-94ba-62898308437a rd.luks.name=9ddc623a-aad5-4739-94ba-62898308437a=cryptroot rd.luks.key=/luks.key.gpg:UUID=bb2c5038-021d-4189-97aa-82f9c51ebfa1 "
2# The leading and trailing space are important.
WARNING

Due to an issue with Plymouth, the "eye-candy" splash screen, the GPG prompt might not show up.

In this case, you need to disable Plymouth:

root@usblinux:/mnt/rootfs/etc/dracut.conf.d/crypt.conf
1kernel_cmdline+=" rd.plymouth=0 "

At this point, you need to rebuild the initramfs. Mount bind the directories and chroot:

root@usblinux:/
1chroot /mnt/rootfs
root@linux:/
1# Look for the kernel version before typing the command:
2dracut -fv --kver <kernel version>
3# Ex: dracut -fv --kver 5.15.0-1-amd64

We assume the initramfs has been built in the /boot directory. Check the /boot/initramfs-<kernel version>.img file:

root@linux:/
1ls -lah /boot/
2
3# -rw-------  1 root root  50M Aug 17 21:05 initramfs-5.15.0-1-amd64.img

Since the kernel/initramfs hasn't changed, we shouldn't need to update the GRUB configuration.

Booting the system ๐Ÿ”—

Upon boot, after GRUB, you should be greeted by a prompt:

root@initramfs:/
1PIN (OpenPGP card <serial number>) [1/3]:

If the Yubikey hasn't been inserted, the prompt will look like this:

root@initramfs:/
1gpg: selecting card failed: No such device
2gpg: OpenPGP card not available: No such device
3Password (/luks.key.gpg on /dev/nvme0n1p2 for /dev/nvme0n1p3) [1/3]:

And if you fail to enter the correct passphrase 3 times in a row, the prompt will look like this:

root@initramfs:/
1Wrong password
2Nothing to read on input.
3Enter passphrase for /dev/nvme0n1p3:

Conclusion ๐Ÿ”—

Hope you've learned something. LUKS decryption using Yubikey isn't very well known, so I wrote this article in hope to spread crypt-gpg usefulness.

I thank Alexander Moch for this wonderful article (Using a YubiKey to unlock LUKS and Root on ZFS with native encryption), which started my journey into setting up LUKS with Yubikey, until I've learned I could just use crypt-gpg and don't need to do custom Dracut modules.

Anyway, all I can say is that LUKS is cool, and with Yubikey, even cooler.