Jump to content

Full Disk Encryption: Difference between revisions

From NixOS Wiki
imported>Zimbatm
Add section about SDDM autologin using LUKS password
 
(32 intermediate revisions by 23 users not shown)
Line 1: Line 1:
= Basic installation =
There are a few options for full disk encryption. The easiest way is to use the graphical installer and choose "encrypt" while doing the installation.


* [https://gist.github.com/martijnvermaat/76f2e24d0239470dd71050358b4d5134 Installation of NixOS with encrypted root]
= LVM on LUKS =
* Have a look at https://wiki.archlinux.org/index.php/Disk_encryption to see all the possible options. This wiki page is not complete.
 
In this example, everything except for the <code>/boot</code> partition is encrypted.
This includes the root and swap partitions.
A password must be entered during boot to unlock the encrypted filesystems.
 
The main drive (here the <code>sda</code> block device) will need two partitions:
# An unencrypted <code>/boot</code> partition (EFI system partition) formatted as FAT.
# A LUKS-encrypted logical volume group for everything else (swap and <code>/</code>).
 
When unlocked and mounted, it will look like this:
 
<syntaxhighlight lang="text">
NAME          MAJ:MIN RM  SIZE RO TYPE  MOUNTPOINT
sda            8:0    0 233.8G  0 disk
├─sda1          8:1    0  500M  0 part  /boot
└─sda2          8:2    0 233.3G  0 part
  └─root      254:0    0 233.3G  0 crypt
    ├─vg-swap 254:1    0    8G  0 lvm  [SWAP]
    └─vg-root 254:2    0 225.3G  0 lvm  /
</syntaxhighlight>
 
== Enter password on Boot ==
 
The initrd needs to be configured to unlock the encrypted <code>/dev/sda2</code> partition during stage 1 of the boot process.
To do this, add the following options (replacing <code>UUID-OF-SDA2</code> with the actual UUID of the encrypted partition <code>/dev/sda2</code>. -- You can find it using <code>lsblk -f</code> or <code>sudo blkid -s UUID /dev/sda2</code>.)
 
<syntaxhighlight lang="nix">
    boot = {
      loader = {
        efi.canTouchEfiVariables = true;
        grub = {
          enable = true;
          device = "nodev";
          efiSupport = true;
        };
      };
      initrd.luks.devices.cryptroot.device = "/dev/disk/by-uuid/UUID-OF-SDA2";
    };
</syntaxhighlight>
 
With <code lang="nix">initrd.luks.devices.cryptroot.device = "/dev/disk/by-uuid/UUID-OF-SDA2";</code>, the initrd knows it must unlock <code>/dev/sda2</code> before activating LVM and proceeding with the boot process.


= Unattended Boot via USB =
== Unattended Boot via USB ==


Sometimes it is necessary to boot a system without needing an Keyboard and Monitor. You will create a secret key, add it to a key slot and put it onto an usb stick.
Sometimes it is necessary to boot a system without needing an keyboard and monitor. You will create a secret key, add it to a key slot and put it onto an USB stick.


<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
dd if=/dev/urandom of=hdd.key bs=4096 count=1
dd if=/dev/random of=hdd.key bs=4096 count=1
cryptsetup luksAddKey /dev/sda1 ./hdd.key
cryptsetup luksAddKey /dev/sda1 ./hdd.key
</syntaxhighlight>You can enable fallback to password (in case the USB stick is lost or corrupted) by setting the <code>boot.initrd.luks.devices.<name>.fallbackToPassword</code> option to <code>true</code>. By default, this option is <code>false</code> so you will have to perform a manual recovery if the USB stick becomes unavailable (which you may prefer, depending on your use case).
=== Option 1: Write key onto the start of the stick ===
This will make the usb-stick unusable for any other operations than being used for decryption. Write the key onto the stick:
<syntaxhighlight lang="bash">
dd if=hdd.key of=/dev/sdb
</syntaxhighlight>
</syntaxhighlight>
== Option 1: Write key onto the start of the stick ==
This will make the usb-stick unusable for any other operations than being used for decryption. Write they key onto the stick: <code>dd if=hdd.key of=/dev/sdb</code>.


Then add the following configuration to your <code>configuration.nix</code>:
Then add the following configuration to your <code>configuration.nix</code>:
Line 21: Line 66:
   "..."
   "..."


   boot.initrd.luks.devices = [
  # Needed to find the USB device during initrd stage
    {  
  boot.initrd.kernelModules = [ "usb_storage" ];
       name = "luksroot";
 
      device = "/dev/disk/by-id/<disk-name>-part2";
   boot.initrd.luks.devices = {
      allowDiscards = true;
       luksroot = {
      keyFileSize = 4096;
        device = "/dev/disk/by-id/<disk-name>-part2";
      # pinning to /dev/disk/by-id/usbkey works
        allowDiscards = true;
      keyFile = "/dev/sdb";
        keyFileSize = 4096;
    }
        # pinning to /dev/disk/by-id/usbkey works
   ];
        keyFile = "/dev/sdb";
        # optionally enable fallback to password in case USB is lost
        fallbackToPassword = true;
      };
   };
}</syntaxhighlight>
}</syntaxhighlight>
As of right now (2017-08-18) the NixOS options do not provide means to hide a key after the MBR as described in [https://bbs.archlinux.org/viewtopic.php?id=158507 this article in the archlinux forums]. More specificially you will need to be able to provide a keyOffset


== Option 2: Copy Key as file onto a vfat usb stick ==
=== Option 2: Copy Key as file onto a vfat usb stick ===


If you want to use your stick for other stuff or it already has other keys on it you can use the following method by Tzanko Matev. Add this to your <code>configuration.nix</code>:
If you want to use your stick for other stuff or it already has other keys on it you can use the following method by Tzanko Matev. Add this to your <code>configuration.nix</code>:
Line 62: Line 110:
}
}
</syntaxhighlight>
</syntaxhighlight>
== Unattended Boot via keyfile ==
A simpler but insecure option for unattended boots is to copy the keyfile into the initrd itself.
{{warning|1=This method is not generally recommended as anyone with physical access to your boot partition will be able to retrieve the key file and use it to decrypt your luks partition. Make sure you understand the security implications.}}
First move the key to a safe location.
<syntaxhighlight lang="bash">
mkdir /var/lib/secrets
chown root:root /var/lib/secrets
chmod 700 /var/lib/secrets
mv -v hdd.key /var/lib/secrets/
chmod 600 /var/lib/secrets/hdd.key
</syntaxhighlight>
Then add the key to the initrd.
<syntaxhighlight lang="nix">
let
  keyFile = "hdd.key";
in
{
  boot.initrd.luks.devices."root" = {
    device = "/dev/disk/by-uuid/<uuid>";
    keyFile = "/${keyFile}";
  };
  boot.initrd.secrets = { "/${keyFile}" = /var/lib/secrets/${keyFile}; };
}
</syntaxhighlight>
== Store key on FIDO2 device or TPM ==
Unattended boot can also happen with a FIDO2 device (e.g. Yubikey) or TPM. This cannot be performed in a fully declarative way because every such security device is unique; some manual running of <code>systemd-cryptenroll</code> is required.
For FIDO2, directly read the [https://github.com/NixOS/nixpkgs/blob/7be68f763d94cdb4c809b7980647828e3274a511/nixos/doc/manual/configuration/luks-file-systems.section.md chapter in the official manual].
For TPM, replace the crypttab and systemd-cryptsetup option <code>fido2-device=auto</code> with <code>tpm-device=auto</code> for systemd stage 1. See [https://github.com/NixOS/nixpkgs/blob/7be68f763d94cdb4c809b7980647828e3274a511/nixos/tests/systemd-initrd-luks-tpm2.nix this integration test] in the nixpkgs source code repository.
Because the TPM is attached to your computer, it provides no protection against a stolen computer when used on its own (it usually allows for setting a password, but that is it). It can only protect against a stolen drive.


= zimbatm's laptop recommendation =
= zimbatm's laptop recommendation =
Line 67: Line 153:
Let's say that you have a GPT partition with EFI enabled. You might be booting on other OSes with it. Let's say that your disk layout looks something like this:
Let's say that you have a GPT partition with EFI enabled. You might be booting on other OSes with it. Let's say that your disk layout looks something like this:


<syntaxhighlight lang="bash">
<syntaxhighlight lang="text">
   8        0  500107608 sda
   8        0  500107608 sda
   8        1    266240 sda1      - the EFI partition
   8        1    266240 sda1      - the EFI partition
Line 79: Line 165:


<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
# format the disk with the luks structure
# format the partition with the luks structure
$ cryptsetup luksFormat /dev/sda4
cryptsetup luksFormat /dev/sda4
# open the encrypted partition and map it to /dev/mapper/cryptroot
# open the encrypted partition and map it to /dev/mapper/cryptroot
$ cryptsetup luksOpen /dev/sda4 cryptroot
cryptsetup luksOpen /dev/sda4 cryptroot
# format as usual
# format as usual
$ mkfs.ext4 -L nixos /dev/mapper/cryptroot
mkfs.ext4 -L nixos /dev/mapper/cryptroot
# mount
# mount
$ mount /dev/disk/by-label/nixos /mnt
mount /dev/disk/by-label/nixos /mnt
$ mkdir /mnt/boot
mkdir /mnt/boot
$ mount /dev/sda1 /mnt/boot
mount /dev/sda1 /mnt/boot
</syntaxhighlight>
</syntaxhighlight>


Line 132: Line 218:
{
{
   boot.initrd.availableKernelModules = [
   boot.initrd.availableKernelModules = [
    "aes_x86_64"
     "aesni_intel"
     "aesni_intel"
     "cryptd"
     "cryptd"
Line 139: Line 224:
</syntaxhighlight>
</syntaxhighlight>


== See also ==
= Unlocking secondary drives =
 
Consider the following example: a secondary hard disk <code>/dev/sdb</code> is to be LUKS-encrypted and unlocked during boot, in addition to <code>/dev/sda</code>.
 
Encrypt the drive and create the filesystem on it (LVM is used in this example):
<syntaxhighlight lang="bash">
cryptsetup luksFormat --label CRYPTSTORAGE /dev/sdb
cryptsetup open /dev/sdb cryptstorage
pvcreate /dev/mapper/cryptstorage
vgcreate vg-storage /dev/mapper/cryptstorage
lvcreate -l 100%FREE -n storage vg-storage
mkfs.ext4 -L STORAGE /dev/vg-storage/storage
</syntaxhighlight>
 
To unlock this device on boot in addition to the encrypted root filesystem, there are two options:
 
=== Option 1: Unlock before boot using a password ===
 
Set the following in <code>configuration.nix</code> (replacing <code>UUID-OF-SDB</code> with the actual UUID of <code>/dev/sdb</code>):
<syntaxhighlight lang="nix">
{
  boot.initrd.luks.devices.cryptstorage.device = "/dev/disk/by-uuid/UUID-OF-SDB";
}
</syntaxhighlight>
During boot, a password prompt for the second drive will be displayed. Passwords previously entered are tried automatically to also unlock the second drive. This means that if you use the same passwords to encrypt both your main and secondary drives, you will only have to enter it once to unlock both.
 
The decrypted drive will be unlocked and made available under <code>/dev/mapper/cryptstorage</code> for mounting.
 
One annoyance with this approach is that reusing entered passwords only happens on the initial attempt. If you mistype the password for your main drive on the first try, you will now have to re-enter it twice, once for the main drive and again for the second drive, even if the passwords are the same.
 
=== Option 2: Unlock after boot using crypttab and a keyfile ===
 
Alternatively, you can create a keyfile stored on your root partition to unlock the second drive just before booting completes. This can be done using the <code>/etc/crypttab</code> file (see manpage <code>crypttab(5)</code>).
 
First, create a keyfile for your secondary drive, store it safely and add it as a LUKS key:
<syntaxhighlight lang="bash">
dd bs=512 count=4 if=/dev/random of=/root/mykeyfile.key iflag=fullblock
chmod 400 /root/mykeyfile.key
cryptsetup luksAddKey /dev/sdb /root/mykeyfile.key
</syntaxhighlight>
 
Next, create <code>/etc/crypttab</code> in <code>configuration.nix</code> using the following option (replacing <code>UUID-OF-SDB</code> with the actual UUID of <code>/dev/sdb</code>):
<syntaxhighlight lang="nix">
{
  environment.etc.crypttab.text = ''
    cryptstorage UUID=UUID-OF-SDB /root/mykeyfile.key
  '';
}
</syntaxhighlight>
 
With this approach, the secondary drive is unlocked just before the boot process completes, without the need to enter its password.
 
Again, the secondary drive will be unlocked and made available under <code>/dev/mapper/cryptstorage</code> for mounting.
 
= Autologin using LUKS password =
 
One downside of full disk encryption is that you need to type in your password twice, once for unlocking the disk and once to log into your desktop. One approach is to skip the LUKS password, such as by using a TPM2, but is [https://oddlama.org/blog/bypassing-disk-encryption-with-tpm2-unlock/ difficult to properly secure]. The other approach is to enable autologin for your display manager:
<syntaxhighlight lang="nix">
{
  services.displayManager.autoLogin.user = "my username";
}
</syntaxhighlight>
 
However, this breaks software such as KWallet which uses the login password to automatically unlock its keyring. The solution is to set the LUKS password, login password, and KWallet keyring password all to the same string, and then use the LUKS password to unlock KWallet. The LUKS password is first collected by a systemd initrd, saved to the kernel keyring, read out by SDDM via a PAM module, then finally passed off to KWallet.
<syntaxhighlight lang="nix">
{
  boot.initrd.systemd.enable = true;
  systemd.services.display-manager.serviceConfig.KeyringMode = "inherit";
  security.pam.services.sddm-autologin.text = pkgs.lib.mkBefore ''
    auth optional ${pkgs.systemd}/lib/security/pam_systemd_loadkey.so
    auth include sddm
  '';
}
</syntaxhighlight>
 
= Further reading =
* [https://shen.hong.io/installing-nixos-with-encrypted-root-partition-and-seperate-boot-partition/ Installing NixOS with LUKS2, Detached LUKS Header, and A Separate Boot Partition on an USB/MicroSD Card]
* [https://gist.github.com/martijnvermaat/76f2e24d0239470dd71050358b4d5134 Installation of NixOS with encrypted root]
* [[NixOS_on_ZFS#Encrypted_ZFS|Encryption in ZFS]]
* [[Yubikey based Full Disk Encryption (FDE) on NixOS|Using a Yubikey as the authentication mechanism]] (unattended boot and two factor boot with user password).
* Have a look at https://wiki.archlinux.org/index.php/Disk_encryption to see all the possible options. This wiki page is not complete.
* [https://gist.github.com/ladinu/bfebdd90a5afd45dec811296016b2a3f Installation with encrypted /boot]
* [[Remote disk unlocking|Using Tor and SSH to unlock your LUKS Disk over the internet]].
* [[Bcachefs]], filesystem which supports native encryption
* [https://discourse.nixos.org/t/full-disk-encryption-tpm2/29454/2 Automatically unlock encrypted disks using TPM2]


- [[NixOS_on_ZFS#Encrypted_ZFS|Encryption in ZFS]]
[[Category:Desktop]]
[[Category:Server]]

Latest revision as of 15:29, 20 August 2025

There are a few options for full disk encryption. The easiest way is to use the graphical installer and choose "encrypt" while doing the installation.

LVM on LUKS

In this example, everything except for the /boot partition is encrypted. This includes the root and swap partitions. A password must be entered during boot to unlock the encrypted filesystems.

The main drive (here the sda block device) will need two partitions:

  1. An unencrypted /boot partition (EFI system partition) formatted as FAT.
  2. A LUKS-encrypted logical volume group for everything else (swap and /).

When unlocked and mounted, it will look like this:

NAME          MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINT
sda             8:0    0 233.8G  0 disk
├─sda1          8:1    0   500M  0 part  /boot
└─sda2          8:2    0 233.3G  0 part
  └─root      254:0    0 233.3G  0 crypt
    ├─vg-swap 254:1    0     8G  0 lvm   [SWAP]
    └─vg-root 254:2    0 225.3G  0 lvm   /

Enter password on Boot

The initrd needs to be configured to unlock the encrypted /dev/sda2 partition during stage 1 of the boot process. To do this, add the following options (replacing UUID-OF-SDA2 with the actual UUID of the encrypted partition /dev/sda2. -- You can find it using lsblk -f or sudo blkid -s UUID /dev/sda2.)

    boot = {
      loader = {
        efi.canTouchEfiVariables = true;
        grub = {
          enable = true;
          device = "nodev";
          efiSupport = true;
        };
      };
      initrd.luks.devices.cryptroot.device = "/dev/disk/by-uuid/UUID-OF-SDA2";
    };

With initrd.luks.devices.cryptroot.device = "/dev/disk/by-uuid/UUID-OF-SDA2";, the initrd knows it must unlock /dev/sda2 before activating LVM and proceeding with the boot process.

Unattended Boot via USB

Sometimes it is necessary to boot a system without needing an keyboard and monitor. You will create a secret key, add it to a key slot and put it onto an USB stick.

dd if=/dev/random of=hdd.key bs=4096 count=1
cryptsetup luksAddKey /dev/sda1 ./hdd.key

You can enable fallback to password (in case the USB stick is lost or corrupted) by setting the boot.initrd.luks.devices.<name>.fallbackToPassword option to true. By default, this option is false so you will have to perform a manual recovery if the USB stick becomes unavailable (which you may prefer, depending on your use case).

Option 1: Write key onto the start of the stick

This will make the usb-stick unusable for any other operations than being used for decryption. Write the key onto the stick:

dd if=hdd.key of=/dev/sdb

Then add the following configuration to your configuration.nix:

{
  "..."

  # Needed to find the USB device during initrd stage
  boot.initrd.kernelModules = [ "usb_storage" ]; 

  boot.initrd.luks.devices = {
      luksroot = {
         device = "/dev/disk/by-id/<disk-name>-part2";
         allowDiscards = true;
         keyFileSize = 4096;
         # pinning to /dev/disk/by-id/usbkey works
         keyFile = "/dev/sdb";
         # optionally enable fallback to password in case USB is lost
         fallbackToPassword = true;
      };
  };
}

Option 2: Copy Key as file onto a vfat usb stick

If you want to use your stick for other stuff or it already has other keys on it you can use the following method by Tzanko Matev. Add this to your configuration.nix:

let
  PRIMARYUSBID = "b501f1b9-7714-472c-988f-3c997f146a17";
  BACKUPUSBID = "b501f1b9-7714-472c-988f-3c997f146a18";
in {

  "..."

  # Kernel modules needed for mounting USB VFAT devices in initrd stage
  boot.initrd.kernelModules = ["uas" "usbcore" "usb_storage" "vfat" "nls_cp437" "nls_iso8859_1"];

  # Mount USB key before trying to decrypt root filesystem
  boot.initrd.postDeviceCommands = pkgs.lib.mkBefore ''
    mkdir -m 0755 -p /key
    sleep 2 # To make sure the usb key has been loaded
    mount -n -t vfat -o ro `findfs UUID=${PRIMARYUSBID}` /key || mount -n -t vfat -o ro `findfs UUID=${BACKUPUSBID}` /key
  '';

  boot.initrd.luks.devices."crypted" = {
    keyFile = "/key/keyfile";
    preLVM = false; # If this is true the decryption is attempted before the postDeviceCommands can run
  };
}

Unattended Boot via keyfile

A simpler but insecure option for unattended boots is to copy the keyfile into the initrd itself.

⚠︎
Warning: This method is not generally recommended as anyone with physical access to your boot partition will be able to retrieve the key file and use it to decrypt your luks partition. Make sure you understand the security implications.

First move the key to a safe location.

mkdir /var/lib/secrets
chown root:root /var/lib/secrets
chmod 700 /var/lib/secrets
mv -v hdd.key /var/lib/secrets/
chmod 600 /var/lib/secrets/hdd.key

Then add the key to the initrd.

let
  keyFile = "hdd.key";
in
{
  boot.initrd.luks.devices."root" = {
    device = "/dev/disk/by-uuid/<uuid>";
    keyFile = "/${keyFile}";
  };
  boot.initrd.secrets = { "/${keyFile}" = /var/lib/secrets/${keyFile}; };
}

Store key on FIDO2 device or TPM

Unattended boot can also happen with a FIDO2 device (e.g. Yubikey) or TPM. This cannot be performed in a fully declarative way because every such security device is unique; some manual running of systemd-cryptenroll is required.

For FIDO2, directly read the chapter in the official manual.

For TPM, replace the crypttab and systemd-cryptsetup option fido2-device=auto with tpm-device=auto for systemd stage 1. See this integration test in the nixpkgs source code repository.

Because the TPM is attached to your computer, it provides no protection against a stolen computer when used on its own (it usually allows for setting a password, but that is it). It can only protect against a stolen drive.

zimbatm's laptop recommendation

Let's say that you have a GPT partition with EFI enabled. You might be booting on other OSes with it. Let's say that your disk layout looks something like this:

   8        0  500107608 sda
   8        1     266240 sda1       - the EFI partition
   8        2      16384 sda2
   8        3  127388672 sda3
   8        4  371409920 sda4    - the NixOS root partition
   8        5    1024000 sda5

Boot the NixOS installer and partition things according to your taste. What we are then going to do is prepare sda4 with a luks encryption layer:

# format the partition with the luks structure
cryptsetup luksFormat /dev/sda4
# open the encrypted partition and map it to /dev/mapper/cryptroot
cryptsetup luksOpen /dev/sda4 cryptroot
# format as usual
mkfs.ext4 -L nixos /dev/mapper/cryptroot
# mount
mount /dev/disk/by-label/nixos /mnt
mkdir /mnt/boot
mount /dev/sda1 /mnt/boot

Now keep installing as usual, nixos-generate-config should detect the right partitioning. You should have something like this in your /etc/nixos/hardware-configuration.nix:

{ # cut
  fileSystems."/" =
    { device = "/dev/disk/by-uuid/5e7458b3-dcd2-49c6-a330-e2c779e99b66";
      fsType = "ext4";
    };

  boot.initrd.luks.devices."cryptroot".device = "/dev/disk/by-uuid/d2cb12f8-67e3-4725-86c3-0b5c7ebee3a6";

  fileSystems."/boot" =
    { device = "/dev/disk/by-uuid/863B-7B32";
      fsType = "vfat";
    };

  swapDevices = [ ];
}

To create a swap add the following in your /etc/nixos/configuration.nix:

{
  swapDevices = [{device = "/swapfile"; size = 10000;}];
}

Perf test

# compare
nix-shell -p hdparm --run "hdparm -Tt /dev/mapper/cryptroot"
# with
nix-shell -p hdparm --run "hdparm -Tt /dev/sda1"

I had to add a few modules to initrd to make it fast. Since cryptroot is opened really early on, all the AES descryption modules should already be made available. This obviously depends on the platform that you are on.

{
   boot.initrd.availableKernelModules = [
    "aesni_intel"
    "cryptd"
  ];
}

Unlocking secondary drives

Consider the following example: a secondary hard disk /dev/sdb is to be LUKS-encrypted and unlocked during boot, in addition to /dev/sda.

Encrypt the drive and create the filesystem on it (LVM is used in this example):

cryptsetup luksFormat --label CRYPTSTORAGE /dev/sdb
cryptsetup open /dev/sdb cryptstorage
pvcreate /dev/mapper/cryptstorage
vgcreate vg-storage /dev/mapper/cryptstorage
lvcreate -l 100%FREE -n storage vg-storage
mkfs.ext4 -L STORAGE /dev/vg-storage/storage

To unlock this device on boot in addition to the encrypted root filesystem, there are two options:

Option 1: Unlock before boot using a password

Set the following in configuration.nix (replacing UUID-OF-SDB with the actual UUID of /dev/sdb):

{
  boot.initrd.luks.devices.cryptstorage.device = "/dev/disk/by-uuid/UUID-OF-SDB";
}

During boot, a password prompt for the second drive will be displayed. Passwords previously entered are tried automatically to also unlock the second drive. This means that if you use the same passwords to encrypt both your main and secondary drives, you will only have to enter it once to unlock both.

The decrypted drive will be unlocked and made available under /dev/mapper/cryptstorage for mounting.

One annoyance with this approach is that reusing entered passwords only happens on the initial attempt. If you mistype the password for your main drive on the first try, you will now have to re-enter it twice, once for the main drive and again for the second drive, even if the passwords are the same.

Option 2: Unlock after boot using crypttab and a keyfile

Alternatively, you can create a keyfile stored on your root partition to unlock the second drive just before booting completes. This can be done using the /etc/crypttab file (see manpage crypttab(5)).

First, create a keyfile for your secondary drive, store it safely and add it as a LUKS key:

dd bs=512 count=4 if=/dev/random of=/root/mykeyfile.key iflag=fullblock
chmod 400 /root/mykeyfile.key
cryptsetup luksAddKey /dev/sdb /root/mykeyfile.key

Next, create /etc/crypttab in configuration.nix using the following option (replacing UUID-OF-SDB with the actual UUID of /dev/sdb):

{
  environment.etc.crypttab.text = ''
    cryptstorage UUID=UUID-OF-SDB /root/mykeyfile.key
  '';
}

With this approach, the secondary drive is unlocked just before the boot process completes, without the need to enter its password.

Again, the secondary drive will be unlocked and made available under /dev/mapper/cryptstorage for mounting.

Autologin using LUKS password

One downside of full disk encryption is that you need to type in your password twice, once for unlocking the disk and once to log into your desktop. One approach is to skip the LUKS password, such as by using a TPM2, but is difficult to properly secure. The other approach is to enable autologin for your display manager:

{
  services.displayManager.autoLogin.user = "my username";
}

However, this breaks software such as KWallet which uses the login password to automatically unlock its keyring. The solution is to set the LUKS password, login password, and KWallet keyring password all to the same string, and then use the LUKS password to unlock KWallet. The LUKS password is first collected by a systemd initrd, saved to the kernel keyring, read out by SDDM via a PAM module, then finally passed off to KWallet.

{
  boot.initrd.systemd.enable = true;
  systemd.services.display-manager.serviceConfig.KeyringMode = "inherit";
  security.pam.services.sddm-autologin.text = pkgs.lib.mkBefore ''
    auth optional ${pkgs.systemd}/lib/security/pam_systemd_loadkey.so
    auth include sddm
  '';
}

Further reading