Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

USB Stick Keyfile

✔️ Click to Expand Table of Contents

This allows you to use a USB stick for your keyfile, with a backup in case you want or need it. There is a setting fallbackToPassword that protects you in case something fails with the USB key.

First, I'll show how to set up a dedicated USB stick for a keyfile. (i.e., one that is only used for this). After that I will show the process of adding the keyfile to a USB stick with existing data on it that you don't want to lose.

Generate the keyfile

sudo dd if=/dev/urandom of=/root/usb-luks.key bs=4096 count=1

Keyfile Enrollment Methods

This is for a dedicated USB stick that we will wipe first then add the key.

Disko defaults to LUKS2

# cryptsetup works for both LUKS1 and LUKS2 formats but doesn't work for
# TPM2, FIDO2, and smartcards
sudo cryptsetup luksAddKey /dev/disk/by-partlabel/luks /root/usb-luks.key

OR

✔️ Click to expand Experimental TPM2 auto-unlock for LUKS

⚠️ WARNING: Security Implications of TPM2 Auto-Unlock

Enabling TPM2 auto-unlock fundamentally changes your system's security model. While this feature protects against certain forms of malicious software injection by tying the decryption key to the system's boot state, it eliminates the need for a user password at boot. This creates a significant risk if your machine is stolen or seized, do not use this feature if the physical security of your machine is a concern. This is still at a stage where you can expect rough edges and workarounds.

You can add an additional layer by encrypting user data, such as individual home folders, with a different mechanism, such as fscrypt-experimental or systemd-homed. Or, you can use a TPM pin to benefit from the security properties of the TPM, while avoiding completely unattended unlocking. --Arch Wiki

I am reading that fscrypt is no longer experimental.

security.pam.enableFscrypt = true;
sudo fscrypt setup --all-users
sudo mv /home/<user> /home/old<user>
sudo mkdir /home/<user>
sudo chown <user>:users /home/<user>
sudo fscrypt encrypt --source pam_passphrase --user <user> --skip-unlock /home/<user>/

--☝️Discourse

It is fairly complex as to how TPM2 auto-unlock can improve security in some ways, it has to do with how Linux distributions fail to authenticate the boot process past the initrd.Even with encryption and Secure Boot enabled, the initrd stage often remains unverified, meaning a tampered initrd could be substituted without detection.

TPMs protect secrets by releasing them only if the boot process can be authenticated through "measurements." During boot, each component involved (firmware, bootloader, kernel, etc.) is hashed, and these hashes are extended into special TPM registers called Platform Configuration Registers (PCRs). These PCRs hold a cumulative, tamper-evident record of the boot process state.

If any part of the boot sequence changes (even slightly), the PCR values will differ from the expected, causing the TPM to refuse to release the bound secret (such as a disk decryption key). This ensures that the system only boots or unlocks secrets when its software stack is known and trusted, providing strong protection against tampering or unauthorized modifications. The values aren't only protected by these PCRs but encrypted with a "seed key" that's generated on the TPM chip itself, and cannot leave the TPM.

Check TPM support:

cat /sys/class/tpm/tpm0/device/description
TPM 2.0 Device

Check for necessary software dependencies:

systemd-analyze has-tpm2

Find your encrypted partition with lsblk:

lsblk

First, you need to use the systemd-cryptenroll command to add a TPM2 key to your encrypted LUKS partition. This process binds a key slot on your disk to the state of your TPM2 chip's PCRs (Platform Configuration Registers).

# This command adds a new key to the LUKS volume, using a key generated by the TPM2 chip.
# It binds the key to PCRs 0,2,7,and 15 ensuring the key is only released if the firmware
# and Secure Boot state of your system is unchanged.
sudo systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0+2+7+15 /dev/disk/by-partlabel/luks

There are quite a few options for the above command, some use the following with less pcrs and a wipe feature:

sudo systemd-cryptenroll --wipe-slot=tpm2 --tpm2-device=auto --tpm2-pcrs=0+7 /dev/disk/by-partlabel/luks
  • Using less pcrs could prevent breakage but reduces security. Check out the PCR Definitions below and decide if you require additional PCRs or less.

  • wipe-slot tells the system to delete any key associated with the TPM2 chip from the LUKS volume's keyslot before adding a new one.

You can choose a more complex --tpm2-pcrs for more security but it makes the configuration more fragile because any legitimate system update altering any measured component tied to these PCRs will prevent the TPM from releasing the key and lock you out, unless you re-enroll the key with the updated PCR values.

That said, I do often see people mention a firmware update breaking their TPM2 auto-unlock functionality. Keep this in mind and have a backup plan. This is also incompatible with the encrypted impermanence setup shared in this book, the boot.initrd.postDeviceCommands conflict.

Change YourUser to your username and ensure that cryptroot is the name of yours, if you followed this books encrypted disko install it should be:

  # Adds your user to the 'tss' group, allowing you to interact with the TPM
  users.users.YourUser.extraGroups = [ "tss" ];
  # Enables TPM2 services and tools on your system
  security.tpm2.enable = true;
  # Ensure the necessary kernel modules are in the initrd
  boot.initrd.kernelModules = ["tpm_tis"];
  # switches the initrd to a systemd-based environment, required for TPM2
  boot.initrd.systemd.enable = true;
  # ❗ Tell the initrd to use the TPM2 key for the encrypted root
  boot.initrd.luks.devices.cryptroot = {
    device = "/dev/disk/by-partlabel/luks";
    # These options tell systemd-cryptsetup to automatically try to unlock the device
    # using the TPM2 key. 'tpm2-measure=yes' ensures the PCRs are verified but only works if you use one disk
    crypttabExtraOpts = ["tpm2-device=auto" "tpm2-measure=yes"];
    fallbackToPassword = true;
  };
  environment.systemPackages = [ pkgs.tpm2-tss ];

❗ NOTE: cryptroot needs to match what your encrypted partition is named, I have seen quite a few different names here.

If you use this, you can't also use the USB Keyfile or the included impermanence guide.

Description

  • /dev/disk/by-partlabel/luks refers to your encrypted partition by its partition label, which is stable and less likely to change than /dev/nvme0n1p2

  • /root/usb-luks.key is the keyfile we generated.

  • You'll be prompted to enter your existing LUKS passphrase to authorize adding the new key.

  • Now our LUKS volume will accept both our existing passphrase and the new keyfile (from the USB stick) for unlocking.

  1. Clear Data on USB stick and replace with 0's
lsblk
NAME        MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
sda           8:0    1   239M  0 disk
sdb           8:16   1   1.4M  0 disk  /run/media/jr/7CD1-149A # Example USB mount
zram0       253:0    0   7.5G  0 disk  [SWAP]
nvme0n1     259:0    0 476.9G  0 disk
├─nvme0n1p1 259:1    0   512M  0 part  /boot
└─nvme0n1p2 259:2    0 476.4G  0 part
  └─cryptroot 254:0  0 476.4G  0 crypt /persist  # Main Btrfs mount
                                               # (other subvolumes are within /persist and bind-mounted by impermanence)
# unplug the device and run lsblk again so your sure
  1. Before wiping you must unmount any mounted partitions:
sudo umount /dev/sda1
# Overwrite with Zeros (fast, sufficient for most uses)
sudo dd if=/dev/zero of=/dev/sda bs=4M status=progress
# Or overwrite with Random Data (More Secure, Slower)
sudo dd if=/dev/urandom of=/dev/sda bs=4M status=progress
# Or for the most secure way run multiple passes of
sudo shred -v -n 3 /dev/sda
  1. Create a New Partition and Format (Optional)
sudo fdisk /dev/sda
  1. Press o to create a new empty DOS partition table (if you are creating partitions on a fresh disk or want to wipe existing partitions and start over). Be very careful with this step as it will erase all existing partition information on the disk.

  2. Press n to create a new partition.

  • You will then be prompted for the partition type:

    • p for a primary partition (you can have up to 4 primary partitions)

    • e for an extended partition (which can contain logical partitions)

  • Next, you'll be asked for the partition number (e.g., 1, 2, 3, 4).

  • Then, you'll be asked for the first sector (press Enter to accept the default, which is usually the first available sector after the previous partition or the beginning of the disk).

  • Finally, you'll be asked for the last sector or size (you can specify a size like +10G for 10 Gigabytes, +512M for 512 Megabytes, or press Enter to use the rest of the available space).

  1. Press w to write the changes to the partition table and exit fdisk.

After pressing w, the kernel needs to be aware of the new partition table. Sometimes this happens automatically, but if you encounter issues, a reboot or a command like partprobe (if available and needed) can help.

Formats as FAT32:

sudo mkfs.vfat /dev/sda1
# or as ext4
sudo mkfs.ext4 /dev/sda1

I chose vfat so I ran sudo mkfs.vfat /dev/sda1. In my case this changed the device path to /run/media/jr/7CD1-149A so it's important to find your own UUID with the following command:

sudo blkid /dev/sda1
/dev/sda1: SEC_TYPE="msdos" UUID="B7B4-863B" BLOCK_SIZE="512" TYPE="vfat" PARTUUID="7d1f9d7f-01"
  • As you can see the above UUID is "B7B4-863B"

  • Remove and re-insert the USB stick, this ensures the system recognizes the new partition and filesystem.

  1. Copy the keyfile to your USB Stick
sudo cp /root/usb-luks.key /run/media/jr/B7B4-863B/
sync
  1. Update your NixOS Configuration

Note the output of blkid /dev/sda1 and if you have a backup device list that also:

The following is from the wiki edited for my setup, it was created by Tzanko Matev:

let
  PRIMARYUSBID = "B7B4-863B";
  BACKUPUSBID = "Ventoy";
in {

  boot.initrd.kernelModules = [
    "uas"
    "usbcore"
    "usb_storage"
    "vfat"
    "nls_cp437"
    "nls_iso8859_1"
  ];

  boot.initrd.postDeviceCommands = lib.mkBefore ''
    mkdir -p /key
    sleep 2
    mount -n -t vfat -o ro $(findfs UUID=${PRIMARYUSBID}) /key || \
    mount -n -t vfat -o ro $(findfs UUID=${BACKUPUSBID}) /key || echo "No USB key found"
  '';

  boot.initrd.luks.devices.cryptroot = {
    device = "/dev/disk/by-partlabel/luks";
    keyFile = "/key/usb-luks.key";
    fallbackToPassword = true;
    allowDiscards = true;
    preLVM = false; # Crucial!
  };
}

If you have issues or just want to remove the key take note of the path used to add it so you don't have to enter the whole key:

sudo cryptsetup luksRemoveKey /dev/disk/by-partlabel/luks --key-file /root/usb-luks.key
  1. Securely Remove the Keyfile from Your System:
sudo shred --remove --zero /root/usb-luks.key

Instructions for Using a USB Stick with Existing Data

  1. Generate the Keyfile
sudo dd if=/dev/urandom of=/root/usb-luks.key bs=4096 count=1
  1. Add the Keyfile to your LUKS Volume
sudo cryptsetup luksAddKey /dev/disk/by-partlabel/luks /root/usb-luks.key

(enter your existing passphrase when prompted)

  1. Copy the Keyfile to the USB Stick
  • Plug in the USB Stick and note its mount point (e.g.,/run/media/$USER/YourLabel)

  • Copy the keyfile:

sudo cp /root/usb-luks.key /run/media/$USER/YourLabel/
sync
  • You run the above as 2 commands, the second being sync.

  • You can rename it if you wish (e.g., luks.key)

  1. Securely Delete the Local Keyfile
sudo shred --remove --zero /root/usb-luks.key
  • You need to ensure the keyfile is accessible in the initrd. Since automounting (like /run/media/...) does not happen in initrd, you must manually mount the USB in the initrd using its UUID or label.

Find the USB Partition UUID:

lsblk -o NAME,UUID
# or
blkid /dev/sda1

Suppose the UUID is B7B4-863B

Add to your configuration.nix:

boot.initrd.kernelModules = [ "usb_storage" "vfat" "nls_cp437" "nls_iso8859_1" ];

boot.initrd.postDeviceCommands = lib.mkBefore ''
  mkdir -p /key
  sleep 1
  mount -n -t vfat -o ro $(findfs UUID=B7B4-863B) /key || echo "USB not found"
'';

boot.initrd.luks.devices.cryptroot = {
  device = "/dev/disk/by-partlabel/luks";
  keyFile = "/key/usb-luks.key"; # or whatever you named it
  fallbackToPassword = true;
  allowDiscards = true;
};