Unlocking LUKS with a USB key

This guide offers a method for unlocking a Red Hat Enterprise Linux / CentOS LUKS encrypted partition with a USB key, that to the casual observer, appears blank. This guide is offered with no warranty and I accept no liability if you turn your computer in to a brick!

Due to the bleeding-edge nature of changes in Fedora, this guide is only recommended for use on Red Hat Enterprise Linux and CentOS versions 6 and 7. You may be able to apply the method if your distribution is based on RHEL/CentOS; e.g. Scientific Linux.

You will need a spare USB memory stick that you are happy to sacrifice. This method fills the stick with random data to ensure a lost stick simply appears as blank (i.e. no partition tables), or as random data - if someone knows what they are looking for. They will still be unable to extract your key if you vary some of the settings outlined below. This method also ties the unlocking to the ID of a particular USB key, thus providing an extra layer of security.

It is assumed you have a single disc with a single LUKS encrypted partition already present that you are unlocking with a passphrase. Unlocking a second (or third) LUKS volume requires the script outlined in section 5.


1: Create a key file

1.1 Fill a USB memory stick with random data via this command: dd if=/dev/urandom of=/dev/sdb bs=1

This assumes your memory stick is /dev/sdb. CAUTION: The 'dd' command will happily wipe your hard drive if you set the wrong /dev/sd?.

It is noted that the entropy pool for creating random data is not particularly large on some computers. A laptop with a larger number of built-in peripheral devices will have a larger pool than a server.

1.2 Extract a "key" from the random data on the USB stick with this command: dd if=/dev/sdb of=luks-secret.key bs=1 count=4096

The above line will extract 4096 bytes of random data from the USB stick. This could be changed to bs=1024 count=2 (for example) to extract data from a different part of the random noise. If you make a change like this, keep a note of it for step 2.2

1.3 Add the key to your LUKS partition with this command: cryptsetup luksAddKey /dev/sda3 luks-secret.key --key-slot 1

The command above adds the newly generated key to slot 1 (assuming slot 0 contains your LUKS passphrase) of your LUKS partition. It is assumed your encrypted partition is on /dev/sda3. Check before applying the changes.

You will be prompted for your usual LUKS passphrase before the key is added. You can check the key has been added to slot 1 with the following command: cryptsetup luksDump /dev/sda3

1.4 Delete the key from your file system with the following command: shred --remove --zero luks-secret.key


2: Modify Dracut

Fedora 12 (and onwards) introduced a new boot system called dracut. It builds the 'initramfs', used to boot the system, from modules which can be modified to carry your own customisations.

2.1 Locate the "by-id" name of your USB key with the following command: ls -l /dev/disk/by-id | grep usb

e.g. usb-LaCie_iamaKey_60f2f4441dc104-0:0 -> ../../sdb

Remember to escape the : when you add the ID to the script in 2.2.

2.2 Modify cryptroot-ask.sh in CentOS 6: /usr/share/dracut/modules.d/90crypt and in CentOS 7: /usr/lib/dracut/modules.d/90crypt

Scroll down the script to:

#
# Open LUKS device
#

...and underneath the #, and before info "luksOpen $device $luksname $luksfile", create some space to enter the text below:

# Unlock with USB key
udevsettle
usbkey=/dev/disk/by-id/usb-LaCie_iamaKey_60f2f4441dc104-0\:0
if [ -e $usbkey ]; then
  ask_passphrase=0
  echo "USB Key detected - unlocking partition $device ..."
  dd if=$usbkey bs=1 count=4096 | cryptsetup luksOpen $device $luksname --key-file=-
fi

Notes: usbkey= should match the by-id discovered in 2.1 and do not forget to adjust bs=1 count=4096 if you changed them in step 1.2. You can add a delay to the script if your system does not see the USB key in time, although udevsettle should stop that. If you experience problems and need to slow things down, add sleep 5 before udevsettle for a 5 second pause. This can be increased to 10, or more, if needed.

The above steps will need to be repeated each time dracut is updated, so it is advisable to keep a back-up copy of the changed files to save you lots of work. See the cheat script in 3.

Notes for CentOS 7

'dd' is not loaded by default into the initramfs, so you need to make the following modifications:

Edit /usr/lib/dracut/modules.d/90crypt/module-setup.sh and add the path to dd in the install() section:

install() {
    <snip>
    dracut_need_initqueue
    inst /usr/bin/dd
}

You need to modify /etc/dracut.conf to stop systemd from running the decryption process. Modify the conf file with the following:

# dracut modules to omit
omit_dracutmodules+="systemd"

# dracut modules to add to the default
add_dracutmodules+="crypt"

...then create a new initramfs, as in 3.


3: Create new initramfs

Use the following command to create a new initramfs: dracut -f

This will create a new initramfs file for the current running kernel. Future kernel updates will take the above modifications. Updates to the dracut package will overwrite the changes in 2.2. Watch out for Yum updates where the kernel and dracut are updated together!

If dracut has been updated, you can use this cheat method to make updates simpler:

3.1 In the /root directory (as super-user), mkdir archive scripts.

3.2 Copy your modified [CentOS 6] /usr/share/dracut/modules.d/90crypt/cryptroot-ask.sh | [CentOS 7]: /usr/lib/dracut/modules.d/90crypt/cryptroot-ask.sh to /root/archive

3.3 Create a script in /root/scripts/ called update-dracut and copy the following in to it:

#!/bin/bash

cp /root/archive/cryptroot-ask.sh /usr/share/dracut/modules.d/90crypt/

dracut -f

exit 0

Now if dracut is updated, simply run /root/scripts/update-dracut and reboot to ensure you can unlock your LUKS partition with the USB key.


4: Reboot

With the key in place, the LUKS volume should automatically unlock. Without the key, the system will prompt for the passphrase.


5: Two LUKS

What do you do if you have, for example, a server with a boot drive which contains a LUKS encrypted root partition, and a RAID array which also contains a LUKS partition? Would you like to open both with the USB key method? Well, you can.

You will need to complete step 1 and add your USB key file to both LUKS partitions; e.g. /dev/sda3, /dev/sdb1.

Step 2 is only needed on the boot drive, as this needs to be opened first. Once it is open and the system has booted, we can open the second volume and mount it via a shell script called from /etc/rc.d/rc.local

Note: If you have created your encrypted volumes prior to using this method, and both are listed in /etc/crypttab, you will need to remove (or comment-out) the second drive's luks reference else dracut will try to open the drive at boot time and you will be prompted for the passphrase. I suggest commenting out the second reference until you are happy with the scripted method below. Do not forget to run the update script in section 3, else the new /etc/crypttab file will not be copied to the initramfs.

We open the second volume from a shell script which can be placed in: /usr/local/sbin. Remember to back-up /usr/local!

In this example, we will create a script called mount-vol1 and give it root : root and 755 permissions:

#!/bin/bash
#
# Script to unlock vol1 and mount it
#
# Last updated: 28th December 2013

# Set specific key
#
USBKEY=/dev/disk/by-id/usb-Ut165_USB_Flash_Disk_0025805090688-0\:0

# Define LUKS partition to unlock and mount
#
LUKS=luks-0945166f-16e5-4323-9c99-d315261235a5
MAPPER="/dev/mapper/$LUKS"
DEVICE=0945166f-16e5-4323-9c99-d315261235a5

DEV="/dev/disk/by-uuid/$DEVICE"

# Check for key and unlock if present
#
if [ -e $usbkey ]; then
echo "Unlocking $DEV"
dd if=$USBKEY bs=1 count=4096 | \
cryptsetup luksOpen $DEV $LUKS --key-file=- --key-slot 1
mount -t ext4 -o defaults $MAPPER /mnt/vol1
fi

# Clean up
#
unset USBKEY LUKS MAPPER DEVICE DEV

exit 0

DEVICE= can be found with the command cryptsetup luksUUID /dev/sdb1 (assuming the second volume is /dev/sdb1) or from /etc/crypttab. LUKS= is the UUID with "luks-" appended.

In the script above, the LUKS encrypted volume contains an ext4 partition which we mount on /mnt/vol1. You can set your own mount-point depending upon your requirements. You can also add other mount options to -o, such as: -o defaults,acl,user_xattr.

We call the script by adding /usr/local/sbin/mount-vol1 to the end of /etc/rc.d/rc.local. The drive is opened and mounted in time for any NFS server to establish shares from the encrypted volume - at least in the author's experience.

Note: With the second drive's luks details commented out of /etc/crypttab, if the USB key is not present, there will be no passphrase prompt to unlock and mount the partition. Readers are invited to create their own manual passphrase script if a back-up solution is needed.

A third or fourth partition could be unlocked by expanding the script to include LUKS2, MAPPER2, DEVICE2, etc., and setting the corresponding $LUKS2, $DEV2 in extra cryptsetup and mount lines in the if/fi loop.


6: Debug

If you experience problems and want to debug the boot process, at the grub screen, press 'a' and remove the "rhgb" and "quiet" from the kernel command line and replace them with [CentOS 6] rdinitdebug rdshell or [CentOS 7] rd.debug. You will be able to follow the boot process with lots of verbose output. This should show you if the USB subsystem is taking a really long time to load. If the boot process fails, it will drop to a shell. You may need a rescue DVD if things have gone horribly wrong!

You may need to view dmesg to look for clues as not everything is printed to stdout, even when you are debug mode. It is also a good idea to reboot without the debug mode set once you have cleared any errors.

This page lists the debugging options: https://fedoraproject.org/wiki/Dracut/Debugging


Page updated: 10th May 2017