The goal is to create an encrypted device which gets automatically unlocked using an usb key. Updated on 20160521 for ubuntu 16.04 which creates a lot of problems.

Create the key on the usb drive

A good idea is to put the key on the usb drive in such a way it’s not obvious that is a key. Usually on the usb drive there is a 512 bytes MBR (1 sector) and then up to 32 other sectors until the first partition. To investigate this you can do:

<pre lang="bash">fdisk /dev/sdc

Command (m for help): p

Disk /dev/sdc: 16.0 GB, 16047407104 bytes
64 heads, 32 sectors/track, 15304 cylinders, total 31342592 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x000edf0e

   Device Boot      Start         End      Blocks   Id  System
/dev/sdc1   *          32    31342591    15671280    c  W95 FAT32 (LBA)

To check what it is in the first 32 sectors, dump them to a file:

<pre lang="bash">dd if=/dev/sdc of=32sectors bs=512 count=32
hexdump 32sectors #or ghex 32sectors

You usualy see that the first 512 bytes contain the MBR, up to the marker aa55 then there are only zeroes.

00001b0 0000 0000 0000 0000 df0e 000e 0000 0180
00001c0 0001 3f0c ffe0 0020 0000 3fe0 01de 0000
00001d0 0000 0000 0000 0000 0000 0000 0000 0000
*
00001f0 0000 0000 0000 0000 0000 0000 0000 aa55
0000200 0000 0000 0000 0000 0000 0000 0000 0000
*

You place your key in this space. Let’s assume you will place the key in the 5th sector.

<pre lang="bash">#Create key from pseudorandom data
dd bs=512 count=4 if=/dev/urandom of=key2048 iflag=fullblock
#Put on disc
dd if=key2048 of=/dev/sdc bs=512 seek=4
#Remove
shred key2048

As an alternative you can create the key in a tmpfs mounted path to avoid storing it locally or you can directly generate it on the usb.

XUbuntu 14 16.04 install

  • Now you can install ubuntu. Start the livecd and select “Try ubuntu” then, after booting, drop to a terminal. Please note that in ubuntu 16.04 there is a bug when creating the usb disk: Not a COM32R image (check here for the fix).
  • Use gparted to partition you disk. Leave 512M for an unencrypted /boot then partition the rest as you choose.
  • Setup the encrypted devices
<pre lang="bash"><del>cryptsetup luksAddKey /dev/sda2 --new-keyfile-offset=2048 --new-keyfile-size=2048 /dev/sdc
cryptsetup luksOpen /dev/sda2 root --keyfile-offset=2048 --keyfile-size=2048 --key-file=/dev/sdc
</del>

do this for each device.

  • Install ubuntu but instead of rebooting drop back to the live session.
  • The above do not work in ubuntu 16.04 since ubiquity sees the opened luks partitions as disks instead of partions (see this bug). The only solution is to use the installer to create encrypted devices using a password, create and format partitions inside then do the rest after the installation. Of course this means destroying the content of the given partition.
  • Chroot into the new install
<pre lang="bash">cd /mnt
mkdir root
mount /dev/mapper/root root
mount /dev/sda1 root/boot
chroot root
mount -t proc proc /proc
mount -t sysfs sys /sys

Please note that for any debuging needed in case you cannot reboot you need to come back into the livecd and do the above commands so you can chroot into the unbootable system.

  • Configure fstab and crypttab

Lets assume an encrypted / and swap devices:

/etc/fstab will be:

<pre lang="bash">              
/dev/mapper/root /               ext4    errors=remount-ro 0       1
# /boot was on /dev/sda1 during installation
UUID=fbbef8ab-5a70-44be-9486-473c00773f5c /boot           ext4    defaults        0       2
/dev/mapper/swap none            swap    sw              0       0

/etc/crypttab will be:

<pre lang="bash"><del>swap    /dev/sda2       /dev/sdb        luks,swap,keyscript=/etc/decryptkeydevice/decryptkeydevice_keyscript.sh
root    /dev/sda3       /dev/sdb        luks,keyscript=/etc/decryptkeydevice/decryptkeydevice_keyscript.sh
</del>

OHOHO! What is decryptkeydevice_keyscript.sh? Default ubuntu install does not support keyfile or keyfile parameters in crypttab and proposes to use the passdev executable: keyscript=/lib/cryptsetup/scripts/passdev which searches for a file on the device (see source bellow). My first idea was to implement this myself but then I found a german forum which provided exactly what I wanted (all credits given). A script which can extract binary data from an usb device with given offset, length.

<pre lang="bash"># configuration for decryptkeydevice
#

# ID(s) of the USB/MMC key(s) for decryption (sparated by blanks)
# as listed in /dev/disk/by-id/
DECRYPTKEYDEVICE_DISKID="mmc-XXX_0x0AAABBBCCCDDD usb-XyzFlash_XYZDFGHIJK_XXYYZZ00AA-0:0"

# blocksize usually 512 is OK
DECRYPTKEYDEVICE_BLOCKSIZE="512"

# start of key information on keydevice DECRYPTKEYDEVICE_BLOCKSIZE * DECRYPTKEYDEVICE_SKIPBLOCKS
DECRYPTKEYDEVICE_SKIPBLOCKS="1"
<pre lang="bash">#!/bin/sh
#
# original file name crypto-usb-key.sh
# heavily modified and adapted for "decryptkeydevice" by Franco
#
### original header :
#
# Part of passwordless cryptofs setup in Debian Etch.
# See: http://wejn.org/how-to-make-passwordless-cryptsetup.html
# Author: Wejn 
#
# Updated by Rodolfo Garcia (kix) 
# For multiple partitions
# http://www.kix.es/
#
# Updated by TJ <linux@tjworld.net> 7 July 2008
# For use with Ubuntu Hardy, usplash, automatic detection of USB devices,
# detection and examination of *all* partitions on the device (not just partition #1), 
# automatic detection of partition type, refactored, commented, debugging code.
#
# Updated by Hendrik van Antwerpen  3 Sept 2008
# For encrypted key device support, also added stty support for not
# showing your password in console mode.

# define counter-intuitive shell logic values (based on /bin/true & /bin/false)
# NB. use FALSE only to *set* something to false, but don't test for
# equality, because a program might return any non-zero on error

# Updated by Dominique Bellenger 
# for usage with Ubuntu 10.04 Lucid Lynx
# - Removed non working USB device check
# - changed vol_id to blkid, changed sed expression
# - changed TRUE and FALSE to be 1 and 0
# - changed usplash usage to plymouth usage
# - removed possibility to read from an encrypted device (why would I want to do this? The script is unnecessary if I have to type in a password)
#
### original header END

# read decryptkeydevice Key configuration settings
DECRYPTKEYDEVICE_DISKID=""
if [ -f /etc/decryptkeydevice/decryptkeydevice.conf ] ; then
		.  /etc/decryptkeydevice/decryptkeydevice.conf
fi

TRUE=1
FALSE=0

# set DEBUG=$TRUE to display debug messages, DEBUG=$FALSE to be quiet
DEBUG=$FALSE

PLYMOUTH=$FALSE
# test for plymouth and if plymouth is running
if [ -x /bin/plymouth ] && plymouth --ping; then
        PLYMOUTH=$TRUE
fi

# is stty available? default false
STTY=$FALSE
STTYCMD=false
# check for stty executable
if [ -x /bin/stty ]; then
	STTY=$TRUE
	STTYCMD=/bin/stty
elif [ `(busybox stty >/dev/null 2>&1; echo $?)` -eq 0 ]; then
	STTY=$TRUE
	STTYCMD="busybox stty"
fi

# print message to plymouth or stderr
# usage: msg "message" [switch]
# switch : switch used for echo to stderr (ignored for plymouth)
# when using plymouth the command will cause "message" to be
# printed according to the "plymouth message" definition.
# using the switch -n will allow echo to write multiple messages
# to the same line
msg ()
{
	if [ $# -gt 0 ]; then
		# handle multi-line messages
		echo $2 | while read LINE; do
			if [ $PLYMOUTH -eq $TRUE ]; then
				/bin/plymouth message --text="$1 $LINE"		
			#else
				# use stderr for all messages
				echo $3 "$2" >&2
			fi
		done
	fi
}

dbg ()
{
	if [ $DEBUG -eq $TRUE ]; then
		msg "$@"
	fi
}

# read password from console or with plymouth
# usage: readpass "prompt"
readpass ()
{
	if [ $# -gt 0 ]; then
		if [ $PLYMOUTH -eq $TRUE ]; then
			PASS=`/bin/plymouth ask-for-password --prompt="$1"`
		else
			[ $STTY -ne $TRUE ] && msg "WARNING stty not found, password will be visible"
			echo -n "$1" >&2
			$STTYCMD -echo
			read -s PASS /dev/null
			[ $STTY -eq $TRUE ] && echo >&2
			$STTYCMD echo
		fi
	fi
	echo -n "$PASS"
}

# flag tracking key-file availability
OPENED=$FALSE

# decryptkeydevice configured so try to find a key
if [ ! -z "$DECRYPTKEYDEVICE_DISKID" ]; then
	msg "Checking devices for decryption key ..."
	# Is the USB driver loaded?
	cat /proc/modules | busybox grep usb_storage >/dev/null 2>&1
	USBLOAD=0$?
	if [ $USBLOAD -gt 0 ]; then
		dbg "Loading driver 'usb_storage'"
		modprobe usb_storage >/dev/null 2>&1
	fi
	# Is the mmc_block driver loaded?
	cat /proc/modules | busybox grep mmc >/dev/null 2>&1
	MMCLOAD=0$?
	if [ $MMCLOAD -gt 0 ]; then
		dbg "Loading drivers for 'mmc'"
		modprobe mmc_core >/dev/null 2>&1
		modprobe ricoh_mmc >/dev/null 2>&1
		modprobe mmc_block >/dev/null 2>&1
		modprobe sdhci >/dev/null 2>&1
	fi

	# give the system time to settle and open the devices
	sleep 5

	for DECRYPTKEYDEVICE_ID in $DECRYPTKEYDEVICE_DISKID ; do
		DECRYPTKEYDEVICE_FILE="/dev/disk/by-id/$DECRYPTKEYDEVICE_ID"
		dbg "Trying disk/by-id/$DECRYPTKEYDEVICE_FILE ..."
		if [ -e $DECRYPTKEYDEVICE_FILE ] ; then
			dbg " found disk/by-id/$DECRYPTKEYDEVICE_FILE ..."
			OPENED=$TRUE
			break
		fi
		$DECRYPTKEYDEVICE_FILE=""
	done
fi

if [ $OPENED -eq $TRUE ]; then
	/bin/dd if=$DECRYPTKEYDEVICE_FILE bs=$DECRYPTKEYDEVICE_BLOCKSIZE skip=$DECRYPTKEYDEVICE_SKIPBLOCKS count=$DECRYPTKEYDEVICE_READBLOCKS 2>/dev/null
	if [ $? -eq 0 ] ; then
		dbg "Reading key from '$DECRYPTKEYDEVICE_FILE' ..."
	else
		dbg "FAILED Reading key from '$DECRYPTKEYDEVICE_FILE' ..."
		OPENED=$FALSE
	fi
fi

if [ $OPENED -ne $TRUE ]; then
	msg "FAILED to find suitable Key device. Plug in now and press enter, or"
	readpass "Enter passphrase: "
	msg " "
else
	msg "Success loading key from '$DECRYPTKEYDEVICE_FILE'"
fi

Just copy these files to /etc/decryptkeydevice.

Prepare initramfs

Now you have to make sure the usb device driver are available at boot time and the script above is also available. Since the crypttab method does not work in ubuntu 16.04 because of systemd (see bug here) the only solution is to trick initramfs into using a cryptroot file (similar to adding the cryptopts option to the kernel).

Add modules to /etc/initramfs-tools/modules

<pre lang="bash">usbcore
sd_mod
ehci_hcd
uhci_hcd
ohci_hcd
usb_storage

Make sure decrypt script is also copied. Create: /etc/initramfs-tools/hooks/decryptkeydevice.hook

<pre lang="bash">#!/bin/sh

. /usr/share/initramfs-tools/hook-functions

mkdir -p $DESTDIR/etc/
cp -rp /etc/ckey $DESTDIR/etc/

for mod in dm_mod dm_crypt xts sha256 aesni_intel; do
 manual_add_modules $mod
done

copy_exec /sbin/cryptsetup
copy_exec /sbin/dmsetup
copy_exec /lib/cryptsetup/askpass

Create /etc/initramfs-tools/conf.d/cryptroot

<pre lang="bash">source=/dev/sda3,target=root,keyscript=/etc/decryptkeydevice/decryptkeydevice.sh,key=/dev/sdb

Update initramfs

<pre lang="bash"># update-initramfs -u

Reboot! Remember. If things do not go as planned and the system does not reboot, go to the live session again and chroot into the system. I had to do this 3 times until it worked :)