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

Run 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 back up 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 file what you like, but I’ve chosen zkyExtSetup.sh For those of you who don’t know how to do this, if you haven’t already, install the nano text editor (sudo apt install nano). Now type cd ~/ && 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 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 goes on to ask 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 encryption key of the drive using the Zymkey, and in turn, unlock the drive at boot (ensuring that the unencrypted key is removed after boot).

#!/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 required python scripts"

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

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, for me Docker required access to the external hard drive, as that’s where I was storing my volumes). You can add the following to the systemd service file under the [unit] heading: After=mount-ext-hdd.service

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, which I had to do for Docker. To do this I added the following under the [service] header of the Docker service file: ExecStartPre=/bin/sleep 40.

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 it right.


0 Comments

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *