Linux/dm-crypt

From Segfault
Jump to: navigation, search

The device mapper can be used with different targets, in this case dm-crypt. Apart from taking the usual precautions when dealing with cryptography, we should decide for a fast, yet secure combination of cipher, keysize, hash algorithm.[1]

Preparation

We may want to remove any pre-existing signatures from the device:

DEV=/dev/sdX                                                # Adjust as needed!

wipefs -a $DEV                                              # Wipe any pre-existing file system signatures
pv < /dev/zero > $DEV                                       # Wipe the device with zeros
shred -vn1 $DEV                                             # Wipe the device with random data

LUKS

cryptsetup is really the way to go to use dm-crypt devices and its setup is more intuitive that plain dm-crypt.

Setup the LUKS container (this has to be done only once):

cryptsetup luksFormat --cipher=aes-xts-plain64 --hash sha256 --key-size 512 --iter-time=5000 $DEV

Open the newly created container:

$ cryptsetup open $DEV test
Enter passphrase for /dev/sdx: 

$ cryptsetup status test
/dev/mapper/test is active.
  type:    LUKS1
  cipher:  aes-xts-plain64
  keysize: 512 bits
  device:  /dev/sdx
  offset:  4096 sectors
  size:    20967424 sectors
  mode:    read/write

$ file -Ls $DEV /dev/mapper/test
/dev/sdx: LUKS encrypted file, ver 1 [aes, xts-plain64, sha256] UUID: 235142f6-6d99-457e-a5b6-ddaed00a63ea
/dev/mapper/test: data

GPG keys

It's possible to specify the key to cryptsetup via stdin and we could use GPG encrypted key material instead of pass phrases.

FIXME: Find out about the security implications here! Also explain the relation between the GPG key size and the --key-size argument from cryptsetup!

Generate random key material and encrypt it with gpg:

openssl rand -hex 4096 | \
   gpg --armor --symmetric --cipher-algo aes256 --digest-algo sha512 > key.asc

Without openssl:

dd if=/dev/urandom bs=1024 count=6 2>/dev/null | base64 | \
   gpg --armor --symmetric --cipher-algo aes256 --digest-algo sha512 > key.asc

Initialize the LUKS partition (once):

DEV=/dev/sdX                                                # Adjust as needed!
gpg --decrypt key.asc | \
       cryptsetup luksFormat --cipher=aes-xts-plain64 --hash sha256 --key-size 512 --iter-time=5000 $DEV 

And open it:

gpg --decrypt key.asc | cryptsetup open $DEV test

Now the new mapping device should be active:

$ cryptsetup status test
/dev/mapper/test is active.
  type:    LUKS1
  cipher:  aes-xts-plain64
  keysize: 512 bits
  device:  /dev/sdx
  offset:  4096 sectors
  size:    20967424 sectors
  mode:    read/write

Plain

While we could setup the dm-crypt volume manually, one should really use cryptsetup instead. But just for posterity, this is how dm-crypt expects to be initialized:

0 <sector count> crypt <sector format> <key> <IV offset> <real device> <sector offset> 
  • The sector count can be determined with blockdev
  • The sector format is the name of the (symmetric) encryption cipher and an optional IV
  • Hexadecimal representation of the encryption key; its size is bound to the selected cipher
  • The IV offset is usually just "0".

Let's do this:

DEV=/dev/sdX                                                # Adjust as needed!
KEY=`openssl rand -hex 64`
echo 0 `blockdev --getsize $DEV` crypt aes-xts-plain64 $KEY 0 $DEV 0 | dmsetup create test

And this worked:

$ dmsetup info test
Name:              test
State:             ACTIVE
Read Ahead:        256
Tables present:    LIVE
Open count:        0
Event number:      0
Major, minor:      254, 0
Number of targets: 1

Test

A quick(!) test would be to write to the encrypted volume and search for the string on the block device:

mkfs.ext4 /dev/mapper/test
mount -t ext4 /dev/mapper/test /mnt/disk
echo  "hey, it worked" > /mnt/disk/bar.txt

Let's see if we can find the string:

$ fgrep -a "hey, it worked" /dev/mapper/test | strings      # Use --devices=read if necessary
Hhey, it worked

$ fgrep  "hey, it worked" $DEV
[should not return anything]

Backup

It's important to backup the LUKS header once in a while:

cryptsetup luksHeaderBackup  $DEV --header-backup-file header_sdX.backup

With that, the LUKS header can be restored should it ever get corrupted:

cryptsetup luksHeaderRestore $DEV --header-backup-file header_sdX.backup

Benchmark

Every system is different, so let's use a quick benchmark to find out which cipher/hash combination is best (fastest) for a particular machine:

$ for a in {1..10}; do echo "a: $a"; cryptsetup benchmark; done 2>&1 | tee c.log

$ grep -A5 Tests c.log | awk '!/^#/ {print $1}' | head -5 | while read a; do printf "hash: $a  "; grep $a c.log | awk '{sum=+$2} END {print sum}'; done | sort -nk3
hash: PBKDF2-whirlpool   10369
hash: PBKDF2-sha512      21700
hash: PBKDF2-sha256      99296
hash: PBKDF2-ripemd160  137970
hash: PBKDF2-sha1       152409                 ***

$ grep -A12 Algorithm c.log | head -13 | awk '!/^#/ {print $1, $2}' | while read a b; do printf "cipher: $a  size: $b  "; egrep "$a.*$b" c.log | awk '{enc+=$3; dec+=$5} END {print enc,dec}'; done  | sort -nk5,6
cipher: serpent-cbc  size: 128b  0 0
cipher: serpent-cbc  size: 256b  0 0
cipher: serpent-xts  size: 256b  0 0
cipher: serpent-xts  size: 512b  0 0
cipher: twofish-cbc  size: 128b  0 0
cipher: twofish-cbc  size: 256b  0 0
cipher: twofish-xts  size: 256b  0 0
cipher: twofish-xts  size: 512b  0 0
cipher: aes-cbc      size: 256b  216   229.2
cipher: aes-xts      size: 512b  228.7 224.1
cipher: aes-cbc      size: 128b  268.4 296.2
cipher: aes-xts      size: 256b  287.6 287     ***

As a short comparison, here's what reading from an external (USB) disk looks like:

$ pv -Ss 2G < /dev/sda > /dev/null
2GiB 0:01:00 [33.6MiB/s]

$ cryptsetup status test
/dev/mapper/test is active and is in use.
  type:    LUKS1
  cipher:  aes-cbc-essiv:sha256
  keysize: 256 bits
  device:  /dev/sda1
  offset:  2056 sectors
  size:    1953519608 sectors
  mode:    read/write

$ pv -Ss 2G < /dev/mapper/test > /dev/null
2GiB 0:01:26 [23.8MiB/s]

Links

References