In this guide, we describe how to set up an encrypted filesystem with Yubikey pre-boot authentication (PBA) on NixOS. While the focus is on NixOS, the same techniques should be able to be used on any Linux system where Linux Unified Key Setup (LUKS) is available.
This guide is inspired by and based on Yubikey based Full Disk Encryption (FDE) on NixOS.
Other methods exist for other Linux distributions:
- ArchLinux: Yubikey Full Disk Encryption
- Debian: Yubikey for Luks
We have the option of using either one (1FA) or two (2FA) factors for authentication. Using 1FA, the Yubikey must be inserted to open the LUKS device, but no extra passphrase is required. With 2FA, once the Yubikey is inserted, we'll be asked to enter a passphrase in order to open the LUKS device.
We'll program the Yubikey in Challenge-Response (HMAC-SHA1) mode in an alternate slot. Then we'll calculare the salt
and iterations
and store them on an unencrypted partition. These values will be used to calculate the challenge for the Yubikey. The response, along with a user-entered passphrase in 2FA, will be used to calculate the LUKS key.
At boot time, NixOSs Yubikey PBA will read the salt
and iterations
, which is again used to calculate the challenge. The Yubikey's response will be used to calculate the LUKS key. If we're using 2FA, we'll enter a passphrase which will be combined with the challenge-response key. If the key is successfully unlocked, NixOS will recalculate the salt
and iterations
values, and the expected Yubikey response. It will use the response to update the LUKS key so the passphrase is different at each time the machine is booted.
Before beginning the process, it's assumed that you have
- An unencrypted partition (Here we use ESP, but any partition is fine)
- A Yubikey with a free configuration slot
- A running NixOS system
For convenience, I've created a Nix expression that includes all dependencies. Enter the nix-shell:
nix-shell https://github.com/sgillespie/nixos-yubikey-luks/archive/master.tar.gz
If you don't want to use the nix expression, we can set up the same environment manually.
You'll need the following software dependencies:
- cryptsetup
- gcc
- openssl
- pbkdf2-sha512
- yubikey-personalization
pkdf2-sha512
is a simple program included in nixpkgs that exposes OpenSSLs PKDF2 implementation. Grap the source file at https://raw.githubusercontent.com/NixOS/nixpkgs/master/nixos/modules/system/boot/pbkdf2-sha512.c and compile it.
Finally, we need a couple of bash helper functions.
rbtohex() {
( od -An -vtx1 | tr -d ' \n' )
}
hextorb() {
( tr '[:lower:]' '[:upper:]' | sed -e 's/\([0-9A-F]\{2\}\)/\\\\\\x\1/gI'| xargs printf )
}
Add these to a shell file and source it.
Program the Yubikey's free slot challenge-response mode (HMAC-SHA1). We let the Yubikey generate a key for us.
ykpersonalize -2 -ochal-resp -ochal-hmac
Create a new partition. As an example, we'll create a new 100G partition on sdb starting at 208G.
parted /dev/sdb -- mkpart primary 208G 308G
Generate the initial salt. It can be any integer value between 16 and 64.
SALT_LENGTH=16
SALT="$(dd if=/dev/random bs=1 count=$SALT_LENGTH 2>/dev/null | rbtohex)"
Enter the 2FA passphrase, if desired
read -s USER_PASSPHRASE
Calculate the initial challenge and response
CHALLENGE="$(echo -n $SALT | openssl dgst -binary -sha512 | rbtohex)"
RESPONSE=$(ykchalresp -2 -x $CHALLENGE 2>/dev/null)
Calculate the LUKS slot key from the desired factors
KEY_LENGTH=512
ITERATIONS=1000000
If you want to use 2FA
LUKS_KEY="$(echo -n $USER_PASSPHRASE | pbkdf2-sha512 $(($KEY_LENGTH / 8)) $ITERATIONS $RESPONSE | rbtohex)"
Otherwise
LUKS_KEY="$(echo | pbkdf2-sha512 $(($KEY_LENGTH / 8)) $ITERATIONS $RESPONSE | rbtohex)"
Create the LUKS device.
CIPHER=aes-xts-plain64
HASH=sha512
As an example, we'll use the partition we created in Step 1: /dev/sdb5
.
echo -n "$LUKS_KEY" | hextorb | cryptsetup luksFormat --cipher="$CIPHER" \
--key-size="$KEY_LENGTH" --hash="$HASH" --key-file=- /dev/sdb5
Store the salt and iterations on an unencrypted partition. Here, we use the ESP
partition mounted on /boot
mkdir -p /boot/crypt-storage
echo -ne "$SALT\n$ITERATIONS" > /boot/crypt-storage/default
Open the LUKS device. As an example, we again use /dev/sdb5.
echo -n "$LUKS_KEY" | hextorb | cryptsetup open /dev/sdb5 encrypted --key-file=-
We can now access the volume at /dev/mapper/encrypted
. For example, to format it as ext4
mkfs.ext4 /dev/mapper/encrypted
Open up your hardware configuration at /etc/nixos/hardware-configuration.nix
and set up the new LUKS-encrypted disk
boot.initrd = {
# Required to open the EFI partition and Yubikey
kernelModules = ["vfat" "nls_cp437" "nls_iso8859-1" "usbhid"];
luks = {
# Support for Yubikey PBA
yubikeySupport = true;
devices."encrypted" = {
device = "/dev/sdb5"; # Be sure to update this to the correct volume
yubikey = {
slot = 2;
twoFactor = true; # Set to false for 1FA
gracePeriod = 30; # Time in seconds to wait for Yubikey to be inserted
keyLength = 64; # Set to $KEY_LENGTH/8
saltLength = 16; # Set to $SALT_LENGTH
storage = {
device = "/dev/sdb1"; # Be sure to update this to the correct volume
fsType = "vfat";
path = "/crypt-storage/default";
};
};
};
};
};
Rebuild your NixOS configuration and reboot
nixos-rebuild boot # Rebuild NixOS configs and set as the default for next boot
reboot