The Debian cryptsetup package supports a setup for decrypting LUKS partitions using an OpenPGP smartcard. Additionally, the official Nitrokey website offers helpful instructions to simplify the configuration of this same package.
In this post, I aim to provide further explanations about this configuration and how it works, making it easier to replicate and adapt on non-Debian-based systems as well.
Assuming you already have a disk encrypted with LUKS, a typical configuration for your /etc/crypttab
file might resemble the following:
sda5_crypt UUID=a766ec9b-1eba-423b-9181-d7ad033d8063 none luks
Let’s recap the meaning of these parameters:
-
sda5_crypt
is the name of the device that will contain the decrypted data; -
UUID=...
is the identifier of the disk; while you could also use/dev/sda5
, it is generally preferred to use UUIDs because they remain stable even if the kernel renames devices when new hardware is added; -
none
indicates that the user must type the passphrase to unlock the disk; here one could specify the path to a key file that can be used to unlock the disk automatically (more on this in a while); -
luks
means that LUKS encryption is being used; additional cryptsetup options can be included here, separated by commas.
The cryptsetup option that is needed to perform the unlocking using an OpenPGP smartcard is the keyscript
option. A keyscript is a script whose output is provided to cryptsetup as the decryption key. It should contain only the key itself, without any trailing newline.
When a keyscript is used, the third option (which was set to none
in the previous example) can be set to a path of a file that is passed as argument to the keyscript.
So, to enable the LUKS-GPG setup we can modify /etc/crypttab
as follow:
sda5_crypt UUID=a766ec9b-1eba-423b-9181-d7ad033d8063 /etc/cryptsetup-initramfs/cryptkey.gpg luks,keyscript=decrypt_gnupg-sc
The file /etc/cryptsetup-initramfs/cryptkey.gpg
contains a disk encryption key that has been encrypted using our GPG public key (the one associated with the smartcard), while decrypt_gnupg-sc
is the keyscript provided by the Debian cryptsetup package, located in /lib/cryptsetup/scripts/decrypt_gnupg-sc
. To be able to decrypt the partition on boot the cryptkey.gpg file must be added to the initramfs
. This inclusion is automatically handled by the update-initramfs
command, which we will execute at the end of the process.
The cryptkey
file can be generated directly reading from /dev/urandom
:
dd if=/dev/urandom bs=1 count=256 > cryptkey
The file can then be encrypted using your public GPG key and copied to /etc/cryptsetup-initramfs/cryptkey.gpg
:
gpg --recipient yourname@example.com --output cryptkey.gpg --encrypt cryptkey
The /etc/cryptsetup-initramfs
directory must also include your public key. This is necessary for GPG to set up the smartcard stubs during boot. A GPG stub acts as a placeholder for the private key, which resides on the smartcard.
To copy the public key to the appropriate location, run the following command:
gpg --export yourname@example.com > /etc/cryptsetup-initramfs/pubring.gpg
Now we can encrypt the disk using the new key. If you are working with a new disk, you could directly run luksFormat
with the new key. However, I recommend using luksAddKey
on a LUKS setup that is already configured with a standard passphrase. This approach minimizes the risk of being locked out of your files, as you will still be able to unlock the disk using the original passphrase.
If this sounds weird consider that LUKS can work with multiple keys, stored in different “keyslots”. Each keyslot contains a copy of a master key encrypted with a different key. The master key is the one actually used for filesystem encryption. LUKS may attempt to decrypt the disk with the prompt you provided many times, trying all the keyslots until it finds a keyslot that works for your input (more info).
To add the keyfile to a new keyslot, you can use the following command, which will prompt you for the previous passphrase:
cryptsetup luksAddKey --new-keyfile=cryptkey /dev/sda5
We can then check the keyslots using the command cryptsetup luksDump /dev/sda5
. The output will show 2 LUKS keyslots:
Keyslots:
0: luks2
...
1: luks2
...
It is now a good idea to remove the original decrypted cryptkey
file. Consider using shred to delete it in a safer way.
At the end we need to update the initramfs to apply the new configuration:
update-initramfs -u
This command will execute the script located at /usr/share/initramfs-tools/hooks/cryptgnupg-sc
, which adds to the initramfs the files cryptkey.gpg and pubring.gpg, along with all the necessary executables to run GPG with the smartcard. It also selects the appropriate pinentry program (the application that prompts for your smartcard PIN), choosing between pinentry-curses
or pinentry-tty
as the second option.
Grub splash
option (which provides a visually appealing loading screen) may cause issues with keyboard input for the pinentry. Therefore, it is advisable to change the splash
option to nosplash
in the /etc/default/grub
file:
GRUB_CMDLINE_LINUX_DEFAULT="nosplash"
Then run the update-grub
command to apply the changes.
Reboot and unlock the disk with your smartcard!
Minimal working example
A minimal working example for education purposes can be achieved with a much simpler keyscript:
#!/bin/sh
gpg --quiet --import /etc/public-key.pgp >&2
# Needed to "wake up" the card
gpg --quiet --card-status >&2
gpg --quiet --decrypt /etc/cryptkey.gpg
This script doesn’t handle the possibility to retry inserting the PIN after a failed attempt.
The following initramfs hook has to be added to /etc/initramfs-tools/hooks/gpg
before rebuilding the initramfs:
#!/bin/sh
. /usr/share/initramfs-tools/hook-functions
cp /etc/gpg-luks/cryptkey.gpg "$DESTDIR/etc/"
cp /etc/gpg-luks/public-key.pgp "$DESTDIR/etc/"
copy_exec "/usr/bin/gpg"
copy_exec "/usr/bin/gpg-agent"
copy_exec "/usr/bin/pinentry-tty" "/bin/pinentry"
copy_exec "/usr/bin/gpg-connect-agent"
copy_exec "/usr/lib/gnupg/scdaemon"
exit 0