I had a setup where my removable device contained a single partition with encrypted data and nothing else on it to distinguish it from random data. On a physically separate device, I had a pair of scripts that opened/closed the device using a GPG-encrypted key:
open.sh
#!/bin/bash
# The crypt UUID is hard-coded. That way it will not try to decrypt the wrong
# device.
cryptuuid='xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
# The UUID of the decrypted device is hard-coded. This is currently how the
# script knows what to mount after decrypting.
deviceuuid='yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy'
# The encrypted key bits are fed to cryptsetup with specific parameters to
# decrypt the crypt.
gpg2 --decrypt <<- END | sudo cryptsetup --offset 0 --cipher aes-xts-plain64 --key-size 256 --hash sha256 --key-file - open --type plain "/dev/disk/by-partuuid/$cryptuuid" Secrets
-----BEGIN PGP MESSAGE-----
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
zzzzz
-----END PGP MESSAGE-----
END
# A small delay is necessary to give time for the decrypted device to show up.
sleep 1
# Mount the decrypted device as if the user did it using the GUI.
gvfs-mount -d $(readlink -f $(blkid -U "$deviceuuid"))
close.sh
#!/bin/bash
# The UUID of the decrypted device is hard-coded. This is currently how the
# script knows what unmount.
deviceuuid='yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy'
# Find all processes using the decrypted device and issue a “friendly” kill
# signal.
sudo lsof -t $(findmnt -rn -S UUID="$deviceuuid" -o TARGET) | while read process; do kill -HUP $process; done
# Try to unmount the decrypted device and then close it with cryptsetup. findmnt
# identifies the path which the device is mounted. lsblk finds the decrypted
# device (minus “/dev/” from the UUID). dmsetup finds the decrypted device name
# given the block device name from before.
gvfs-mount -u $(findmnt -rn -S UUID="$deviceuuid" -o TARGET) && sudo cryptsetup close $(sudo dmsetup info --columns --noheadings --options Name --select BlkDevName=$(lsblk --raw --noheadings --output KNAME,UUID | awk '{if ($2=="'"$deviceuuid"'") { print $1}}'))
The scripts worked, but ideally, I would like to leverage as much of the built-in GUI support for encrypted partitions as possible, lessening the use of the terminal.
In my attempt to move closer to this goal, I took a look at the Logical Volume Manager (LVM) for its JBOD capability. The idea was to have a LUKS partition that would show up in the file manager, but to also keep the LUKS header away from the encrypted data, leaving the encrypted data indistinguishable from random data.
To accomplish this, I employed 2 volumes:
- A file header.img (exactly 2,139,648 bytes)
- A physical device /dev/sdx
header.img contains a GPT partition, which hosts a LVM PV just big enough to contain the entire LUKS header. /dev/sdx contains a GPT partition which hosts a LVM PV containing the encrypted data.
They are created using these steps:
fallocate -l 2139648 header.imgcreates an image to hold the first PV.- I mount the image and create a 4,112-sector GPT partition (GUID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) within it for the PV.
sudo pvcreate --labelsector 0 --metadatatype 2 --pvmetadatacopies 1 --metadatasize 32768b --dataalignment 512b /dev/disk/by-partuuid/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxcreates the header’s physical volume.- For the device (like /dev/sdx) where I want to store the encrypted data, I determine the partition alignment that I need and add one more sector at the beginning for the LVM header. If a SSD drive comes preformatted with a partition beginning at sector 65536, delete that partition and create one beginning at sector 65535. That way, the file system will still be optimally aligned.
sudo pvcreate --labelsector 0 --pvmetadatacopies 0 --metadatasize 0b --dataalignment 512b '/dev/disk/by-partuuid/yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy'creates a physical volume with just the LVM header but no metadata.vgcreate --metadatacopies unmanaged --physicalextentsize 512b Secrets '/dev/disk/by-partuuid/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' '/dev/disk/by-partuuid/yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy'creates a volume group on top of both of those physical volumes.lvcreate --contiguous n --extents 100%FREE Secrets --name Cryptcreates a single logical volume that occupies all of the free space in the volume group.- Now a peculiar problem I ran into was that if I wrote any data into the logical volume, the largest physical volume would always be written to first. That’s bad, because I want the LUKS header to be written to the smaller of the 2 physical volumes. I fix it by exporting the volume group’s data to a text file where I can reorder the physical volumes:
vgcfgbackup -f metadata.txt Secrets. - Editing the resulting text file is straightforward. Under the
logical_volumessection, there should be 2 segments named thusly:segment1andsegment2. You will need to edit thestart_extentandextent_countvalues under each of them.segment1must havestart_extent = 0andextent_count = 4040. Andsegment2must havestart_extent = 4040andextent_countequal to the larger physical volume’spe_count—which you can find under thephysical_volumessection. You may need to swappv0andpv1around, but double-check the volume definitions to make sure they’re sane. vgcfgrestore -f metadata.txt Secretswill apply your changes to the volume group.cryptsetup --verbose --cipher aes-xts-plain64 --key-size 512 --hash sha512 --align-payload 1 luksFormat /dev/Secrets/Cryptcreates the LUKS encrypted volume.- Open the new encrypted volume and do what needs to be done.
- To eject: unmount any file system on the encrypted volume, close it, and run
vgchange -an Secrets. You can then detach and/or eject the physical volumes.
When /dev/sdx is inserted and header.img is mounted using the Disk Utility, an icon shows up on the Launcher which—when clicked—prompts for a password to unlock the volume; the terminal is not needed in any of the steps except on the random occasion that the logical volume doesn’t register. Then running vgscan --mknodes and udevadm trigger seems to fix the hiccup.
The problem is that when unmounting the volume, I have to also use the terminal to deactivate the LVM volume group (VG) before I remove the loop device and eject the encrypted storage. Physically removing the storage medium does not seem to make the volume group go away and loop devices are prevented from being dismounted.
I would be satisfied with just solving the device removal problem, but I think I’m over-complicating things just by creating and maintaining such a setup.
Does any Linux distribution support easy-to-use, multi-factor full disk encryption today?
