Raspberry PI 4 with encrypted root partition, LVM and remote unlock

This how-to describes the necessary steps to set up a Raspberry PI 4 with a LUKS encrypted root partition, LVM and remote unlock via Dropbear SSH server in initramfs.

I’ve compiled this from various other tutorials, most notably Secure Kali PI 2018 and Raspberry Pi Encrypt Root Patition. The main differences are:

  • Device UUIDs instead of paths in fstab, crypttab and kernel command line.
  • LVM inside the encrypted partition.
  • cryptroot-unlock as Dropbear command.
  • No modification of /usr/share/initramfs-tools/scripts/init-premount/dropbear.

Note: because of a problem when creating the initramfs for the first time and the encrypted root partition is no yet fully set up, a screen and keyboard need to be connected to the Raspberry PI for one step.

Prepare storage device

The following steps are executed on another computer, rather the Raspberry PI.

Download the image from the Raspberry PI website.

Connect your storage device (either a SD card or a USB storage device) and run lsblk to determine the device path. Be absolutely sure about this because you might destroy your system when picking the wrong one. I’ll write /dev/sdX for the rest of this how-to.

Copy the downloaded image onto the storage device:

dd if=raspios.img of=/dev/sdX bs=4M status=progress

Run fdisk to get some information about the storage device:

fdisk -l /dev/sdXDisk /dev/sdX: 1.73 GiB, 1858076672 bytes, 3629056 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x067e19d7
Device Boot Start End Sectors Size Id Type
/dev/sdX1 8192 532479 524288 256M c W95 FAT32 (LBA)
/dev/sdX2 532480 3629055 3096576 1.5G 83 Linux

The important values are the sector size (512) and the total number of sectors (3629056).

The goal is to have a partition of 2 GB at the end of the device for a temporary unencrypted root and use all the space in between for the encrypted LVM partition.

Calculate the first sector of the new partition 2 GB from the end:

$NUMBER_OF_SECTORS - 2 * 1024^3 / $SECTOR_SIZE

Run fdisk again and create the partition:

fdisk /dev/sdXn        # create new partition
p # primary partition
<enter> # use default partition number
XXX # the calculated start sector from above
<enter> # use suggested end sector
w # write

Now copy the data from partition 2 to partition 3 and resize it to use the full 2 GB:

dd if=/dev/sdX2 of=/dev/sdX3 bs=4M status=progressresize2fs /dev/sdX3

Run fdisk again and recreate the second partition.

fdisk /dev/sdXp        # note start sector of partition 2
d # delete partition
2 # select partition 2
n # create new partition
p # primary partition
2 # partition number 2
XXX # start sector from original partition 2
<enter> # use suggested end sector
w # write

Run blkid /dev/sdX3 and copy the UUID.

Mount the boot partition, edit cmdline.txt and replace the value of the root parameter with the UUID from above. E. g. replace root=PARTUUID=XXXX-2 with root=UUID=XXX-XXX.

mkdir -p /mnt/rpi/bootmount /dev/sdX1 /mnt/rpi/bootvim /mnt/rpi/boot/cmdline.txt

If you want SSH to be enabled right from the start, create a file named ssh in the boot directory:

touch /mnt/rpi/boot/ssh

Unmount and clean up

umount /dev/sdX1
rm -r /mnt/rpi

Create the encrypted volume. I’ll use the cipher aes-xts with a key size of 256 and sha256 for the best performance on the Raspberry PI. Once it becomes available in the default Raspberry PI OS, aes-adiantum might be an interesting choice.

cryptsetup luksFormat \
--type=luks2 \
--pbkdf=pbkdf2 \
--cipher=aes-xts-plain64 \
--key-size=256 \
--hash=sha256 \
/dev/sdX2

Open the encrypted volume and setup LVM. I’ll use 50G for the root file system. Adapt this to your needs and the size of your storage medium.

cryptsetup open /dev/sdX2 rpipvcreate /dev/mapper/rpivgcreate vgrpi /dev/mapper/rpilvcreate -L 50G vgrpi -n rootmkfs.ext4 /dev/mapper/vgrpi-rootlvchange -an vgrpicryptsetup close /dev/mapper/rpi

Raspberry PI configuration

Now connect your storage device with the Raspberry PI and power up.

Update and install the required software

apt-get update
apt-get install \
dropbear \
lvm2 \
cryptsetup \
busybox \
dropbear-initramfs \
cryptsetup-initramfs

Open the encrypted volume

cryptsetup open /dev/sdX2 crypt

Run blkid and note the UUID of /dev/sdX2 and /dev/mapper/vgrpi-root. They will be required in the next steps.

Edit /etc/fstab and replace the file system for / with the UUID of /dev/mapper/vgrpi-root. It should look like this:

proc           /proc  proc  defaults          0 0
UUID=XXXXXX /boot vfat defaults 0 2
UUID=XXXXXX / ext4 defaults,noatime 0 1

Edit /etc/crypttab and add an entry with the UUID of /dev/sdX2. Make sure to use tabs as separator between the values.

crypt	UUID=XXXXXX	none	luks

Open /boot/cmdline.txt in an editor. Change the value of the root parameter to the UUID of /dev/mapper/vgrpi-root and add a new parameter cryptdevice with the UUID from /dev/sdX2 followed by :crypt. Append rootdelay=2 at the very end.

[...] root=UUID=XXXXXX cryptdevice=UUID=XXXXXX:crypt [...] rootdelay=2

Uncomment and change the following line in /etc/cryptsetup-initramfs/conf-hook:

CRYPTSETUP=y

Create /etc/dropbear-initramfs/authorized_keys and insert your SSH public key (usually from ~/.ssh/id_rsa.pub on your other machine).

Uncomment and edit the following line in /etc/dropbear-initramfs/config:

DROPBEAR_OPTIONS="-sgjkc cryptroot-unlock"

The options here mean:
-s Disable password logins
-g Disable password logins for root
-j Disable local port forwarding
-k Disable remote port forwarding
-c Force command to be executed

Add the following line at the end of /boot/config.txt:

initramfs initramfs.gz followkernel

Create the initramfs. This might show some errors from cryptsetup you can ignore for the moment.

mkinitramfs -o /boot/initramfs.gz

Copy root file system to encrypted volume

Power off your Raspberry PI and connect the storage device to your other computer again.

Mount the unencrypted temporary root partition, the encrypted root partition and copy the data.

mkdir -p /mnt/rpi/{default,crypt}mount /dev/sdb3 /mnt/rpi/defaultcryptsetup open /dev/sdb2 rpimount /dev/mapper/vgrpi-root /mnt/rpi/cryptrsync -avh /mnt/rpi/default/* /mnt/rpi/crypt/umount /mnt/rpi/cryptumount /mnt/rpi/defaultlvchange -an vgrpicryptsetup close /dev/mapper/rpirm -r /mnt/rpi

Boot Raspberry PI from encrypted volume

The first boot from the encrypted volume will probably fail, so you need to connect a screen and keyboard to fix it manually. I’m happy for any hints how to solve this in a more elegant way.

Wait until the errors stop and you are dropped into the initramfs shell
Enter the following commands and hit CTRL-D afterwards:

cryptsetup open /dev/sdX2 crypt
vgchange -ay

Now the boot process should continue and you can login.

As a final step, recreate the initramfs to fix the errors from above. It should now execute without any errors.

mkinitramfs -o /boot/initramfs.gz

Now that everything is set up, you can erase the temporary root file system on partition 3 or keep it as a recovery system.

Please note that you have to recreate the initramfs after every kernel upgrade!