0

I want to encrypt the root filesystem on an Ubuntu Server 24.04 installation on my Raspberry Pi 5. I don't have any cables for the weird microHDMI connector they use on the new Pis, so it would be nice to do this headless. Root filesystem is located on an SD card, and I would like to use key files for encryption instead of passwords. There is this answer and this guide, but nothing about a way to do this headless, unfortunately.

1 Answers1

0

I've been struggling to find any way to do this, until I was reminded about the existence of dropbear-initramfs by this answer. This seems to be the easiest solution by far. Just install an SSH server into initramfs. Here's the steps I followed, to make it work.

All commands are to be run as the superuser, sudo -s to open root shell.

Steps marked with may require changing commands or file contents with data specific to your situation.

Preparing initramfs

  1. Install the system onto the SD card (I used Raspberry Pi Imager), insert into your RPi and power it on. Update it by running

    apt update
    apt upgrade
    reboot
    
  2. Install dropbear-initramfs and busybox:

    apt update
    apt install dropbear-initramfs busybox
    
  3. Add your SSH public key to /etc/dropbear/initramfs/authorized_keys so that you're able to access the SSH server once it's running. You can copy the key from your machine:

    cat ~/.ssh/id_rsa.pub
    

    And paste it into the appropriate file on the RPi:

    vi /etc/dropbear/initramfs/authorized_keys
    
  4. Edit /etc/initramfs-tools/initramfs.conf so that initramfs acquires an IP address from DHCP. Find the end of the file, and add:

    IP=::::<hostname>::dhcp:::
    

    Replace <hostname> with the hostname the Pi should have.

    SSH may be mad about Pi constantly changing signature. Use this answer if desperate.

  5. Edit /etc/initramfs-tools/modules to add the kernel modules necessary using for LUKS and encryption to initramfs. Add the following at the end of the file:

    algif_skcipher
    aes_arm64
    aes_ce_blk
    aes_ce_ccm
    aes_ce_cipher
    sha256_arm64
    cbc
    dm-crypt
    
  6. Add the tools for working with filesystems to initramfs by creating /etc/initramfs-tools/hooks/luks_hooks with the following content:

    #!/bin/sh -e
    PREREQS=""
    case $1 in
            prereqs) echo "${PREREQS}"; exit 0;;
    esac
    

    . /usr/share/initramfs-tools/hook-functions

    copy_exec /sbin/e2fsck /sbin copy_exec /sbin/resize2fs /sbin copy_exec /sbin/fdisk /sbin copy_exec /sbin/cryptsetup /sbin

    Don't forget to make it executable:

    chmod +x /etc/initramfs-tools/hooks/luks_hooks
    
  7. Update initramfs to apply all of these changes:

    update-initramfs -u
    
  8. You can verify that initramfs has all the necessary tools and modules by using the following commands.

    lsinitramfs /boot/initrd.img-<...> | grep -P "sbin/(cryptsetup|resize2fs|fdisk|e2fsck)"
    

    It should print:

    usr/sbin/cryptsetup
    usr/sbin/e2fsck
    usr/sbin/fdisk
    usr/sbin/resize2fs
    

    And

    lsinitramfs /boot/initrd.img-<...> | grep -P "(algif_skcipher|aes-arm64|sha256-arm64|cbc|dm-crypt)"
    

    should print:

    usr/lib/modules/<...>/kernel/arch/arm64/crypto/aes-arm64.ko.zst
    usr/lib/modules/<...>/kernel/arch/arm64/crypto/sha256-arm64.ko.zst
    usr/lib/modules/<...>/kernel/crypto/algif_skcipher.ko.zst
    usr/lib/modules/<...>/kernel/crypto/pcbc.ko.zst
    usr/lib/modules/<...>/kernel/crypto/xcbc.ko.zst
    usr/lib/modules/<...>/kernel/drivers/md/dm-crypt.ko.zst
    

    For me, <...> is 6.8.0-1020-raspi, which I suppose is the kernel version. It will probably be different for you, though. Take care to replace it in all commands above with your version.

Preparing boot

  1. Generate a key to use for encryption and place it in /boot/firmware, as this is a location that is easily accessible from initramfs:

    apt update
    apt install uuid
    cd /boot/firmware
    dd if=/dev/random bs=512 count=4 of=$(uuid).lek
    
  2. Edit /etc/fstab to use the encrypted volume which we will create in a few steps. Change the first line to:

    /dev/mapper/rootfs  /   ext4    defaults    0   0
    
  3. Update initramfs, ignoring its dire warnings:

    update-initramfs -u
    
  4. Edit /boot/firmware/cmdline.txt:

    • change root=LABEL=writable to root=/dev/mapper/rootfs
    • add cryptdevice=/dev/mmcblk0p2:rootfs at the end of the line

    Some articles want you to also add break=init at the end of the line, but if you do that, it'll drop you into initramfs two times, first because it can't boot, and then because you asked for it. The thing is, dropbear won't start the second time. So don't do this if you're doing this headless.

    It also seems that Desktop versions of Ubuntu also have splash argument in cmdline.txt, which should be removed, as the splash screen will not at all aid you in this endeavor.

  5. Now you can finally reboot:

    reboot
    

Encrypting rootfs in-place from initramfs

  1. Your Pi should now be booted into initramfs with an IP address acquired from your router via DHCP. If you do not know what this IP address is, log into your router to check. After that, it's a matter of using SSH to log into initramfs:

    ssh -i ~/.ssh/id_rsa root@<Pi's IP address>
    

    (If your SSH private key is stored in some other location, be sure to adjust the command)

  2. Once in initramfs, mount the system-boot partition:

    mkdir -p /mnt/boot
    mount /dev/mmcblk0p1 /mnt/boot
    
  3. Check and repair rootfs with e2fsck (this step is required):

    e2fsck -f /dev/mmcblk0p2
    
  4. Shrink rootfs to make space for LUKS header:

    resize2fs -M /dev/mmcblk0p2
    
  5. Encrypt rootfs in-place by using cryptsetup reencrypt (this will take some time, depending on the size of the SD card and its performance class, for me it took 40 minutes for 64 GB):

    cryptsetup reencrypt --new --reduce-device-size=16M --type=luks2 -c aes-xts-plain64 -s 256 -h sha256 --use-urandom --key-file /mnt/boot/<key file name>.lek /dev/mmcblk0p2
    
  6. Once the encryption is finished, unlock the encrypted rootfs:

    cryptsetup luksOpen --key-file /mnt/boot/<key file name>.lek /dev/mmcblk0p2 rootfs
    
  7. Expand the rootfs to fill the entire partition:

    resize2fs /dev/mapper/rootfs
    

    At this point, you can try and mount the rootfs to see if everything is working correctly:

    mkdir -p /mnt/root
    mount /dev/mapper/rootfs /mnt/root
    cd /mnt/root
    ls -la
    

    Retrace your steps if the output of any of these commands isn't right.

  8. At this point, despite /dev/mapper/rootfs existing, the Pi will make no attempt at switching to it. To do that, first reboot the Pi:

    reboot -f
    

    Then, without waiting too long, connect to it as you did in step 1 of this section, and unlock rootfs again:

    mkdir -p /mnt/boot
    mount /dev/mmcblk0p1 /mnt/boot
    cryptsetup luksOpen --key-file /mnt/boot/<key file name>.lek /dev/mmcblk0p2 rootfs
    

    You should get something along the lines of

    Connection to <IP> closed by remote host.
    

    in 2-3 seconds after unlocking rootfs. If it doesn't work, try again, but quicker. After this, the Pi will boot into rootfs.

Restoring normal boot

  1. Since Debian's implementation of crypttab doesn't seem to support reading key files directly from devices, you can use a 'keyscript' to do this for you. Create /lib/cryptsetup/scripts/unlock with the following content:

    #!/bin/sh
    set -e
    if [ ! -e /sdboot ]; then
      mkdir -p /sdboot
      sleep 3
    fi
    if mount /dev/mmcblk0p1 /sdboot >/dev/null; then
      if [ -e /sdboot/$CRYPTTAB_KEY.lek ]; then
        cat /sdboot/$CRYPTTAB_KEY.lek
        umount /sdboot
        exit
      fi
      umount /sdboot
    fi
    /lib/cryptsetup/askpass "Insert key or enter passphrase: "
    

    This script will try and mount the system-boot partition of the SD card to /sdboot and use the key file name, which will be passed to it by crypttab, to find the correct key on the system-boot partition. Then, it will dump the contents of the file onto stdout, which will be interpreted as the contents of the key file. The /sdboot mount point will only exist within initramfs and won't be a part of rootfs.

    Don't forget to make it executable:

    chmod a+x /lib/cryptsetup/scripts/unlock
    
  2. Add an entry to /etc/crypttab to unlock rootfs automatically at boot time:

    rootfs  /dev/mmcblk0p2  <key file name (without the .lek extension)>    luks,initramfs,keyscript=unlock
    
  3. Update initramfs to apply changes:

    update-initramfs -u
    
  4. Reboot to test:

    reboot
    

At this point, you can modify the setup to your liking. For example, you might want to move the key file onto a USB drive and modify the script accordingly. I have a GitHub Gist with detailed instructions on how to do exactly this. Or you can switch to password-based unlock with the key as a backup. The point is, you now have a working system which boots and with which you can do anything that you wish.

My recommendation would be to change the encryption key if you switch to USB-drive-based unlocking (as the one written to the SD card could still be read if erased improperly), and to use device UUIDs (you can view them wuth blkid) instead of /dev/<whatever>-style block device specification, as UUIDs are not system-dependent.