One of the use cases I was hoping the Zymkey could support was the ability to securely mount an encrypted external drive automatically at boot. To my surprise, there was no tutorial/examples provided for this use case, and the forum had a single post stating that they had found a solution, but hadn’t outlined what that was.
For those of you who haven’t been able to figure it out, and have been scratching your heads like me. Here’s my solution to the problem. I’m not saying it’s perfect, or the best way of going about it, but it does work for me!
Step 1 – Get the device address
sudo fdisk -l after plugging in your external hard drive. Take careful note of the drive’s partition address (for example, /dev/sda1, or /dev/sdb1). You want to make absolutely sure you’re referencing the right drive and partition here! The fdisk command should provide additional information about the manufacturer and the partition size to help you determine what is what! Failure to note the correct device address can result in you destroying data on other disks, so be very careful to ensure it’s the correct address!
Step 2 – Create a backup of the external drive
We’re going to be encrypting the drive. It’s always wise to take a backup beforehand! This can either be a full sector by sector disk image backup, or a file system backup. It really depends on what you’re using the external drive for, as to which will suit your needs. If you’re just storing files on your external drive, a file system backup is probably sufficient. Note that if the drive is already encrypted, you can still continue with the process without having to decrypt the drive first (the encryption will remain as is), but I’d still take a backup first. Clearly, for a filesystem backup on an encrypted drive, the drive will need to be unlocked and mounted accordingly!
Step 3 – Create the following shell script
You can call the script what you like, but I’ve chosen zkyExtSetup.sh (it’s convention to use the .sh file extension for shell scripts if you choose a different name).
For those of you who don’t know how to create a shell script, if you haven’t already, install the nano text editor (
sudo apt install nano). Now type
&& sudo nano zkyExtSetup.sh You then just need to copy and paste the code below (updating <<EXT DEVICE>> with the device address from step 1, and <<MOUNT NAME>> with the folder name of your choice for the drive mount. Once you’ve done this, you can save the file by clicking
ctrl-X and then
Y on the keyboard.
The script itself does several things – first it generates a new encryption key for the drive using the Zymkey’s sudo random byte generator, it then encrypts/locks this key (again with the Zymkey), using the lock function of the Zymbit Python API. It then goes on to check if the external hard drive is already encrypted.
If you choose to use an already encrypted drive, it asks you for the passphrase which is used to add the Zymkey generated key to the LUKS header of the drive (you can have several keys associated with a LUKS encrypted drive). Doing things this way means you will always have the ability to unlock the drive, both with your original passphrase, and the new Zymkey key (providing some resilience in the face of Zymkey hardware failure).
If however, you choose to use an unencrypted drive, the script goes on to warn you that this will completely erase the disk! There is no turning back from this point, so make sure you don’t mind the disk being erased! The script will ask if you wish to add a separate password for unlocking the drive (in addition to the Zymkey key). This will help protect against hardware failure of the Zymkey device. However, please note, that any passphrase is likely to be weaker than an encryption key, and you should use a strong passphrase. Of course, I’d always recommend you ensure the password you’re using is unique to the device in question, and not one that you’ve widely used elsewhere. You may also wish to test that your password isn’t appearing within any public hacked password databases to give you peace of mind.
Finally the script sets up a service to automatically unlock the the drive at boot. It does this by using the Zymkey to unlock the encrypted drive key at boot. The unencrypted key is stored in the root partition temporarily and once it’s mounted the drive, it deletes the unencrypted key. I may update the script to store the key in memory rather than on the root drive, but in my case the root drive is also encrypted, so it doesn’t matter if the key is stored on the root drive (especially given that the script goes on to delete the unencrypted key once the drive has successfully mounted).
#!/bin/bash # Ensure running as root or exit if [ "$(id -u)" != "0" ]; then echo "run this as root or use sudo" 2>&1 && exit 1 fi EXT_DRIVE="<<EXT DRIVE>>" DISK_MOUNT_NAME="<<MOUNT NAME>>" KEY_NAME="extHddKey" echo "Creating required directories" mkdir -p /media/$DISK_MOUNT_NAME mkdir -p /etc/luks echo #new line echo "Creating generate key script" cat > ~/generateExtHddKey.py <<EOF import os,zymkey from zymkey.exceptions import VerificationError try: # create a pseudorandom 512 byte (4096 bit) key from zymkey's random byte # generator numBytes = 512 extHddKey = zymkey.client.get_random(numBytes) # encrypt and sign new key using zymkey lockedExtHddKey = zymkey.client.lock(extHddKey) # write unlocked external hard drive key to same location as the zymbit root hdd # key (the key used to unlock the root partition) f = open("/etc/luks/$KEY_NAME.bin", "wb") f.write(extHddKey) f.close() os.chmod("/etc/luks/$KEY_NAME.bin", 0o600) # write locked (encrypted and signed) external hard drive key to same location # as the zymbit root hdd key (the key used to unlock the root partition) f = open("/etc/luks/$KEY_NAME.bin.lock", "wb") f.write(lockedExtHddKey) f.close() os.chmod("/etc/luks/$KEY_NAME.bin.lock", 0o600) except VerificationError as e: print(e) EOF # Create unlock script cat > /etc/luks/unlockExtHddKey.py <<EOF import time,base64,zymkey # Open the generated locked external hard drive key for the zymbit to unlock f = open("/etc/luks/$KEY_NAME.bin.lock", "rb") # Output the decrypted key in base64 format print(base64.b64encode(zymkey.client.unlock(f.read())).decode("ascii")) f.close() EOF echo #new line echo "Generating and locking encryption key using zymkey device" python3 ~/generateExtHddKey.py rm ~/generateExtHddKey.py echo #new line read -p "Are you wishing to add a Zymkey key to an already encrypted LUKS drive without erasing its content? (Y/N)" confirm echo #new line if [[ $confirm == [yY] || $confirm == [yY][eE][sS] ]]; then echo #new line read -s -p "Please enter the passphrase for the existing encrypted LUKS drive (this is required to add the additional Zymkey key):" passphrase echo #new line if [[ $passphrase ]]; then echo -n $passphrase | cryptsetup -q -v luksAddKey $EXT_DRIVE /etc/luks/$KEY_NAME.bin >/dev/null echo #new line echo "Zymkey key added to existing drive" else echo #new line echo "No passphrase was provided, script terminated." 2>&1 && exit 1 fi else echo #new line read -p "!!!WARNING!!! This script will completely erase your chosen external hard drive and all it's contents. Are you sure you wish to continue? You will not be able undo the erasing of the drive! (Y/N)" erase echo #new line if [[ $erase == [yY] || $erase == [yY][eE][sS] ]]; then echo #new line echo "Creating LUKS volume on chosen external hard drive" echo #new line read -p "Would you like to add a passphrase to your drive to act as a failback in case of zymkey hardware failure? The drive will then be unlockable either with the zymkey key or the passphrase. (Y/N)" confirm2 echo #new line if [[ $confirm2 == [yY] || $confirm2 == [yY][eE][sS] ]]; then echo #new line read -s -p "Please enter the fallback passphrase you wish to use:" fallbackPass1 echo #new line if [[ $fallbackPass1 ]]; then echo #new line read -s -p "Please confirm the fallback passphrase you wish to use:" fallbackPass2 echo #new line if [[ $fallbackPass2 ]] && [[ $fallbackPass1 == $fallbackPass2 ]]; then echo -n $fallbackPass1 | cryptsetup -q -v luksFormat $EXT_DRIVE >/dev/null echo #new line echo "Passphrase added to drive" echo -n $fallbackPass1 | cryptsetup -q -v luksAddKey $EXT_DRIVE /etc/luks/$KEY_NAME.bin >/dev/null echo #new line echo "Zymkey key added to drive" else echo #new line echo "Passphrases don't match, script terminated." 2>&1 && exit 1 fi else echo #new line echo "No passphrase was provided, script terminated." 2>&1 && exit 1 fi else echo #new line echo "Zymkey key added to drive" cryptsetup -q -v luksFormat $EXT_DRIVE /etc/luks/$KEY_NAME.bin >/dev/null fi echo #new line echo "Opening LUKS volume" cryptsetup -d /etc/luks/$KEY_NAME.bin -q -v luksOpen $EXT_DRIVE $DISK_MOUNT_NAME >/dev/null echo #new line echo "Creating ext4 filesystem on encrypted LUKS volume" mkfs.ext4 -j /dev/mapper/$DISK_MOUNT_NAME -F >/dev/null || exit else echo #new line echo "Script terminated" 2>&1 && exit 1 fi fi echo #new line echo "Deleting unlocked encryption key" rm /etc/luks/$KEY_NAME.bin UUID_EXT_DIST=$(blkid -s UUID -o value $EXT_DRIVE) echo #new line echo "Creating shell script that unlocks external drive key and mounts drive at boot" cat > /etc/luks/mountExtHdd.sh <<EOF #!/bin/bash export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" # run python script to unlock encrypted external hard drive python3 /etc/luks/unlockExtHddKey.py | base64 -d | cryptsetup -d - -v luksOpen /dev/disk/by-uuid/$UUID_EXT_DIST $DISK_MOUNT_NAME mount /dev/mapper/$DISK_MOUNT_NAME /media/$DISK_MOUNT_NAME -o discard,defaults,noatime EOF chmod +x /etc/luks/mountExtHdd.sh echo #new line echo "Creating and enabling service to to mount external drive at boot" cat > /etc/systemd/system/mount-ext-hdd.service <<EOF [Unit] Description=Example systemd service. After=zkifc.service multi-user.target [Service] RemainAfterExit=true Restart=always RestartSec=10 TimeoutStartSec=600 ExecStart=/etc/luks/mountExtHdd.sh [Install] WantedBy=multi-user.target EOF systemctl daemon-reload systemctl enable mount-ext-hdd.service systemctl start mount-ext-hdd.service echo #new line echo "Script completed successfully" 2>&1 && exit 1
Step 4 – Making the script executable
To do this run
sudo chmod +x ~/zkyExtSetup.sh (clearly, if you’ve chosen a different name and location for this file, use that one instead)
Step 5 – Run the script
If you’re not already in the home directory, run
cd ~/ Once you’ve done that, run
sudo ./zkeyExtSetup.sh (you need to run the script as the sudo/root user)
Step 6 – Ensure services requiring the external hard drive wait for the drive to mount at boot
If you have any services that depend on the external hard drive being mounted (for example, I required Docker to wait for the external hard drive, as that’s where I was storing my Docker volumes). You can add the following to the systemd service file under the [unit] heading:
This tells the service to wait for the mount-ext-hdd.service to load first. You may also need to add a slight timeout within the service file (I had to do this for Docker). To do this I added the following under the [service] header of the Docker service file:
This tells the service to sleep for 40 seconds before loading, the exact number of seconds may need to be something you experiment with to get right.