When it comes to block device encryption on Linux, dm-crypt in combination with a LUKS header is the de facto standard.

While the setup procedure is well documented in the arch wiki, I couldn't find a straightforward explanation on how to move an existing installation to an encrypted partition. Here is what I did to save me the trouble of reinstalling and setting up my system.

Threat model

Let's be clear, disk encryption is not a silver bullet against every possible kind of attack. In particular, as the arch wiki mentions, you will still be vulnerable to:

  • Attackers who can break into your system after you have already unlocked the encrypted parts of the disk.

  • Attackers who are able to gain physical access to the computer while it is running, or very shortly after it was running, if they have the resources to perform a cold boot attack

  • Rubber-hose cryptanalysis. Also, see XKCD #538

My thread-model though is quite simple in this case: I will be using this drive in a notebook and carrying it around a lot. I might lose it or have it stolen by random thieves. If they are able to perform any of the attacks above, I'm sure they will also be able to force me to give them the key, so I won't bother considering those. The setup above should be good enough, that I will be quietly weeping over my trusty X230 instead of the private data on the drive, if forget it on the train.

Prerequisites

You will need a few basics. I moved the installation to a new SSD of the same size and used a SATA to USB adapter to connect them both at once.

  • The new drive
  • SATA-USB adapter
  • Bootable live media, I used the arch install USB
  • A working installation

I'll also assume you have a backup of your data, since you could easily lose it if something goes wrong (you should have those anyway).

Create the partitions

My setup has only two partitions: a small EFI partition (/boot) and the root file system (/). You can get this information e.g. form fdisk, using p to print the partitions.

Disk /dev/sda: 223.58 GiB, 240057409536 bytes, 468862128 sectors
Disk model: KINGSTON SA400S3
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: gpt
Disk identifier: BF841FB1-0634-4826-A240-2D70832E3D5C

Device Start End Sectors Size Type
/dev/sda1 2048 1128447 1126400 550M EFI System
/dev/sda2 1128448 468860927 467732480 223G Linux filesystem

There are valid reasons to encrypt the /boot, but it makes the setup more complicated and not necessary for what I want to accomplish. My goah is to encrypt /, which especially also contains my /home folder with private data. The bootloader will remain installed in the unencrypted /boot partition and unlock the encrypted partition on boot.

Armed with this information you can plug in the drive using the adapter and recreate the partition table like you would normally. I won't go into detail on how to create the partitions, as there is nothing special it, the encryption step comes later. I used gparted, also don't forget to set the boot and esp flags for the boot partition and set the correct filesystem types.

Setup encryption

At this point you can boot into your previously created live media. I'll assume in the following, that the existing (unencrypted) installation is located on your internal drive /dev/sda and the new, to be encrypted drive is located at /dev/sdb. This is the right moment to create the filesystem on your new /boot drive.

mkfs.fat -F32 /dev/sdb1

While the /boot partition (/dev/sdb1) requires nothing else for now, we now need to set up the encryption on the new root partition at /dev/sdb2.

Using cryptsetup we initialize the LUKS partition and set the initial passphrase on key-slot 0. LUKS allows for multiple key-slots, which may be used for other forms of authentication, e.g. a hardware security token. The following initializes the LUKS partition, unlocks it and creates an ext4 filesystem inside.

cryptsetup -y -v luksFormat /dev/sdb2
cryptsetup open /dev/sdb2 cryptroot
mkfs.ext4 /dev/mapper/cryptroot

Before continuing it might be a good idea to verify that everything went well and the LUKS partition works as expected. You should be able to close and re-open it, we will try mounting it in the next step too.

cryptsetup close cryptroot
cryptsetup open /dev/sda2 cryptroot

Copy the data

We will now mount everything and copy over the data. Assuming you are booted into a live media and have all necessary privileges create two folders: one for the "old" drive (/dev/sda) and one for the "new" one (/dev/sdb). It wont matter where you create them as they are ephemeral. For simplicity, I created two folders in the root home directory.

mkdir /root/old_rootfs
mkdir /root/new_rootfs

You can mount the existing installation to /root/old_rootfs. This also includes mounting the /boot partition located on /dev/sda inside the mounted root filesystem of the old drive.

mount /dev/sda2 /root/old_roofts
mount /dev/sda1 /root/old_rootfs/boot

You can mount the new, encrypted, drive in the same manner, the only difference being that you will mount /dev/mapper/cryptroot instead of mounting /dev/sdb2 directly.

cryptsetup open /dev/sdb2 cryptroot # not necessary if you didn't close it
mount /dev/mapper/cryptroot /root/new_rootfs
mount /dev/sdb1 /root/new_rootfs/boot

While you could have copied the partitions one after the other, with the setup above you now only have to copy everything from /root/old_rootfs to /root/new_rootfs, taking care to preserve file attributes.

The rsync command should be available or can be easily installed on most platforms and will do the job nicely. Adding the --info=progress2 flag, gives an estimate of the total progress instead of the single files being copied. Watch out for the slashes in the command though, as rsync will copy the folder itself instead of the contents if you omit them.

rsync -aAXv --info=progress2 /root/old_rootfs/ /root/new_rootfs/

Depending on the amount of used storage this might take a while. Let it run until it finishes. When it's finished, /root/new_rootfs should list the same contents as /root/old_rootfs.

At this point, you can unmount the old drive to prevent any mistakes that might mess it up. This is not strictly necessary, but gives you an additional backup in case you want to revert to it or start over. I even went one step further, taking the old drive out physically and rebooting into the live system with only the new drive now build in. If you do so, you will need to mount everything again, also watch out for changed device descriptors.

umount /root/old_rootfs/boot
umount /root/old_rootfs

Congrats, your data is now on an encrypted partition! There are a few more steps to make it boot though.

Chroot into new system

The following steps need to be done from inside the new system. It is not bootable yet, but we can chroot (or arch-chroot on Linux arch) into it. With both /dev/sdb1 and /dev/sdb2 still mounted, use arch-chroot.

arch-chroot /root/new_rootfs

Execute the rest of these instructions in the chroot.

Configure fstab

Depending on your setup, you might need to update your /etc/fstab. Using lsblk -f you should see something similar to this:

$ lsblk -f
NAME FSTYPE UUID FSAVAIL FSUSE% MOUNTPOINT
sda
├─sda1 vfat A319-9DF9 476.4M 13% /boot
└─sda2 crypto_LUKS 424372e4-ac1d-4925-83ba-baa6d5ec6117
  └─cryptroot ext4 9b2863ce-ed0b-41de-a96e-b02a02f3bada 81.9G 57% /

Use the shown UUIDs to write your /etc/fstab. Warning: Don't confuse the UUID of your root partition itself (e.g. /dev/sda2) with the UUID of the encrypted volume! In my case my /etc/fstab file looks like this:

UUID=9b2863ce-ed0b-41de-a96e-b02a02f3bada / ext4 rw,relatime,stripe=8191 0 1
UUID=A319-9DF9 /boot vfat nosuid,nodev,noexec,noatime,fmask=0133 0 0

Once again: Use the UUID of cryptroot for /, not the one from /dev/sda2!

Configure mkinitcpio

It is now time to configure the kernel to work with the encryption. For this we need to add some hooks to /etc/mkinitcpio.conf. There are different hooks that can be used here, I will be using the default encrypt hook. For more detailed information the arch wiki is again a great reference.

In particular, we need to add the keyboard, keymap and finally the encrypt hook to HOOKS=(..). You can leave out the keymap hook, if you are you use the default US keymap. In my case the HOOKS=(...) in /etc/mkinitcpio.conf look like this:

HOOKS=(base udev autodetect keyboard keymap modconf block encrypt filesystems keyboard fsck)

After saving the file, regenerate the initramfs with all presets

mkinitcpio -P #That is a captial -P!

Config bootloader

The last step is to configure the bootloader. We need to pass this kernel parameter:

cryptdevice=UUID=device-UUID:cryptroot root=/dev/mapper/cryptroot

Note: The device-UUID is here UUID of /dev/sdb2, not the one from the encrypted volume like before.

For grub this means editing /etc/default/grub like this:

GRUB_DISTRIBUTOR="Arch"
GRUB_CMDLINE_LINUX_DEFAULT="quiet cryptdevice=UUID=424372e4-ac1d-4925-83ba-baa6d5ec6117:cryptroot root=/dev/mapper/cryptroot"
GRUB_CMDLINE_LINUX=

With the file in place, regenerate the grub.cfg file with:

grub-mkconfig -o /boot/grub/grub.cfg

Reboot

That's it, you're done! If everything went well, you can now reboot into the system. Before doing so, leave the chroot, then unmount the partitions for good measure and throw a sync in there to be sure everything is written.

umount /root/new_rootfs/boot
umount /root/new_rootfs
sync
reboot

At this point your system should reboot into grub and then ask you for a passphrase along the boot process. If you experience problems, it might help to reinstall the bootloader.

I later added my Yubikey to the setup, allowing it to be used to unlock the encrypted volume. I will be detailing how that was done in a separate post.