Update: 2019/06/22

After upgrade to Debian 10 with gpg 2.2, the script is broken. One possible workaround is avoiding the use of pinentry-curses, which could be achieved by altering the content of the /etc/luks_gpg/decrypt.sh to

 #!/bin/sh
UUID=`basename $0`
export GNUPGHOME=/etc/luks_gpg/
read -p "pincode: " -s pincode
echo "$pincode" | gpg --batch --pinentry-mode loopback --passphrase-fd 0 \
        --no-tty --decrypt "/etc/luks_gpg/$UUID.key.gpg"

and appending allow-loopback-pinentry option to /etc/luks_gpg/gpg-agent.conf.


Background

I used to use a static password stored in an yubikey to protect LUKS root on my Linux machines. It is not safe if losing my yubikey since the static password does not require any pin code. I was always thinking about using yubikey’s GPG smartcard capacity to make it safer, and finally I spent about 1 hour getting it work today. XD

I generally followed this link.

How to

I use Debian stretch, and I have already setup an LUKS partition on a partition whose UUID is <UUID>. Login as root user, and following these steps.

  1. Install necessary packages.
    apt install -y scdaemon pinentry-curses
    
  2. Create /etc/luks_gpg/ and set permission properly.
    mkdir -p /etc/luks_gpg
    chown root:root /etc/luks_gpg
    chmod 700 /etc/luks_gpg
    
  3. Edit /etc/luks_gpg/gpg-agent.conf to force gpg to use curses interface since initramfs has no GUI.
    pinentry-program /usr/bin/pinentry-curses
    
  4. Import your public key with $GNUPGHOME set to /etc/luks_gpg/, and trust it.
    gpg --homedir /etc/luks_gpg --import <path_to_your_public_key>
    gpg --homedir /etc/luks_gpg --edit-key [email protected]
    gpg> trust
    
  5. Generate a password file for LUKS and setup it as LUKS’s password.
    dd if=/dev/random of=/etc/luks_gpg/<UUID>.key bs=1 count=256
    cryptsetup luksAddKey /dev/disk/by-uuid/<UUID> /etc/luks_gpg/<UUID>.key
    
  6. Use your public key to encrypt the key and safely delete it.
    gpg --homedir /etc/luks_gpg --encrypt --recipient [email protected] /etc/luks_gpg/<UUID>.key
    shred -u /etc/luks_gpg/<UUID>.key
    
  7. Add /etc/luks_gpg/decrypt.sh as decryption script.
    #!/bin/sh
    UUID=`basename $0`
    export GNUPGHOME=/etc/luks_gpg/
    gpg --no-tty --decrypt "/etc/luks_gpg/$UUID.key.gpg"
    
  8. Don’t forget to setup permission for it.
    chmod +x /etc/luks_gpg/decrypt.sh
    
  9. I applied a simple trick so that I could use different password for different partition easily, for each parition with different <UUID>, we could create a soft link /etc/luks_gpg/<UUID> to /etc/luks_gpg/decrypt.sh.
    cd /etc/luks_gpg
    ln -s decrypt.sh <UUID>
    
  10. Here is the trick. Update /etc/crypttab by appending the following to each LUKS partition with UUID of <UUID>.
    ,keyscript=/etc/luks_gpg/<UUID>
    
  11. Add /etc/initramfs-tools/hooks/luks_gpg to include necessary binaries as well as $GNUPGHOME in initramfs.
    #!/bin/sh
    set -e
    PREREQ="cryptroot"
    prereqs()
    {
        echo "$PREREQ"
    }
    case $1 in
    prereqs)
        prereqs
        exit 0
        ;;
    esac
    . /usr/share/initramfs-tools/hook-functions
    cp -a /etc/luks_gpg/ "${DESTDIR}/etc/"
    mkdir -p "${DESTDIR}/etc/terminfo/l/"
    cp -a /lib/terminfo/l/linux "${DESTDIR}/etc/terminfo/l/linux"
    copy_exec /usr/bin/gpg
    copy_exec /usr/bin/gpg-agent
    copy_exec /usr/bin/pinentry-curses
    copy_exec /usr/lib/gnupg/scdaemon
    exit 0
    
  12. Setup permissions for it.
    chown root:root /etc/initramfs-tools/hooks/luks_gpg
    chmod 750 /etc/initramfs-tools/hooks/luks_gpg
    
  13. Before updating the initramfs, you need to trigger a card-edit, or scdaemon would not be correctly triggered during boot.
    export GNUPGHOME=/etc/luks_gpg/
    gpg --card-edit
    
  14. Finally update the initramfs.
    update-initramfs -u
    

How to debug

update-initramfs -uv
lsinitramfs /boot/initrd.img-4.18.0-0.bpo.1-amd64

Links