Full Disk Encryption

From NixOS Wiki
Jump to: navigation, search

There are a few options for full disk encryption.

Enter password on Boot (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   /

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

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:

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";
      };
  };
}

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
  };
}

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"
  ];
}

Set Up Full Disk Encryption with Swap Volume and Unencrypted Boot

Here is a tiny tutorial for one of the approaches to achieve full disk encryption with a new installation.

You will be able to achieve full disk encryption with an individual swap and root volume through Logical Volume Management and an unencrypted boot partition to boot up from, storing all EFI files and the boot loader.

Boot into the NixOS installer, and create a disk layout similar to this

sda           8:0    1 114,6G  0 disk 
├─sda1        8:1    1     2G  0 part 
└─sda2        8:2    1 112,6G  0 part

Then you would want to format the disk and make volumes similar to this (feel free to change the names as you like as long as you change them in the later steps)

# format the boot partition
mkfs.fat -F 32 /dev/sda1 -n "NixOS-Boot"
# create an encrypted partition
cryptsetup luksFormat -y --label="NixOS-Encrypted" /dev/sda2
# open the encrypted partition and map it to /dev/mapper/cryptroot
cryptsetup luksOpen /dev/sda2 cryptroot
# create the physical volume
pvcreate /dev/mapper/cryptroot
# create a volume group inside
vgcreate lvmroot /dev/mapper/cryptroot
# create the swap volume
lvcreate --size 8G lvmroot --name swap
# if you desire, create a home volume
lvcreate --size 150G lvmroot --name home
# create the root volume
lvcreate -l 100%FREE lvmroot --name root
# format as usual for root partition
mkfs.ext4 -L "NixOS-Root" /dev/mapper/lvmroot-root
# if you previously made the home partition, do it too
mkfs.ext4 -L "NixOS-Home" /dev/mapper/lvmroot-home
# format the swap partition
mkswap -L "NixOS-Swap" /dev/mapper/lvmroot-swap
# mount root
mount /dev/disk/by-label/NixOS-Root /mnt
# mount boot
mkdir /mnt/boot
mount /dev/sda1 /mnt/boot
# again, if you did the home volume
mkdir /mnt/home
mount /dev/disk/by-label/NixOS-Home /mnt/home
# turn on swap
swapon /dev/disk/by-label/NixOS-Swap

This should be how your disk layout looks now

sda                disk
├─sda1             part  /mnt/boot
└─sda2             part
  └─cryptroot      crypt
    ├─lvmroot-swap lvm   [SWAP]
    └─lvmroot-root lvm   /mnt

Now you would need to modify the hardware-configuration for it to be able to boot with encryption enabled:

{ # cut
  # We need to add "cryptd" as one of our kernel modules, or else the system won't be booted expecting an encrypted partition, which is where our root, swap, (and home) logical volumes resides in.
  boot.initrd.kernelModules = [ "dm-snapshot" "cryptd" ]; # <- Add "cryptd" in it

  fileSystems."/" =
    # Modify this to the name of the root logical volume (name you used in mkfs.ext4)
    { device = "/dev/disk/by-label/NixOS-Root"; # <- Change this
      fsType = "ext4";
    };

  # If you also did the home logical volume
  fileSystems."/home" =
    {  device = "dev/disk/by-label/NixOS-Home"; # <- Change this
      fsType = "ext4";
    };

  # Modify this to the name of the encrypted partition (name you used in cryptsetup luksFormat)
  boot.initrd.luks.devices."cryptroot".device = "/dev/disk/by-label/NixOS-Encrypted"; # If you followed the guide with the same names, or else change "NixOS-Encrypted" to whetever you named it 

  # Modify this to the name of the unencrypted boot partition (name you used in mkfs.fat)
  fileSystems."/boot" =
    { device = "/dev/disk/by-label/NixOS-Boot"; # <- Change this
      fsType = "vfat";
    };
  # Modify this to the name of the swap logical volume (name you used in mkswap)
  swapDevices = [{device = "/dev/disk/by-label/NixOS-Swap"}]; # <- Change this
}

Now continue configure your system in configuration.nix as usual, and you would achieve full disk encryption with separate swap partition (and home partition).

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 = {
    mode = "0600";
    text = ''
      # <volume-name> <encrypted-device> [key-file] [options]
      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.

Using Veracrypt

To use a Veracrypt encrypted disk (support for Veracrypt is built into most recent kernel versions), and to mount it automatically during boot, you can use the following (replacing inside <angle brackets>:

{
  environment.etc.crypttab = {
    mode = "0600";
    text = ''
      <your-name-for-disk> PARTUUID=<UUID> <path-to-password-file> tcrypt,tcrypt-veracrypt
    '';
  };
  # Veracrypt mount
  fileSystems."</path/to/mount>" =
    { device = "/dev/mapper/<your-name-for-disk>";
      # For customising filesystem type
      # fsType = "ntfs-3g";
      # options = [ "defaults,rw,dmask=027,fmask=037,uid=1000,guid=1000,windows_names,permissions,nofail 0 0" ];
    };
}

Further reading