Introduction[edit]

This page contains instructions describing how to set-up Debian Jessie in order to protect the LUKS decryption key using GnuPG in combination with OpenPGP smart-card.

Be warned that this is a fairly involving and complicated process that can not only make your data inaccessible while setting it up, but also in case something goes wrong during system upgrades! You should feel very comfortable with both GnuPG and smart-cards, and have backups just in case. It is recommended to keep backup of disk decryption keys in one form or another, and to have handy procedures on how to recover system in case something fails after a package upgrade. If upgrading distro, I would in fact recommend switchin the system back to password encryption before actually upgrading to latest release.

The goal is to improve security of the encryptped disk by making it harder to brute-force the installation, and to ensure that you need a physical token before decryption takes place (some caveats to keep in mind are listed below).

The following is covered:

  • Basic installation of Debian Jessie with disk encryption.
  • Setting-up GnuPG keyring that will be used for decryption purposes.
  • Configuring and building initramdisk in order to make it possible to use GnuPG during boot process for decryption.
  • Configuring and building initramdisk in order to make it possible to use OpenPGP smart-card during boot process for decryption (in conjunction with GnuPG).
  • Setting-up a separate boot device (USB flash).

Installing the OS[edit]

When it comes down to Debian Jessie installation, you should be able to use standard procedure for installing it on top of encrypted partitions. This guide will assume the simplest possible case with three partitions (boot, root, and swap).

Actual installation will probably differ, but you might find this a good starting step when testing things out.

Install Debian Jessie using the following steps:

  1. Boot Debian installation CD.

  2. Select option Install.

  3. Set language to English.

  4. Set country to Sweden.

  5. Set keyboard layout to en_US.UTF-8.

  6. Set keymap to American English.

  7. Set hostname to encrypt.

  8. Set domain to local.

  9. Set password for the root account.

  10. Set full name for the user to My User.

  11. Set username for the user to user.

  12. Set password for the user account.

  13. Partition the disk:

    1. Set method to Manual.

    2. Wipe-out any available partitions and create a new partition table.

    3. Create partitions as follows:

      • 256MB, Primary, Beginning, Ext4, /boot/
      • 8GB, Primary, Beginning, physical volume for encryption
      • remaining space, Primary, physical volume for encryption
    4. Select option Configure encrypted volumes.

    5. Write changes to disk.

    6. Select Create encrypted volumes, select /dev/sda2.

    7. Select Create encrypted volumes, select /dev/sda3.

    8. Select Finish.

    9. Confirm operation.

    10. Wait for erase to finish.

    11. Provide encryption password for first partition. This password will be replaced at later stage.

    12. Provide encryption password for second partition. This password will be replaced at later stage.

    13. Set-up the two new encrypted partitions:

      • sda2_encrypt should be Ext4 with mount point /.
      • sda3_encrypt should be swap.
    14. Finally finish partitioning and write changes to disk.

  14. Select Debian archive mirror.

  15. Opt out of popularity contest.

  16. On software selection screen pick:

    • SSH server
    • standard system utilities
  17. When prompted, install GRUB boot loader to /dev/sda.

  18. Finally Reboot into the new system, and provide decryption passwords.

Setting-up GnuPG keyring[edit]

We will start off with setting-up a GnuPG keyring.

Perform the following steps:

  1. Log-in as root into the machine.

  2. Create directory where the keyring will be stored:

    mkdir /etc/luks_gnupg/
    chmod 700 /etc/luks_gnupg/
    
  3. Initialise the keyring and create GnuPG private key that will be used later on for encrypting the LUKS encryption key:

    gpg2 --homedir /etc/luks_gnupg/ --gen-key
    
  4. Pick RSA and RSA as key type.

  5. Set length to 2048 bits.

  6. Set expiration to 0.

  7. Confirm the key does not expire.

  8. Set real name to My User.

  9. Set email address to myuser@localhost.

  10. Set empty comment.

  11. Confirm identity.

  12. Provide password for GnuPG key.

  13. Install PIN entry application:

    apt-get install pinentry-curses
    
  14. Configure GnuPG agent to use pinentry-curses by creating file /etc/luks_gnupg/gpg-agent.conf:

    pinentry-program /usr/bin/pinentry-curses
    

Setting-up new decryption key for LUKS[edit]

During installation a password was provided for decrypting the partitions. What we want to do is to replace this password with a more complex symmetric key, that will in turn be encrypted using GnuPG private key.

Set-up new decryption key for encrypted partitions:

  1. Generate 256-bit random key:

    dd if=/dev/random bs=1 count=256 > /etc/luks_gnupg/disk.key
    
  2. Add the generated decryption key to first encrypted partition:

    cryptsetup luksAddKey /dev/sda2 /etc/luks_gnupg/disk.key
    
  3. Provide existing password.

  4. Add the generated decryption key to second encrypted partition:

    cryptsetup luksAddKey /dev/sda3 /etc/luks_gnupg/disk.key
    
  5. Provide existing password.

  6. Encrypt the decryption key using GnuPG private key:

    gpg2 --homedir /etc/luks_gnupg/ --recipient myuser@localhost --encrypt-files /etc/luks_gnupg/disk.key
    
  7. Shred and remove the the plaintext disk encryption key:

    shred -u /etc/luks_gnupg/disk.key
    

Setting-up decryption script[edit]

In order to use GnuPG, we will create a decryption script that will invoke GnuPG with correct parameters.

Perform the following steps:

  1. Create script for obtaining decryption key /usr/local/sbin/luks_gnupg_decrypt.sh:

    1
    2
    3
    4
    5
    6
    #!/bin/sh
    
    # This is the safest way to ensure the GnuPG home directory is correctly set.
    export GNUPGHOME=/etc/luks_gnupg/
    
    gpg2 --no-tty --decrypt /etc/luks_gnupg/disk.key.gpg
    
  2. Fix script permissions:

    chown root:root /usr/local/sbin/luks_gnupg_decrypt.sh
    chmod 750 /usr/local/sbin/luks_gnupg_decrypt.sh
    
  3. Test decryption of first partition:

    /usr/local/sbin/luks_gnupg_decrypt.sh | cryptsetup open --test-passphrase /dev/sda2 test
    
  4. Test decryption of second partition:

    /usr/local/sbin/luks_gnupg_decrypt.sh | cryptsetup open --test-passphrase /dev/sda3 test
    

Preparing initramdisk[edit]

With all pieces in place, it is time to prepare a new initramdisk that will contain GnuPG binaries, GnuPG keyring, and decryption scripts.

Perform the following steps:

  1. Back-up the existing initramdisk:

    cp -a /boot/initrd.img*-amd64 /boot/initrd.bak
    
  2. Update /etc/crypttab by appending the following to both lines:

    ,keyscript=/usr/local/sbin/luks_gnupg_decrypt.sh
    
  3. Create initramdisk hook that will make sure the necessary binaries, GnuPG keyring, and decryption script are included in resulting image. Create file /etc/initramfs-tools/hooks/luks_gnupg:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    #!/bin/sh
    
    set -e
    
    PREREQ="cryptroot"
    
    prereqs()
    {
            echo "$PREREQ"
    }
    
    case $1 in
    prereqs)
            prereqs
            exit 0
            ;;
    esac
    
    . /usr/share/initramfs-tools/hook-functions
    
    # Deploy the keyring.
    cp -a /etc/luks_gnupg/ "${DESTDIR}/etc/"
    
    # Deploy terminfo (required for pinentry-curses).
    mkdir -p "${DESTDIR}/etc/terminfo/l/"
    cp -a /lib/terminfo/l/linux "${DESTDIR}/etc/terminfo/l/linux"
    
    # Deploy GnuPG binaries and pinentry-curses.
    copy_exec /usr/bin/gpg2
    copy_exec /usr/bin/gpg-agent
    copy_exec /usr/bin/pinentry-curses
    
    exit 0
    
  4. Fix script permissions:

    chown root:root /etc/initramfs-tools/hooks/luks_gnupg
    chmod 750 /etc/initramfs-tools/hooks/luks_gnupg
    
  5. Update the initramdisk:

    update-initramfs -u -k all
    

Booting into the system[edit]

If you have not made any errors, at this point you should be able to boot into the new system, this time around using the decryption key protected by your GnuPG keyring.

Perform the following steps:

  1. Reboot the machine.

  2. Boot Debian from disk.

  3. You should be prompted to provide password for the GnuPG encryption key twice (for both partitions). Don't mistake this password for the initial disk encryption password, they are completely separate.

Setting-up OpenPGP smart-card[edit]

Once you have LUKS disk decryption working using GnuPG soft keys, you may want to move on onto setting-up an OpenPGP smart-card for this purpose. The main advantage of switching to such a schema is that in case someone gains access to your initrd image, they will be unable to perform brute force against the software key stored within. I.e. the security will be improved by storing the private key on a dedicated physical device which makes it hard to extract the credentials.

Perform the following steps:

  1. Log-in as root into the machine`.

  2. Install additional package that allows GnuPG to use smart-cards:

    apt-get install scdaemon
    
  3. Set-up configuration file for scdaemon that will redirect all log messages to /dev/null in order to reduce clutter. Create file /etc/luks_gnupg/scdaemon.conf:

    log-file /dev/null
    
  4. Insert smart-card into smart-card reader connected to the machine.

  5. Transfer the encryption private key to smart-card:

    1. Start command for manipulating the keys:

      gpg2 --homedir /etc/luks_gnupg/ --edit-key myuser@localhost
      
    2. Toggle to manipulation of secret key material:

      toggle
      
    3. Select the encryption sub-key:

      key 1
      
    4. Transfer the encryption sub-key to smart-card:

      keytocard
      
    5. When prompted where to store the key, type 2 (for (2) Encryption key entry).

    6. You will be prompted for the key password first. Provide it.

    7. Afterwards you will be prompted for the card admin password. Provide it.

    8. Wait for operation to finish.

    9. Save keyring changes and exit:

      save
      
  6. Verify that decryption now uses the OpenPGP smart-card:

    /usr/local/sbin/luks_gnupg_decrypt.sh | cryptsetup open --test-passphrase /dev/sda2 test
    

Updating initramdisk to handle OpenPGP smart-card[edit]

Once the encryption key has been moved to the OpenPGP smart-card, the initramdisk needs to be updated as well in order to include the necessary binaries.

Perform the following steps:

  1. Update file /etc/initramfs-tools/hooks/luks_gnupg to include the line copy_exec /usr/lib/gnupg2/scdaemon. The full file should look as follows:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    #!/bin/sh
    
    set -e
    
    PREREQ="cryptroot"
    
    prereqs()
    {
            echo "$PREREQ"
    }
    
    case $1 in
    prereqs)
            prereqs
            exit 0
            ;;
    esac
    
    . /usr/share/initramfs-tools/hook-functions
    
    # Deploy the keyring.
    cp -a /etc/luks_gnupg/ "${DESTDIR}/etc/"
    
    # Deploy terminfo (required for pinentry-curses).
    mkdir -p "${DESTDIR}/etc/terminfo/l/"
    cp -a /lib/terminfo/l/linux "${DESTDIR}/etc/terminfo/l/linux"
    
    # Deploy GnuPG binaries and pinentry-curses.
    copy_exec /usr/bin/gpg2
    copy_exec /usr/bin/gpg-agent
    copy_exec /usr/bin/pinentry-curses
    copy_exec /usr/lib/gnupg2/scdaemon
    
    exit 0
    
  2. Update the initramfs:

    update-initramfs -u -k all
    
  3. Reboot the system with smart-card still present in the smart-card reader. You should be prompted to provide the smart-card PIN twice (once per encrypted disk partition).

Removing old encryption password[edit]

With everything working, finally it is time to remove the old encryption password.

Perform the following steps:

  1. Log-in as root into the machine.

  2. Verify that decryption using the original password still works:

    cryptsetup open --test-passphrase /dev/sda2 test
    cryptsetup open --test-passphrase /dev/sda3 test
    
  3. Remove the old decryption password from root partition device:

    cryptsetup luksRemoveKey /dev/sda2
    
  4. Enter the old password when prompted.

  5. Remove the old decryption password from swap partition device:

    cryptsetup luksRemoveKey /dev/sda3
    
  6. Enter the old password when prompted.

  7. Verify that decryption using the original password does not work anymore:

    cryptsetup open --test-passphrase /dev/sda2 test
    cryptsetup open --test-passphrase /dev/sda3 test
    

Setting-up separate boot device[edit]

At this point you should be pretty well off when it comes down to securing the disk decryption keys. If you are using an OpenPGP card, you are already combining something that you have (smart-card) with something that you know (smart-card PIN) in order to access the data during boot.

However, one weak link in this process is the boot device (partition in this case). Since bootloader cannot read encrypted partitions on its own, it must rely on boot partition (the Linux kernel and initramdisk) to provide this capability. This means that the boot partition itself has to be kept unencrypted.

A possible attack may involve an adversary that is able to gain access to your hard-drive for a limited time - long enough to modify the kernel and/or initramdisk to introduce a malicious payload into the system (or even replace the bootloader). This payload could easily extract disk decryption key by wrapping around the GnuPG calls to output the file in raw form and submit it over network or some other way. After that it would be sufficient for the attacker to steal the disk and have unhindered and immediate access to all data.

Even with password set in BIOS (to prevent boots from arbitrary devices), adversary could quickly remove disk from your machine, update partition on another computer, and put the disk back in, without you even noticing. This can be particularly effective since if planned correctly you might not notice the change (for example if the machine was off at the point you left it).

As a side-note, there is also a proverbial 5-dollar wrench (see the famed XKCD comic).

There is a couple of ways that could help you thwart this kind of attack. One could involve using secure boot - although, do not take my word for it, since I have not had time to deal with it in detail (in particular, not sure if initramdisk/kernel stored on boot partition can also be verified for integrity during the boot). This kind of protection would also require that the distro you are using works well with secure-boot (has properly signed bootloader/kernel images etc).

An alternative would be to boot off of a USB device (flash drive) over which you have a full control, and which you never (ever) leave anywhere without attention (yes, I know, good luck with that...).

In this section we will cover how to set-up a separate USB boot device, replacing the boot device set-up during the OS installation. You will need a USB flash drive, so make sure you have one handy.

Perform the following steps in order to set-up a separate USB boot device:

  1. Insert the USB flash drive into machine.

  2. Log-in as root into the machine.

  3. Check if the USB flash drive is visible, and verify device path (should be /dev/sdb):

    lsblk
    
  4. Start a disk partitioner:

    fdisk /dev/sdb
    
  5. Create a new empty DOS partition table:

    Command (m for help): o
    
  6. Create a new partition:

    Command (m for help): n
    
  7. Press ENTER to create it as a primary partition.

  8. Press ENTER to create it as a first partition on disk.

  9. Press ENTER twice to allocate all space for this partition.

  10. Write the changes and exit from disk partitioner:

    Command (m for help): w
    
  11. Create an ext4 file-system on partition:

    mkfs.ext4 -L boot /dev/sdb1
    
  12. Create directory for mounting the new boot partition:

    mkdir /mnt/new_boot
    
  13. Mount the new boot partition:

    mount /dev/sdb1 /mnt/new_boot
    
  14. Copy content from the existing boot partition to the new partition:

    cp -a /boot/* /mnt/new_boot/
    
  15. Determine the UUID of the new boot partition:

    blkid /dev/sdb1
    
  16. Update the /boot entry in /etc/fstab, replacing the old boot partition UUID with the new one.

  17. Unmount both boot partitions:

    umount /mnt/new_boot/
    umount /boot/
    
  18. Remount the boot partition:

    mount /boot/
    
  19. Check if correct partition got mounted (should be /dev/sdb1):

    mount
    
  20. Reconfigure bootloader to use the USB flash drive:

    dpkg-reconfigure grub-pc
    
  21. When prompted for Linux command line and Linux default command line, just press ENTER to accept existing values.

  22. When prompted for devices where bootloader should be installed, select /dev/sdb, and make sure /dev/sda is deselected.

  23. Reboot the system, and boot from the flash drive. If all is good, the boot process should finish successfully.

  24. Log-in as root into the machine.

  25. Wipe the old boot partition:

    dd if=/dev/urandom of=/dev/sda1 bs=16M
    
  26. Wipe the initramdisk backup:

    shred -u /boot/initrd.bak
    
  27. Reboot once again, boot from the disk (not USB flash drive). Verify the boot process fails (unknown filesystem type should be reported by bootloader).

  28. Reboot again, boot from USB flash drive to verify the boot process works.

Using existing keyring[edit]

Previous sections all assume you do not have any GnuPG keyring of your own - mainly in order to make testing easier. In real situation you probably already have the GnuPG keyring already set-up (probably together with the OpenPGP card).

Let's have a look what you need to do to create a minimal keyring for LUKS. Instructions will assume that the keyring is available in standard location in home directory, and that you are running them as regular user.

If you happen to be using the keyring from previous sections for testing, just make sure you don't shoot yourself in the foot by wiping out the disk decryption key. Been there, done that ;)

Perform the following steps:

  1. Insert the OpenPGP smart-card into the reader.

  2. Identify secret key stored on the smart-card (the short identifier):

    gpg2 --list-secret-keys
    
  3. Export the encryption secret sub-key. Provided you have moved the encryption sub-key to smart-card, this will result in stub file only, not the actual key. Make sure to supply your own short key ID, and to preserve the exclamation sign at end (this will instruct GnuPG to export stub for only this specific sub-key):

    gpg2 --export-secret-subkeys --armour 'KEYID!' > ~/enckey.asc
    
  4. Create directory for storing the minimal keyring:

    mkdir ~/luks_gnupg/
    chmod 700 ~/luks_gnupg
    
  5. Import the encryption sub-key stub into the new keyring:

    gpg2 --homedir ~/luks_gnupg/ --import ~/enckey.asc
    
  6. Verify that the key is available in new keyring:

    gpg2 --homedir ~/luks_gnupg/ --list-secret-keys
    
  7. Set-up GnuPG agent to use pinentry-curses:

    echo "pinentry-program /usr/bin/pinentry-curses" > ~/luks_gnupg/gpg-agent.conf
    
  8. Redirect all logging for scdaemon to /dev/null:

    echo "log-file /dev/null" > ~/luks_gnupg/scdaemon.conf
    
  9. Switch to root user.

  10. Move the newly-created keyring to destination directory, and set-up its permissions:

    mv /home/user/luks_gnupg/ /etc/
    chown -R root:root /etc/luks_gnupg/
    
  11. The rest of instructions should still apply, simply skip things like generating new GnuPG key or transferring the keys to smart-card.

Troubleshooting[edit]

How do I boot using backup initramdisk?[edit]

If you happen to run into issues while booting the system, and you still have both password set as decryption key on the disk, and the backed-up initramdisk, you should be able to easily boot by modifying the boot entry. Perform the following steps:

  1. When you reach the bootloader, move the cursor to Debian GNU/Linux entry

  2. Press e.

  3. Navigate to the initrd line, and set the filename to initrd.bak.

  4. Press Ctrl-x to boot using the backup initramdisk.

How do I debug initramdisk if something is wrong?[edit]

If you ever need to figure out what is happening within the initramdisk (more specifically why mounting is failing), you can easily drop into middle of the boot process by passing in additional boot parameters.

Perform the following steps:

  1. When you reach the bootloader, move the cursor to Debian GNU/Linux entry

  2. Press e.

  3. Navigate to the linux line, and append break=premount to it.

  4. Press Ctrl-x to boot the system.

  5. You should be dropped to shell, just prior to initramdisk mounting the disk. From within this environment you should be able to figure out why the decryption commands etc do not work. Some example commands you might find useful:

    # Try to decrypt the key manually.
    GNUPGHOME=/etc/luks_gnupg gpg2 --decrypt /etc/luks_gnupg/disk.key.gpg
    
    # Try to run decryption script.
    /lib/cryptsetup/scripts/luks_gnupg_decrypt.sh
    
  6. Depending on errors you get, you could try to figure out what is happening. Perhaps a binary is missing, or some dependency? Maybe you have a typo somewhere in the configuration? A good way to figure out missing files etc is to set-up initramdisk with strace in addition to binaries listed above. This way you can figure out what files are being accessed during the boot.