Dm-crypt full disk encryption

From Gentoo Wiki
(Redirected from DM-Crypt LUKS)
Jump to: navigation, search
Resources

This article discusses several aspects of using Dm-crypt for (full) disk encryption. It is primarily meant as a source of supplementary information that is not already discussed on the Dm-crypt, genkernel, or initramfs wiki pages.

Important
All additional security strategies discussed in this article are optional. Some aspects presented here are solely discussed because they are routinely subject of questions, or highly personal security believes. The aim of this article is thus to provide a, hopefully, objective discussion. As additional source of information the cryptsetup FAQ is a highly recommended read.

Which cipher:hash combination?

The default cipher for LUKS is nowadays aes-xts-plain64, i.e. AES as cipher and XTS as mode of operation. This should be changed only under very rare circumstances. The default is a very reasonable choice security wise and by far the best choice performance wise that can deliver between 2-3 GiB/s encryption/decryption speed on CPUs with AES-NI. XTS uses two AES keys, hence possible key sizes are -s 256 and -s 512.

The default choice of cipher and key size can be overriden by the command line parameters -c and -s, for example

root #cryptsetup luksFormat -c aes-xts-plain64 -s 512 ...
root #cryptsetup luksFormat -c aes-cbc-essiv:sha256 -s 256 ...

If you want to know more about the performance on a given set of cipher and mode of operation you can run cryptsetup -c ... -s ... benchmark. Issuing the command without -c and -s runs the benchmark for a number of different choices. For example, a (slightly shortened) output for a mid-2014 Intel Core i7 CPU might look like:

root #cryptsetup benchmark
# Tests are approximate using memory only (no storage IO).
#  Algorithm | Key |  Encryption |  Decryption
     aes-cbc   256b   469.7 MiB/s  2093.2 MiB/s
 serpent-cbc   256b    85.3 MiB/s   523.3 MiB/s
 twofish-cbc   256b   173.4 MiB/s   340.2 MiB/s
     aes-xts   256b  2386.7 MiB/s  2387.4 MiB/s
 serpent-xts   256b   548.6 MiB/s   531.1 MiB/s
 twofish-xts   256b   320.0 MiB/s   342.3 MiB/s
Warning
dm-crypt supports many different combinations of ciphers, modes of operation, and IV modes [1]. Some of these are not as secure as others, some are considered to be highly insecure (for example ECB). You can read more about cryptographic ciphers and modes of operation elsewhere. If in doubt, stick with the default parameters.
Note
Additional information on how to use cryptsetup luksFormat is given on the dm-crypt wiki page.
Note
Some cipher choices have an additional hash specification of the form cipher:hash, for example aes-cbc-essiv:sha256. Such a hash specification is used by some mode of operations (like cbc-essiv) for IV generation. Mode of operations with plain64, however, do not use such a supplied hash specification (for generating an IV). In this case the hash specification should be omitted.

What choice of hash for key derivation?

LUKS uses PBKDF2 for key derivation. In essence, the supplied passphrase by the user is combined with a salt and hashed a specified number of rounds. This key stretching makes the password more secure against brute force attacks. The hash function used in PBKDF2 can be set via -h. The default is sha256 and can (depending on taste) be changed to another secure hash algorithm. The total number of iterations is determined by the speed of the current hardware and can be influenced by setting the number of milliseconds that shall be spent in PBKDF2 passphrase processing by --iter-time. To increase the default from 2s to 3s and use sha512 one could for example use

root #cryptsetup luksFormat ... -h sha512 --iter-time 3000 ...
Warning
It is not recommended to lower the number of PBKDF2 iterations below the default value [2].

On passphrases, detached LUKS headers, and (encrypted) keyfiles

A reasonably long passphrase (use, e.g., 8-12 common random words, see xkcd on that subject) in combination with PBKDF2 for key stretching can be considered a reasonably secure setup.

Additional protection against brute force attacks can be achieved by setting up a an external USB flash drive to store essential decryption information (like a keyfile, or the LUKS header itself). The flash drive then has the equivalent function of a physical key; opening the encrypted partition is only possible if both, flash drive and passphrase, are provided. However, this comes with a significant downside in terms of complexity, for example for setting up full disk encryption, or potential to loose decryption keys (by loosing the USB flash drive).

Detached LUKS header

It is possible to encrypt a partition with detached LUKS header (where all information about password derivation is stored) that is stored at (physically) different location, e.g., a USB flash drive. This leaves an attacker that is not in possession of the flash drive with no information about key derivation and encryption algorithms used. This makes brute force attacks potentially more difficult.

The following commands first create a file luks-header with a fixed size of 5MB. Then, a detached LUKS header is written to the file. (See dm-crypt for further information on how to use cryptsetup luksFormat, or cryptsetup open.)

root #truncate -s 5M /path/to/luks-header
root #cryptsetup luksFormat ... --header /path/to/luks-header ...

To check that the header was written successfully, run cryptsetup luksDump /path/to/luks-header. In order to open the encrypted device,

root #cryptsetup open ... --header /path/to/luks-header
Note
A detached LUKS header is currently not supported by genkernel.

Generating a GnuPG encrypted keyfile

The more traditional approach to use a USB flash drive is to store a GnuPG encrypted keyfile (that contains sufficient entropy) on it. Such a key file is readily supported by genkernel.

The following commands create a GnuPG encrypted keyfile of 512 bits and set up an encrypted partition with it:

root #dd if=/dev/urandom count=64 | gpg --symmetric --cipher-algo aes --armor > /path/to/key.gpg
root #gpg --decrypt /path/to/key.gpg | cryptsetup luksFormat ...

In order to open the encrypted device,

root #gpg --decrypt /path/to/key.gpg | cryptsetup open ...
Note
dm-crypt, of course, also works with clear, non-encrypted keyfiles, see the dm-crypt wiki page. The usage of clear keyfiles stored on the root partition to "chain open" encrypted partitions is discussed further down below.

Preparing disks

Warning
Extra precautions must be taken if the strategy outlined in this section is applied to an SSD.

It is sometimes recommended to overwrite the whole disk with random numbers prior to setting up disk encryption. The rationale behind this recommendation is that reading an encrypted disk should leak as little information as possible. But if unused blocks (by the file system) are still all zero, an adversary can recover some high level information by determining which (encryption) blocks are likely used (because they contain random looking data) and which are unused (because they are, for example, all zero). The number and location of likely used encryption blocks can reveal information such as disk usage, the file system in use, or likely average file size.

Thus, a somewhat effective (but time consuming) counter measure is to overwrite the disk with random data before using it. An efficient way to do generate large amounts of random data quickly is to use a cryptsetup mapping. For example, in order to overwrite /dev/sdXX, use

root #cryptsetup open --type=plain --key-file=/dev/urandom /dev/sdXX delete_sdXX
root #dd if=/dev/zero of=/dev/mapper/delete_sdXX bs=1M status=progress
root #cryptsetup close delete_sdXX

After that, format the disk with cryptsetup luksFormat ...

Warning
Overwriting a whole SSD with random data will mark every block as used and can, depending on manufacturer and model, severely degrade wear levelling. This might have potentially disastrous effects on disk lifetime. As mitigation, it is sometimes recommended to leave a substantial portion of the disk, around 10 - 20%, unused to have enough empty blocks for wear levelling. This can be done by creating a suitably sized, unformatted partition, and properly discarding it with blkdiscard. Please note that mounting a LUKS container with the --allow-discards option will transparently forward discards to the SSD and would contradict above setup.

Dm-crypt on SSDs and hybrid drives

For additional information about security aspects of using dm-crypt on SSDs and hybrid drives, have a look at the cryptsetup FAQ.

Cryptsetup can transparently forward discard operations to an SSD. This feature is activated by using the --allow-discards option in combination with cryptsetup open. Enabling discards on an encrypted SSD can be a measure to ensure effective wear levelling and longevity, especially if the full disk is encrypted. For an in detail discussion about the security implications, have a look at the cryptsetup FAQ and the man page of cryptsetup.

Generating an initramfs

After encrypting system or disk(s), one will need an initramfs so that rootfs can be mounted in there and then pass the control to real init. There are a few generic initramfs builder that can be used to accomplish the task such as dracut, mkinitcpio (there's an ebuild ad thread in the forums) sys-kernel/mkinitramfs-ll or even sys-kernel/genkernel[cryptsetup] (or the next variant) which has LUKS support.

Genkernel/Genkernel-next

To use sys-kernel/genkernel or sys-kernel/genkernel-next, edit /etc/portage/package.use to enable the cryptsetup USE flag before emerging it. The static USE flag may also be enabled on the sys-fs/cryptsetup so that genkernel will use the system binaries (otherwise it will build its own private copy). The following example will build only an initramfs (not an entire kernel) and enable support for luks. Even if you don't use lvm, `--lvm' option is required to include necessary udev rules.

root #genkernel --luks --lvm initramfs

The genkernel manpage outlines other options depending on system requirements.

The initrd will require parameters to tell it how to decrypt the drive, and they are supplied the same way as other kernel parameters. For example:

FILE grub.conf
title Gentoo Linux 3.4.0-gentoo
root (hd0,0)
kernel /boot/kernel-3.4.0-gentoo crypt_root=UUID=<encrypted partition uuid> root=/dev/mapper/root
initrd /boot/initramfs-3.4.0-gentoo
Note
Note TRIM support may be enabled if the encrypted device is capable (ie. SSD) with root_trim=yes but please read first about the security implications of this outlined in the --allow-discards section of the cryptsetup wiki.

Further information can be found in the Genkernel Manual.

Dracut

The sys-kernel/dracut package was ported from the RedHat project and serves a similar tool for generating an initramfs. Since it is currently in ~arch for testing, users will need to edit /etc/portage/package.accept_keywords to emerge it. Before doing so, the variable DRACUT_MODULES="crypt" should be added to /etc/portage/make.conf. Other modules may be desired, please refer to Dracut. Generally, the following command will generate a usable default initramfs.

root #dracut -a crypt

The initrd will require parameters to tell it how to decrypt the drive, and they are supplied the same way as other kernel parameters. For example:

FILE grub.conf
title Gentoo Linux 3.4.0-gentoo
root (hd0,0)
kernel /boot/kernel-3.4.0-gentoo root=UUID=<root filesystem uuid> rd.luks.uuid=<encrypted partition uuid>
initrd /boot/initramfs-3.4.0-gentoo.img
Note
Dracut will match UUIDs that are not complete as long as they are not ambiguous, so it is possible to only enter the first stanza for brevity/clarity desired
Note
Note TRIM support may be enabled if the encrypted device is capable (ie. SSD) with rd.luks.allow-discards but please read first about the security implications of this outlined in the --allow-discards section of the cryptsetup wiki.

For a comprehensive list of luks options within dracut please see the section in the Dracut Manual.

Mkinitramfs-LL

The unofficial sys-kernel/mkinitramfs-ll (found in tokiclover's bar-overlay) is a lightweight and modular variant of the previous well known initramfs generating tools which comes with udev free dependency. It depends only on busybox with mdev by default and depends on extra packages for additional functionalities. So, nor bash, coreutils nor util-linux is bundled into the initramfs. Extra flexibilities are offered as well, like the possibility to have DM-Crypt LUKS on top of LVM or vice versa, btrfs or ZFS on top of DM-Crypt LUKS, DM-Crypt LUKS on top of RAID, detached header (to a device or a file) for dm-crypt LUKS et al.

root #mkinitramfs-ll --luks --lvm --firmware=iwlwifi-5000

Use the following GRUB2 configuration excerpt to get going for LVM/LUKS and regular key file on a removable device setup.

FILE /etc/default/grub
...
GRUB_CMDLINE_LINUX="root=<VG-LV> luks=reg:LABEL=PENDRIVE:/key.reg lvm=<MAPPING>-UUID=<uuid>"
...
Note

There is an optional environment variable in the configuration file (mkinitramfs-ll.conf) to set environment variables that have the same effect as the the kernel command line arguments that let boot with a kernel cmdline free kernel. This default cmdline (as environment variables), however, can be disabled at run time if necessary by appending env=No cmdline argument.

Custom

Now, with a complicated setup one could have to build his/her own to satisfy specific requirements. This where modular or advanced initramfs package shine such as mkinitramfs-ll dracut or mkinitcpio.

The following is a simple script that will build an initramfs with LUKS support for the running kernel or for the kernel version passed as argument 1. Be careful as this will build the initramfs image and copy it into /boot so you need to have that mounted (and if you have an existing initramfs for this kernel, it might get overwritten).

FILE mkinitramfs.bash
#!/bin/bash
kv=${1:-$(uname -r)}
arc=$(getconf LONG_BIT)

mkdir -p initramfs-$kv && pushd initramfs-$kv
mkdir -p {usr/,}{{,s}bin,lib$arc} dev proc sys newroot mnt/tok
ln -s lib{$arc,}
pushd usr && ln -s lib{$arc,} && popd
mkdir -p usr/share/{consolefonts,keymap}

mknod -m 600 dev/console c 5 1
mknod -m 666 dev/urandom c 1 9
mknod -m 666 dev/random  c 1 8
mknod -m 640 dev/mem     c 1 1
mknod -m 666 dev/null    c 1 3
mknod -m 666 dev/tty     c 5 0
mknod -m 666 dev/zero    c 1 5
mknod -m 640 dev/tty1    c 4 1

[[ $(echo "$kv" | cut -d'.' -f1 ) -eq 3 ]] &&
    [[ $(echo "$kv" | cut -d'.' -f2) -ge 1 ]] &&
    mknod -m 600 dev/loop-control c 10 237

cp $(which busybox) bin/busybox || exit
cp ../init . && chmod 755 init || exit

# this is handy for password
keymap="${2:-$(sed -nre 's,^keymap="([a-zA-Z].*)",\1,p' /etc/conf.d/keymaps)}"
[[ $keymap ]] && loadkeys -b -u $keymap >usr/share/keymaps/${keymap}-$(uname -m).bin

consolefont="${3:-$(sed -nre 's,^consolefont="([a-zA-Z].*)",\1,p' /etc/conf.d/consolefont)}"
[[ $consolefont ]] && pushd usr/share/consolefonts &&
cp /usr/share/consolefonts/${consolefont}*.gz . &&
gzip -d ${consolefont}*.gz && popd

cp $(which cryptsetup) sbin || exit
cp $(which lvm.static) sbin/lvm && lvm=lvm

function docp()
{
	local link=${1} prefix
	[[ -n ${link} ]] || return
	cp -a {,.}${link}

	[[ -h ${link} ]] &&
	while true; do
	    prefix=${link%/*}
		link=$(readlink ${link})
		[[ ${link%/*} == ${link} ]] && link=${prefix}/${link}
		cp -a {,.}${link} || die
		[[ -h ${link} ]] || break
	done

	return 0
}

for bin in busybox cryptsetup $lvm; do
    ldd $bin >/dev/null || continue
    for lib in $(ldd $bin  | sed -nre 's,.* (/.*lib.*/.*.so.*) .*,\1,p' \      
        -e 's,.*(/lib.*/ld.*.so.*) .*,\1,p')
    do
        mkdir -p ${lib%/*} && docp {,.}$lib
    done
done

mount /boot
find . -print0 | cpio --null -ov --format=newc | xz -9 --check=crc32 > /boot/initramfs-$kv.cpio.xz

popd
unset -v arc consolefont kv keymap

And the following is a simple init script which will mount rootfs in initramfs environment. The following script and the previous are adapted from mkinitramfs-ll.

FILE init
#!/bin/sh
export PATH=/bin:/sbin:/usr/bin:/usr/sbin
export init=/sbin/init newroot=/newroot sh=/bin/sh

rescueshell() {
    export PS1='rsh:$(tty | cut -c6-):$PWD # '
    if which setsid &>/dev/null; then setsid $sh -i 0<$console 1>$console 2>&1
    else $sh -i 0<$console 1>$console 2>&1; fi
}

die() {
    echo -ne "Dropping into a rescueshell..."
    echo -ne "$@"
    rescueshell || exec $sh -i
}

getdev() {
    echo $(blkid | grep $1 | cut -d: -f1)
}

for arg in $(cat /proc/cmdline); do                                             
    case $arg in                                                                
        rescue*) export rescue=1;;                                            
        ro|rw) export mtopt=$arg;;                        
        single) export level=2;;
        console*|font*|init*|keyfile*|keymap*|lvm*|root*) export $arg;;                                           
     esac                                                                       
done

[ $keymap ] && loadkmap </usr/share/keymaps/$keymap
[ $font ] && loadfont </usr/share/consolefont/$font

umask 0077
mount -t proc proc /proc
mount -t sysfs sysfs /sys
if grep devtmpfs /proc/filesystems &>/dev/null; then
    mount -t devtmpfs devtmpfs /dev
else mount -t tmpfs tmpfs /dev; fi
mdev -s
echo /sbin/mdev > /proc/sys/kernel/hotplug

[ -h /dev/fd     ] || ln -fs /proc/self/fd   /dev/fd
[ -n /dev/stderr ] || ln -fs /proc/self/fd/2 /dev/stderr
[ -n /dev/stdin  ] || ln -fs /proc/self/fd/0 /dev/stdin
[ -n /dev/stdout ] || ln -fs /proc/self/fd/1 /dev/stdout
:   ${console:=/dev/console}
exec 0<$console 1>$console 2>&1

if [ $keyfile ]; then
    dev=$(echo $keyfile | cut -d: -f1)
    key=$(echo $keyfile | cut -d: -f2)
    dev=$(getdev $dev)
    mount $dev /mnt/tok || die
fi

root=$(getdev $root)
[ $root ] || die
cryptsetup open $root root ${key:+-d $key} && root=/dev/mapper/root || die

if [ $lvm ]; then
    # lvm cmdline argument is passed as <vg-lv>, standard way
    lvm vgscan ${lvm%-*}
    lvm vgchange -ay ${lvm%-*} && root=/dev/mapper/$lvm || die
fi

mount $root $newroot

umount -l /mnt/tok
umount /sys
umount /dev
umount /proc

exec switch_root $newroot ${init:-/sbin/init} $level

Note
Note that, LVM is optional, just omit the cmdline argument to remove LVM support.

See mkintramfs-ll/init for a fully featured init script.

Notice that the previous init script support LABEL UUID and raw block device name sd[a-z][0-9] with or without the /dev/ prefix. One can even provide part of a LABEL or UUID, getdev() will just return the block device rightly.

Finally, you need to tweak the bootloader to use the initramfs - e.g. for Grub, something like this should work.

FILE grub.conf
title Gentoo Linux 3.4.0-gentoo
root (hd0,0)
kernel /boot/kernel-3.4.0-gentoo root=UUID<uuid> lvm=<VG-LV> keyfile=LABEL=PENDRIVE:/key
initrd /boot/initramfs-3.4.0-gentoo.cpio.xz
Note
While playing with the initramfs (finding out what needs to be done in the init script), I do recommend to disable automatic start of X Window. It may happen that some of the devices won't work under X (e.g. keyboard and mouse), thus making you stuck on login screen. The chance of this happening in the console is much lower, and you can always start X with something like 'sleep 30; /etc/init.d/xdm' stop in another console.