Linux/dm-crypt
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
Setup
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 --type luks2 ${DEV}
Or, when using key files:[2][3][4]
head -c 256 /dev/random > keyfile cryptsetup luksFormat --type luks2 --key-file keyfile ${DEV}
Open the newly created container:
$ cryptsetup open ${DEV} test # Add --key-file as needed. Enter passphrase for /dev/sdx: $ cryptsetup status test /dev/mapper/test is active. type: LUKS2 cipher: aes-xts-plain64 keysize: 512 bits key location: keyring device: /dev/sdb1 sector size: 512 offset: 32768 sectors size: 468827312 sectors mode: read/write $ file -Ls ${DEV} /dev/mapper/test /dev/sdx: LUKS encrypted file, ver 2, header size 16384, ID 3, algo sha256, salt 0x8f7124c9553e10fc [...] /dev/mapper/test: data
FDE
For FDE setups, the name of the encrypted root disk can be changed[5]. In short:
Replace OLD_NAME
with NEW_NAME
in /etc/crypttab
, then:
dmsetup rename OLD_NAME NEW_NAME update-initramfs -c -t -k all update-grub reboot
mkinitrd --force /boot/initramfs-`uname -r`.img `uname -r` grub2-mkconfig -o /boot/efi/EFI/fedora/grub.cfg # UEFI systems grub2-mkconfig -o /boot/grub2/grub.cfg # BIOS systems
Key Management
Let's use a keyfile to encrypt our volume:
head -c 256 /dev/random > keyfile cryptsetup luksFormat --batch-mode --verbose --key-file keyfile /dev/vg0/lv1
Let's try to open the device:
$ cryptsetup open --verbose /dev/vg0/lv1 luks-lv1 Enter passphrase for /dev/vg0/lv1: No key available with this passphrase.
That....didn't work. Since we encrypted the volume with a keyfile, we really need to use the --key-file
option here:
$ cat keyfile | cryptsetup open --verbose --key-file - /dev/vg0/lv1 luks-lv1 Key slot 0 unlocked. Command successful.
We can add a second key to the same volume:
$ cryptsetup luksAddKey --key-file keyfile /dev/vg0/lv1 Enter new passphrase for key slot: Verify passphrase:
Now we can unlock the volume with a passphrase too - or we can use the --key-file
of course.
When we remove the keyfile we can only unlock the volume with the passphrase we just supplied:
$ cryptsetup luksRemoveKey --key-file keyfile /dev/vg0/lv1 $ cryptsetup open --verbose /dev/vg0/lv1 luks-lv1 Enter passphrase for /dev/vg0/lv1: Key slot 1 unlocked. Command successful.
Let's add the keyfile again, and remove the passphrase:
$ cryptsetup luksAddKey --new-keyfile keyfile /dev/vg0/lv1 $ cryptsetup luksDump /dev/vg0/lv1 | grep luks2 0: luks2 <== keyfile 1: luks2 <== passphrase $ cryptsetup luksKillSlot --verbose --key-file keyfile /dev/vg0/lv1 1 Keyslot 1 is selected for deletion. Key slot 0 unlocked. Key slot 1 removed. Command successful.
Token Management
We can use tokens too. For that to work, the key needs to be added to the device first, and then a token will describe how to fetch that key to unlock the device.
openssl rand -hex 32 > secret cryptsetup luksAddKey --key-file keyfile --new-keyfile secret /dev/vg0/lv1
Note: in this case, the secret
was added to keyslot 1. Now we can use cryptsetup-ssh
to specify how to get to that secret:
cryptsetup-ssh add --verbose --key-slot=1. --ssh-keypath=/home/luks/.ssh/id_ed25519 --ssh-path=secret --ssh-server=localhost --ssh-user=luks /dev/vg0/lv1
Let's have a look:
$ cryptsetup luksDump /dev/vg0/lv1 | grep -A6 Token
Tokens:
0: ssh
ssh_server: localhost
ssh_user: luks
ssh_path: secret
ssh_key_path: /home/luks/.ssh/id_ed25519
Keyslot: 1.
Now the volume can be unlocked w/o any interaction:
$ cryptsetup open --verbose /dev/vg0/lv1 luks-test SSH token initiating ssh session. Key slot 1 unlocked. Command successful.
Note: the SSH command will use sftp
to download that key. On the remote side we can restrict our user somewhat:
$ chsh -s /bin/sh luks # Needed for sftp to work...? $ head -1 ~luks/.ssh/authorized_keys restrict ssh-ed25519 AAAAC0d910909d2baf99d019ac096ba303d74cabfda2021f4bc6695b14f65f69 root@localhost
$ cat /etc/ssh/sshd_config [...] AllowUsers luks@127.0.0.1 Match User luks ChrootDirectory /home/luks/ ForceCommand internal-sftp
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/random 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
can be initialized:[7]
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/foo.txt
Let's see if we can find the string:
$ grep -Fa "hey, it worked" /dev/mapper/test | strings # Use --devices=read if necessary Hhey, it worked $ grep -Fa "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 i in {1..10}; do echo "i: ${i}"; cryptsetup benchmark; done 2>&1 | tee c.log $ grep -A5 Tests c.log | awk '!/^#/ {print $1}' | head -5 | while read h; do printf "hash: ${h} "; grep ${h} 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 c s; do printf "cipher: ${c} size: ${b} "; grep -E "${c}.*${s}" 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
- TrueCrypt
- RAID
- Archlinux: dm-crypt
- Gentoo: DM-Crypt LUKS
- Archlinux: System encryption using LUKS and GPG encrypted keys for arch linux
References
- ↑ dm-crypt benchmarks
- ↑ How to add a passphrase, key, or keyfile to an existing LUKS device
- ↑ dm-crypt/Device encryption: Creating a keyfile with random characters
- ↑ 2.12 What are the security requirements for a key read from file?
- ↑ How to change the name an encrypted full-system partition is mapped to
- ↑ Equivalent to update-grub on Fedora 30?
- ↑ Documentation/device-mapper/dm-crypt.txt