Index » Community Contributions » System encryption using LUKS and GPG encrypted keys for arch linux

Intro

Using arch linux mkinitcpio's encrypt hook, one can easily use encrypted root partitions with LUKS. It's also possible to use key files stored on an external drive, like an usb stick. However, if someone steals your usb stick, he can just copy the key and potentially access the system. I wanted to have a little extra security by additionally encrypting the key file with gpg using a symmetric cipher and a passphrase.
Since the encrypt hook doesn't support this scenario, I created a modifed hook called “etwo” (silly name I know, it was the first thing that came to my mind). It will simply look if the key file has the extension .gpg and, if yes, use gpg to decrypt it, then pipe the result into cryptsetup.

Conventions

In this short guide, I use the following disk/partition names:
/dev/sda: is the hard disk that will contain an encrypted swap (/dev/sda1), /var (/dev/sda2) and root (/dev/sda3) partition.
/dev/sdb is the usb stick that will contain the gpg encrypted luks keys, the kernel and grub. It will have one partition /dev/sdb1 formatted with ext2.
/dev/mapper/root, /dev/mapper/swap and /dev/mapper/var will be the encrypted devices.

Credits

Thanks to the authors of SECURITY_System_Encryption_DM-Crypt_with_LUKS (gentoo wiki), System Encryption with LUKS (arch wiki), mkinitcpio (arch wiki) and Early Userspace in Arch Linux (/dev/brain0 blog)!

Guide

1. Boot the arch live cd
I had to use a newer testing version, because the 2010.05 cd came with a broken gpg. You can download one here: http://releng.archlinux.org/isos/. I chose the “core“ version. Go ahead and boot the live cd, but don't start the setup yet.

2. Set keymap
Use km to set your keymap. This is important for non-qwerty keyboards to avoid suprises with passphrases...

3. Wipe your discs
ATTENTION: this will DELETE everything on /dev/sda and /dev/sdb forever! Do not blame me for any lost data!
Before encrypting the hard disc, it has to be completely wiped and overwritten with random data. I used shred for this. Others use badblocks or dd with /dev/urandom. Either way, this will take a long time, depending on the size of your disc. I also wiped my usb stick just to be sure.

shred -v /dev/sda
shred -v /dev/sdb

4. Partitioning
Fire up fdisk and create the following partitions:

  • /dev/sda1, type linux swap.

  • /dev/sda2: type linux

  • /dev/sda3: type linux

  • /dev/sdb1, type linux

Of course you can choose a different layout, this is just how I did it. Keep in mind that only the root filesystem will be decrypted by the initcpio. The rest will be decypted during normal init boot using /etc/crypttab, the keys being somewhere on the root filesystem.

5. Format  and mount the usb stick
Create an ext2 filesystem on /dev/sdb1:

mkfs.ext2 /dev/sdb1
mkdir /root/usb
mount /dev/sdb1 /root/usb
cd /root/usb # this will be our working directory for now.

Do not mount anything to /mnt, because the arch installer will use that directory later to mount the encrypted root filesystem.

6. Configure the network (if not already done automatically)

ifconfig eth0 192.168.0.2 netmask 255.255.255.0
route add default gw 192.168.0.1
echo "nameserver 192.168.0.1" >> /etc/resolv.conf

(this is just an example, your mileage may vary)

7. Install gnupg

pacman -Sy
pacman -S gnupg

Verify that gnupg works by launching gpg.

8. Create the keys
Just to be sure, make sure swap is off:

cat /proc/swaps

should return no entries.

Create gpg encrypted keys (remember, we're still in our working dir /root/usb):

dd if=/dev/urandom bs=512 count=4 | gpg -v --cipher-algo aes256 --digest-algo sha512 -c -a  > root.gpg
dd if=/dev/urandom bs=512 count=4 | gpg -v --cipher-algo aes256 --digest-algo sha512 -c -a > var.gpg

Choose a strong password!!

Don't do this in two steps, e.g don't do dd to a file and then gpg on that file. The key should never be stored in plain text on an unencrypted device, except if that device is wiped on system restart (ramfs)!

Note that the default cipher for gpg is cast5, I just chose to use a different one.

9. Create the encrypted devices with cryptsetup
Create encrypted swap:

cryptsetup -c aes-cbc-essiv:sha256 -s 256 -h whirlpool -d /dev/urandom create swap /dev/sda1

You should see /dev/mapper/swap now. Don't format nor turn it on for now. This will be done by the arch installer.

Important: From the Cryptsetup 1.1.2 Release notes:

Cryptsetup can accept passphrase on stdin (standard input). Handling of new line (\n) character is defined by input specification:
    if keyfile is specified as "-" (using --key-file=- or by positional argument in luksFormat and luksAddKey, like cat file | cryptsetup --key-file=- <action> ), input is processed
      as normal binary file and no new line is interpreted.
    if there is no key file specification (with default input from stdin pipe like echo passphrase | cryptsetup <action> ) input is processed as input from terminal, reading will
      stop after new line is detected.

If I understand this correctly, since the randomly generated key can contain a newline early on, piping the key into cryptsetup without specifying --key-file=- could result in a big part of the key to be ignored by cryptsetup. Example: if the random key was "foo\nandsomemorebaratheendofthekey", piping it directly into cryptsetup without --key-file=- would result in cryptsetup using only "foo" as key which would have big security implications. We should therefor ALWAYS pipe the key into cryptsetup using --key-file=- which ignores newlines.

gpg -q -d root.gpg 2>/dev/null | cryptsetup -v -–key-file=- -c aes-cbc-essiv:sha256 -s 256 -h whirlpool luksFormat /dev/sda3
gpg -q -d var.gpg 2>/dev/null | cryptsetup -v –-key-file=- -c aes-cbc-essiv:sha256 -s 256 -h whirlpool -v luksFormat /dev/sda2

Check for any errors.

10. Open the luks devices

gpg -d root.gpg 2>/dev/null | cryptsetup -v –-key-file=- luksOpen /dev/sda3 root
gpg -d var.gpg 2>/dev/null | cryptsetup -v –-key-file=- luksOpen /dev/sda2 var

If you see /dev/mapper/root and /dev/mapper/var now, everything is ok.

11. Start the installer /arch/setup
Follow steps 1 to 3.
At step 4 (Prepare hard drive(s), select “3 – Manually Configure block devices, filesystems and mountpoints. Choose /dev/sdb1 (the usb stick) as /boot, /dev/mapper/swap for swap, /dev/mapper/root for / and /dev/mapper/var for /var.
Format all drives (choose “yes” when asked “do you want to have this filesystem (re)created”) EXCEPT for /dev/sdb1, choose “no”. Choose the correct filesystem for /dev/sdb1, ext2 in my case. Use swap for /dev/mapper/swap. For the rest, I chose ext4.

Select DONE to start formatting.

At step 5 (Select packages), select grub as boot loader. Select the base group. Add mkinitcpio.

Start step 6 (Install packages).

Go to step 7 (Configure System).
By sure to set the correct KEYMAP, LOCALE and TIMEZONE in /etc/rc.conf.

Edit /etc/fstab:

/dev/mapper/root / ext4 defaults 0 1
/dev/mapper/swap swap swap defaults 0 0
/dev/mapper/var /var ext4 defaults 0 1
# /dev/sdb1 /boot ext2 defaults 0 1

Configure the rest normally. When you're done, setup will launch mkinitcpio. We'll manually launch this again later.

Go to step 8 (install boot loader).
Be sure to change the kernel line in menu.lst:

kernel /vmlinuz26 root=/dev/mapper/root cryptdevice=/dev/sda3:root cryptkey=/dev/sdb1:ext2:/root.gpg

Don't forget the :root suffix in cryptdevice!

Also, my root line was set to (hd1,0). Had to change that to

root (hd0,0)

Install grub to /dev/sdb (the usb stick).

Now, we can exit the installer.

12. Install mkinitcpio with the etwo hook.
Create /mnt/lib/initcpio/hooks/etwo:

#!/usr/bin/ash

run_hook() {
    /sbin/modprobe -a -q dm-crypt >/dev/null 2>&1
    if [ -e "/sys/class/misc/device-mapper" ]; then
        if [ ! -e "/dev/mapper/control" ]; then
            /bin/mknod "/dev/mapper/control" c $(cat /sys/class/misc/device-mapper/dev | sed 's|:| |')
        fi
        [ "${quiet}" = "y" ] && CSQUIET=">/dev/null"

        # Get keyfile if specified
        ckeyfile="/crypto_keyfile"
        usegpg="n"
        if [ "x${cryptkey}" != "x" ]; then
            ckdev="$(echo "${cryptkey}" | cut -d: -f1)"
            ckarg1="$(echo "${cryptkey}" | cut -d: -f2)"
            ckarg2="$(echo "${cryptkey}" | cut -d: -f3)"
            if poll_device "${ckdev}" ${rootdelay}; then
                case ${ckarg1} in
                    *[!0-9]*)
                        # Use a file on the device
                        # ckarg1 is not numeric: ckarg1=filesystem, ckarg2=path
                        if [ "${ckarg2#*.}" = "gpg" ]; then
                            ckeyfile="${ckeyfile}.gpg"
                            usegpg="y"
                        fi
                        mkdir /ckey
                        mount -r -t ${ckarg1} ${ckdev} /ckey
                        dd if=/ckey/${ckarg2} of=${ckeyfile} >/dev/null 2>&1
                        umount /ckey
                        ;;
                    *)
                        # Read raw data from the block device
                        # ckarg1 is numeric: ckarg1=offset, ckarg2=length
                        dd if=${ckdev} of=${ckeyfile} bs=1 skip=${ckarg1} count=${ckarg2} >/dev/null 2>&1
                        ;;
                esac
            fi
            [ ! -f ${ckeyfile} ] && echo "Keyfile could not be opened. Reverting to passphrase."
        fi
        if [ -n "${cryptdevice}" ]; then
            DEPRECATED_CRYPT=0
            cryptdev="$(echo "${cryptdevice}" | cut -d: -f1)"
            cryptname="$(echo "${cryptdevice}" | cut -d: -f2)"
        else
            DEPRECATED_CRYPT=1
            cryptdev="${root}"
            cryptname="root"
        fi

        warn_deprecated() {
            echo "The syntax 'root=${root}' where '${root}' is an encrypted volume is deprecated"
            echo "Use 'cryptdevice=${root}:root root=/dev/mapper/root' instead."
        }

        if  poll_device "${cryptdev}" ${rootdelay}; then
            if /sbin/cryptsetup isLuks ${cryptdev} >/dev/null 2>&1; then
                [ ${DEPRECATED_CRYPT} -eq 1 ] && warn_deprecated
                dopassphrase=1
                # If keyfile exists, try to use that
                if [ -f ${ckeyfile} ]; then
                    if [ "${usegpg}" = "y" ]; then
                        # gpg tty fixup
                        if [ -e /dev/tty ]; then mv /dev/tty /dev/tty.backup; fi
                        cp -a /dev/console /dev/tty
                        while [ ! -e /dev/mapper/${cryptname} ];
                        do
                            sleep 2
                            /usr/bin/gpg -d "${ckeyfile}" 2>/dev/null | cryptsetup --key-file=- luksOpen ${cryptdev} ${cryptname} ${CSQUIET}
                            dopassphrase=0
                        done
                        rm /dev/tty
                        if [ -e /dev/tty.backup ]; then mv /dev/tty.backup /dev/tty; fi
                    else
                        if eval /sbin/cryptsetup --key-file ${ckeyfile} luksOpen ${cryptdev} ${cryptname} ${CSQUIET}; then
                            dopassphrase=0
                        else
                            echo "Invalid keyfile. Reverting to passphrase."
                        fi
                    fi
                fi
                # Ask for a passphrase
                if [ ${dopassphrase} -gt 0 ]; then
                    echo ""
                    echo "A password is required to access the ${cryptname} volume:"

                    #loop until we get a real password
                    while ! eval /sbin/cryptsetup luksOpen ${cryptdev} ${cryptname} ${CSQUIET}; do
                        sleep 2;
                    done
                fi
                if [ -e "/dev/mapper/${cryptname}" ]; then
                    if [ ${DEPRECATED_CRYPT} -eq 1 ]; then
                        export root="/dev/mapper/root"
                    fi
                else
                    err "Password succeeded, but ${cryptname} creation failed, aborting..."
                    exit 1
                fi
            elif [ -n "${crypto}" ]; then
                [ ${DEPRECATED_CRYPT} -eq 1 ] && warn_deprecated
                msg "Non-LUKS encrypted device found..."
                if [ $# -ne 5 ]; then
                    err "Verify parameter format: crypto=hash:cipher:keysize:offset:skip"
                    err "Non-LUKS decryption not attempted..."
                    return 1
                fi
                exe="/sbin/cryptsetup create ${cryptname} ${cryptdev}"
                tmp=$(echo "${crypto}" | cut -d: -f1)
                [ -n "${tmp}" ] && exe="${exe} --hash \"${tmp}\""
                tmp=$(echo "${crypto}" | cut -d: -f2)
                [ -n "${tmp}" ] && exe="${exe} --cipher \"${tmp}\""
                tmp=$(echo "${crypto}" | cut -d: -f3)
                [ -n "${tmp}" ] && exe="${exe} --key-size \"${tmp}\""
                tmp=$(echo "${crypto}" | cut -d: -f4)
                [ -n "${tmp}" ] && exe="${exe} --offset \"${tmp}\""
                tmp=$(echo "${crypto}" | cut -d: -f5)
                [ -n "${tmp}" ] && exe="${exe} --skip \"${tmp}\""
                if [ -f ${ckeyfile} ]; then
                    exe="${exe} --key-file ${ckeyfile}"
                else
                    exe="${exe} --verify-passphrase"
                    echo ""
                    echo "A password is required to access the ${cryptname} volume:"
                fi
                eval "${exe} ${CSQUIET}"

                if [ $? -ne 0 ]; then
                    err "Non-LUKS device decryption failed. verify format: "
                    err "      crypto=hash:cipher:keysize:offset:skip"
                    exit 1
                fi
                if [ -e "/dev/mapper/${cryptname}" ]; then
                    if [ ${DEPRECATED_CRYPT} -eq 1 ]; then
                        export root="/dev/mapper/root"
                    fi
                else
                    err "Password succeeded, but ${cryptname} creation failed, aborting..."
                    exit 1
                fi
            else
                err "Failed to open encryption mapping: The device ${cryptdev} is not a LUKS volume and the crypto= paramater was not specified."
            fi
        fi
        rm -f ${ckeyfile}
    fi
}

Create /mnt/lib/initcpio/install/etwo:

#!/bin/bash

build() {
    local mod

    add_module dm-crypt
    if [[ $CRYPTO_MODULES ]]; then
        for mod in $CRYPTO_MODULES; do
            add_module "$mod"
        done
    else
        add_all_modules '/crypto/'
    fi
    add_dir "/dev/mapper"
    add_binary "cryptsetup"
    add_binary "dmsetup"
    add_binary "/usr/bin/gpg"
    add_file "/usr/lib/udev/rules.d/10-dm.rules"
    add_file "/usr/lib/udev/rules.d/13-dm-disk.rules"
    add_file "/usr/lib/udev/rules.d/95-dm-notify.rules"
    add_file "/usr/lib/initcpio/udev/11-dm-initramfs.rules" "/usr/lib/udev/rules.d/11-dm-initramfs.rules"

    add_runscript
}
help ()
{
cat<<HELPEOF
  This hook allows for an encrypted root device with support for gpg encrypted key files.
  To use gpg, the key file must have the extension .gpg and you have to install gpg and add /usr/bin/gpg
  to your BINARIES var in /etc/mkinitcpio.conf.
HELPEOF
}

Edit /mnt/etc/mkinitcpio.conf (only relevant sections displayed):

MODULES=”ext2 ext4” # not sure if this is really nessecary.
BINARIES=”/usr/bin/gpg” # this could probably be done in install/etwo...
HOOKS=”base udev usbinput keymap autodetect pata scsi sata usb etwo filesystems” # (usbinput is only needed if you have an usb keyboard)

Copy the initcpio stuff over to the live cd:

cp /mnt/lib/initcpio/hooks/etwo /lib/initcpio/hooks/
cp /mnt/lib/initcpio/install/etwo /lib/initcpio/install/
cp /mnt/etc/mkinitcpio.conf /etc/

Verify your LOCALE, KEYMAP and TIMEZONE in /etc/rc.conf!

Now reinstall the initcpio:

mkinitcpio -g /mnt/boot/kernel26.img

Make sure there were no errors and that all hooks were included.

13. Decrypt the "var" key to the encrypted root

mkdir /mnt/keys
chmod 500 /mnt/keys
gpg –output /mnt/keys/var -d /mnt/boot/var.gpg
chmod 400 /mnt/keys/var

14. Setup crypttab
Edit /mnt/etc/crypttab:

swap    /dev/sda1    SWAP    -c aes-cbc-essiv:sha256 -s 256 -h whirlpool
var /dev/sda2    /keys/var

15. Reboot
We're done, you may reboot. Make sure you select the usb stick as the boot device in your bios and hope for the best. big_smile. If it didn't work, play with grub's settings or boot from the live cd, mount your encrypted devices and check all settings. You might also have less trouble by using uuid's instead of device names.  I chose device names to keep things as simple as possible, even though it's not the optimal way to do it.

Make backups of your data and your usb stick and do not forget your password(s)! Or you can say goodbye to your data forever...

0 (0)
Article Rating (No Votes)
Rate this article
Attachments
There are no attachments for this article.
Comments
There are no comments for this article. Be the first to post a comment.
Full Name
Email Address
Security Code Security Code
Related Articles RSS Feed
RHEL: How to change a USER/GROUP UID/GID and all owned files
Viewed 2283 times since Sat, Jun 2, 2018
ZFS: Remove an existing zfs filesystem
Viewed 573 times since Sun, Jun 3, 2018
Linux LVM recovery
Viewed 1139 times since Wed, Jan 23, 2019
Improve security with polyinstantiation
Viewed 100 times since Fri, May 15, 2020
Linux File Systems (mkfs, mount, fstab) ext4
Viewed 803 times since Sat, Jun 2, 2018
Using grep to find string in files
Viewed 155 times since Fri, May 15, 2020
linux aix Killing a process and all of its descendants
Viewed 134 times since Tue, May 5, 2020
RHEL: Extending a vmdk (Virtual Machine disk)
Viewed 1013 times since Sun, May 27, 2018
How to accurately determine when the system was booted
Viewed 469 times since Wed, Oct 3, 2018
ZPOOL: Detach a submirror from a mirrored zpool
Viewed 634 times since Sun, Jun 3, 2018