Introduction
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.
Prerequisites
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:
-
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.
-
Add the key to the LUKS volume.
cryptsetup luksAddKey /dev/sdb1 /tmp/mykey
Replace
/dev/sdb1
with the path to your encrypted volume. -
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 usingpkcs15-tool --list-public-keys
. -
Encrypt the keyfile using the RSA public key.
openssl rsautl -inkey /tmp/publickey.pem -pubin -encrypt -pkcs -in /tmp/mykey -out /tmp/encryptedkey.pkcs1
-
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 fromstdin
(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
):
#!/bin/bash
LUKS_TARGET_DEV=/dev/sdb1
LUKS_CURRENTKEY_FILE=/tmp/mykey
RSA_PUBKEY_FILE=/tmp/publickey.pem
ENCRYPTED_KEYFILE=/tmp/encryptedkey.pkcs1
# 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
Background
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.
TODO
- Use of PKCS#1 v2.1 padding instead of v1.5.
At the time of writing both OpenSSL's
rsautl
and OpenSC'spkcs15-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.