filesystem surgery
intro
Encrypting sensitive data on your computer is pretty important, especially if it's a laptop (and therefore easier to steal than a desktop), and doubly so if you have anything extra sensitive like legal documents or work data on it.
On my laptop, which is my daily driver, I used to use an XFS home partition with ecryptfs on top of it, but there were a couple deficiencies in this setup that ultimately led me to abandon it:
- There is a filename size limitation.
- Using xfsdump and xfsrestore is unreliable (again due to filename length issues -- this actually is what led to the creation of kyanite).
- There is a concern of running out of kernel stack. The ecryptfs docs warn that using it with XFS may cause this, but I figured since that particular page was pretty old, I'd see if it was still an issue. In practice, I only ran into it while unwinding the decryption during a shutdown after filling the partition enough.
- I still needed to encrypt my swap space -- it can contain all manner of sensitive keys or application data.
So, in March, after a few kernel panics on shutdown related to the third of the above bullet points, I decided to favor stability over security temporarily and setup a vanilla unencrypted home directory (this was pretty painless thanks to kyanite and my cold backup drive) while I weighed my options. Fast forward to late July, and my laptop still wasn't stolen but I wanted to protect the data therein, so I devised a new scheme:
- Use ZFS with its own native encryption for my home partition.
- Use LUKS encryption for my swap partition.
- Use a LUKS encrypted partition on my keychain USB drive to store the keyfiles for both, and decrypt it with a password on boot.
- Also, switch from a LUKS encrypted XFS partition for my cold backups to, again, ZFS with native encryption.
The rationale behind this is that for my backups and my home directory, if I have some kind of hardware failure on my main device and need to access my data, I can do it from any operating system since OpenZFS is available on every major platform (XFS has been Linux-only for a good while). The other advantage of ZFS over XFS is that it has integrity checksumming and automatic repair via scrubbing. I would have used a ZFS ZVol for my swap, but it's apparently not a stable setup so I went with the LUKS encrypted swap (which doesn't need to be portable anyways). One could gain additional security in terms of the operating system itself being immune to snooping and manipulation with full-disk encryption, but the opportunities for error are a bit more numerous on that path, as well as the necessary time to complete the system migration, so this middle-ground works for me.
moving bits
I run Void Linux on my machines so your mileage may vary, but here goes.
First step is to install the zfs
package, which includes the utilties and the dynamic kernel module:
# # Optionally, you can purge your old kernels, since building the kernel module can take a while # vkpurge rm all # xbps-install -S zfs
After rebooting and verifying that ZFS is working (zpool status
), I made sure all my data was sound so I could start fresh on my backups. After meticulously verifying that my live data was stable and complete, I wiped my backup drive and started fresh:
# zpool create -f -o feature@encryption=enabled \ -O compression=on -O encryption=on -O keyformat=passphrase -O keylocation=prompt \ -m /media/cryonix cryonix /dev/sdb
I was prompted to enter the password, and it automounted upon creation. So far so good. Next, I used kyanite to make a new generation of backups (I also backup my server and media drive, but here is just my home directory):
# kyanite full /home/nilix/ /media/cryonix/ --exclude .cache
After the backup completed, next was to setup the USB key. After doing a zpool export cryonix
(I only have one USB3 port on my laptop, oh well), I ran cfdisk
and added a 1GB partition to the tail end of my keychain USB drive. It's a 128GB drive and I previously only had around half of it allocated as a FAT32 partition, so that was pretty painless. Next, to create the LUKS partition to hold the keyfiles for the other encrypted filesystems:
# cryptsetup luksFormat /dev/sdb3
Again, it asks for a decryption passphrase. Once confirmed, you can create the filesystem and mount it:
# cryptsetup luksOpen /dev/sdb3 enclave # mkfs.vfat /dev/mapper/enclave # mount /dev/mapper/enclave /mnt
Ok, next is to generate the keyfiles for the other filesystems:
# head -c 32 /dev/urandom > /mnt/zhomekey # head -c 32 /dev/urandom > /mnt/swapkey
Now comes the fun part. I already had the partitions created for home and swap, so after rebooting into single-user mode, unmounting /home
and doing swapoff -a
, I created new filesystems:
# zpool create -f -o feature@encryption=enabled \ -O encryption=on -O keyformat=raw -O keylocation=file:///mnt/zhomekey \ -m /home zhome /dev/sda4 # cryptsetup luksFormat /dev/sda3 # cryptsetup luksAddKey /dev/sda3 /mnt/swapkey
In the above commands, the first cryptsetup
line creates the LUKS swap device with a backup passphrase, and the second one adds the keyfile we made earlier.
At this point, you can restore your home partition like so (assuming your backup drive is connected/mounted/imported):
# mkdir /home/nilix # chmod 0744 /home/nilix # chown -R nilix:nilix /home/nilix # kyanite restore /media/cryonix/ksatrya/{timestamp}_full/ /home/nilix/
finishing touches
To bring it all together, we need to edit /etc/crypttab
and /etc/fstab
as well as (in my case, at least) do some platform-specific mojo. First is /etc/crypttab
. We have to get the USB key to ask for the passphrase, decrypt, and automount. Do a ls -la /dev/disk/by-uuid/
to see what UUID corresponds to the encrypted partition on the USB drive, and then edit /etc/crypttab
. I added a line that looks like this:
enclave /dev/disk/by-uuid/037a3483-4f3f-4345-b306-046e908bce4b none
Now in /etc/fstab
I commented out the lines that corresponded to my old home and swap partitions and added this one (after the /boot/efi entry and before the tmpfs entry):
/dev/mapper/enclave /mnt vfat defaults 0 2
And last but not least, I added a custom script to the runit core services at /etc/runit/core-services/03-filesystems_custom.sh
(to ensure it runs right after the fstab
is processed):
#!/bin/sh cryptsetup luksOpen --key-file=/mnt/swapkey /dev/sda3 cryptswap swapon /dev/mapper/cryptswap zfs mount -l zhome umount /mnt cryptsetup luksClose enclave
This decrypts and activates the swap partition, decrypts and mounts the ZFS root dataset, and then unmounts and closes the encrytped USB partition. On other Linux distributions you would probably create a systemd unit file to replicate this part. There are also hooks into PAM for decrypting ZFS filesystems but the documentation is (at the time of writing) ethereal, so I went with this method.
reboot!
So, if all went well, rebooting with your USB key plugged into the machine, you will see it ask for the passphrase to unlock it, and after that it should proceed as normal (taking an extra second or two to decrypt the partitions). If you happen to boot the machine without the USB key present, in my setup it drops you into an emergency shell after trying to mount the nonexistent /dev/mapper/enclave
.
So there it is. A caveat to the setup as I have it is that hibernation to disk doesn't seem to work, but the only time I ever used it was inadvertently when the machine would automatically hibernate if the battery was about to run out. Not a dealbreaker for me. Now I can rest easy that all my sensitive documents and work data is encrypted at rest, and even if a bad actor were to snatch my messenger bag at the coffee shop or airport (not that I'm out much these days!), they wouldn't be able to do a damn thing with it!