Encrypting whole volumes is usually done using LUKS volumes, managed using cryptsetup. The LUKS format allow one to use several key slots, i.e. allowing multiple passphrases and/or keyfiles to unlock the volume. Out of the box, it does not allow you to use a security device like a smart card or token to store the secret. In this article I'll show you how enable a smart card or token device.


For this approach to work, I assume you have...

  • your smart card or token working with OpenSC
  • initialized it and have at least one RSA keypair installed

I.e. pkcs15-tool --list-keys shows list your keys installed.

I've tested this on Ubuntu 12.04 and 14.04 using a Feitian ePass2003, but it should work with any OpenSC-compatible device.

Using an encrypted keyfile

LUKS can't talk to your token directly and only knows about static key files, so here's the basic principle of using an encrypted keyfile. Create a fully random keyfile, add that to a LUKS keyslot and encrypt the keyfile using your RSA public key. To unlock the volume, one can then decrypt the keyfile using the token providing the decrypted keyfile to cryptsetup.

Here's how to do that in practice:

  1. Creating a random keyfile can be accomplished by using dd and the /dev/urandom random source. The size of the key is chosen to be 245 bytes here. Why 245 bytes? See the section Key size of 245 bytes below.

    dd if=/dev/urandom of=/tmp/mykey bs=1 count=245

    Note that this approach temporarily stores the encryption key in plaintext on your system. To avoid this, use the script as mentioned below.

  2. Add the key to the LUKS volume.

    cryptsetup luksAddKey /dev/sdb1 /tmp/mykey

    Replace /dev/sdb1 with the path to your encrypted volume.

  3. Obtain the public key of the keypair on your token, store it to a file.

    pkcs15-tool --read-public-key 1234 > /tmp/publickey.pem

    Replace the 1234 with your key ID as listed using pkcs15-tool --list-public-keys.

  4. Encrypt the keyfile using the RSA public key.

    openssl rsautl -inkey /tmp/publickey.pem -pubin -encrypt -pkcs -in /tmp/mykey -out /tmp/encryptedkey.pkcs1
  5. Remove the plaintext key.

    shred /tmp/mykey

Then test the ability to unlock the volume by decrypting the encrypted encryption key:

pkcs15-crypt --decipher --input /tmp/encryptedkey.pkcs1 --pkcs1 --raw | cryptsetup --key-file=- luksOpen /dev/sdb1

Some more explanation on the command above:

  • combination of --pkcs1 and --raw ensures 'raw' output of an PKCS1 padded encrypted file.
  • --key-file=- will have cryptsetup read the keyfile from stdin (output of the command left of the pipe).

Enhance security: avoid temporary key storage

By using a small script to generate the temporary key and feeding that to both OpenSSL and cryptsetup is more secure, because it avoids saving the key file to disk. The trick is to use tee and some subshells to copy stdout. In order to use the script without interaction this does require you to set up a temporary static key which will be replaced by this script.

dd if=/dev/urandom of=/tmp/mykey bs=1 count=256
cryptsetup luksAddKey /dev/sdb1 /tmp/mykey

Then execute this script (save it to a file, chmod +x file, ./file):



# generate random key (unique, plaintext)
# 245 bytes is based on 2048 bits RSA key minus 11 bytes PKCS1 padding
dd if=/dev/urandom of=/dev/stdout bs=1 count=245 | \
  # Copy stdout pipe to multiple subshells, avoiding the need to save the \
  # secret key to a temporary file. \
  tee \
    >( \
      # encrypt the key using the public key \
       openssl rsautl -inkey ${RSA_PUBKEY_FILE} -pubin -encrypt -pkcs -out ${ENCRYPTED_KEYFILE} \
    ) \
     >( \
       # add the key to the LUKS container \
       cryptsetup --key-file=${LUKS_CURRENTKEY_FILE} luksChangeKey ${LUKS_TARGET_DEV} /dev/stdin \
     ) \
  > /dev/null


Key size of 245 bytes

An RSA key of 2048 bits (256 bytes) should in theory allow you to use a message of up to the same size. However, by using a PKCS1 padding (which uses a minimum of 11 bytes for padding) the maximum message to encrypt/decrypt with the RSA key is 256 - 11 = 245 bytes.


  • Use of PKCS#1 v2.1 padding instead of v1.5. At the time of writing both OpenSSL's rsautl and OpenSC's pkcs15-crypt don't support the use of v2.1 on the command line.
  • Explain a bit more on the PKCS#1 format, regarding padding.

More later...

In a later post I'll show how this applies to full disk encryption (root filesystem); unlocking the device from the initramfs during boot.

Share on: TwitterHacker NewsFacebookLinkedInRedditEmail


comments powered by Disqus

Related Posts





Connect with me on...