Frank DENIS random thoughts.

Options for full disk encryption on Linux

Full disk encryption is, as the name suggests, a way to encrypt entire storage volumes instead of (or in addition to) individual files.

This improves security against important physical threats, such as stolen disk drives and cold boot attacks.

All modern operating systems have great support for full disk encryption, and Linux is no exception.

Linux provides a variety of algorithms that can be used for full disk encryption, and the trade-offs are not necessarily clear. So, we’re going to review the available options.

Setting Up an Encrypted Volume Using LUKS

To set up an encrypted volume, we have to start with an available volume. It could be a partition, an LVM volume, a loopback volume, or anything that can be formatted with a filesystem.

Using that volume for encryption requires metadata that can be written with the cryptsetup command. Here’s an example instantiation of that command:

cryptsetup -y -v luksFormat --sector-size 4096 \
-cipher aegis128-plain64 --key-size 128 --integrity aead \
/dev/nvme1n1p1

/dev/nvme1n1p1 is an unused partition, but it could be anything, including a logical volume such as /dev/mapper/ubuntu--vg-encrypted.

If integrity has been enabled, that operation can take a while, but it only has to be done once.

After an encrypted volume has been set up, it can be activated with the following command:

cryptsetup luksOpen /dev/nvme1n1p1 encrypted

This creates a new device /dev/mapper/encrypted, which can then be formatted using a regular filesystem:

mkfs.ext4 /dev/mapper/encrypted

It can then be mounted:

mount /dev/mapper/encrypted /mnt/encrypted

To automatically enable encrypted volumes, the /etc/crypttab file should be edited, followed by editing /etc/fstab as usual to mount the actual filesystems.

Storing Passwords in TPM

Intel CPUs often embed a Trusted Platform Module (TPM) designed to securely store and use secrets. Instead of having to manually enter a password every time an encrypted partition has to be mounted, passwords can be stored in the TPM. Most Linux distributions support this out of the box. On Ubuntu, for example, this can be done with the clevis command:

apt install clevis clevis-tpm2 clevis-luks clevis-initramfs clevis-systemd initramfs-tools

clevis luks bind -d /dev/nvme1n1p1 tpm2 '{"pcr_ids":"1,3,5,7,11,12,14", "pcr_bank":"sha256"}'

update-initramfs -u -k all

Note that the clevis package alone is not enough. Without the other packages listed in the apt command above, the command may succeed, but volumes won’t be automatically mounted, and passwords won’t be synchronized with the TPM. Recreating the initramfs is also essential.

Changing Passwords

Data on an encrypted partition is encrypted using a randomly generated key, and that key is encrypted using another key directly derived from the password (key wrapping). As a result, the password can be changed at any time without having to re-encrypt the entire volume.

Changing the password of an encrypted volume can be done with the cryptsetup command:

cryptsetup luksChangeKey /dev/nvme1n1p1

If a volume was configured to read the password from the TPM, the command will also automatically update the information in the TPM.

Don’t Forget the Swap Partitions

Swap partitions are critical to protect, as they can include secrets initially meant to be stored temporarily in memory. This includes interactive passwords, session keys, decrypted data, and more. It is no surprise that OpenBSD has had encrypted swap partitions by default for ages.

Encrypted swap partitions differ from regular volumes because, due to them being ephemeral, a password is not required. It’s perfectly fine, and actually more secure, to generate a random password every time the system boots.

That can be achieved by adding a line such as the following in /etc/crypttab:

swap /dev/nvme1n1p1 /dev/urandom swap,cipher=aes-ctr-plain64,sector-size=4096

This creates a /dev/mapper/swap device that can be mounted as a regular swap partition via /etc/fstab.

Encryption and Integrity: Why Both Matter

Before diving into encryption algorithms, let’s discuss integrity.

Encryption ensures that without the encryption key, an adversary inspecting a volume’s contents cannot decipher the stored data. However, encryption alone does not prevent an active attacker from physically stealing a drive, modifying its contents, and returning it unnoticed.

Will the System Detect Tampering?

That depends on whether integrity checking is enabled.

  • Without integrity checking, modifications go undetected. How much an adversary can alter data without knowing the key depends on the encryption algorithm.
  • With integrity checking, any unauthorized modification is immediately flagged as an I/O error. This not only detects malicious tampering but also catches data corruption from software bugs or hardware failures.

Integrity vs. Traditional Checksums

Standard checksums are designed to detect hardware failures but can be trivially forged by an attacker.

In contrast, cryptographic authentication—such as that used by LUKS—prevents forgery, ensuring integrity protection against active threats.

Should You Enable Integrity Checking?

If the threat model includes physical access, where an attacker can steal and return a drive, integrity checking is highly recommended. However, it comes with trade-offs:

  • Storage overhead: Typically 16 or 32 extra bytes per sector.
  • Performance impact: Additional CPU cycles for verification.

If the only concern is data theft (where the drive is stolen but never returned), integrity checking may not be worth the cost.

With LUKS, integrity checking is either optional or mandatory, depending on the chosen algorithm.

LUKS Algorithms

Volumes can be encrypted using different algorithms. While the cryptsetup command includes a benchmarking tool, it is neither accurate, complete, nor sufficient to wisely choose the best options for a given use case.

So, let’s compare options that make sense. Options that offer no security or performance advantages have been intentionally omitted.

LUKS Algorithm Benchmarks

Read and write performance benchmarks were conducted using the following command on an AMD Zen4 CPU with an NVMe disk:

fio --randrepeat=1 --ioengine=libaio --direct=1 --gtod_reduce=1 \
    --name=test --bs=4k --iodepth=64 --readwrite=randrw --rwmixread=75 \
    --size=16G --filename=/encrypted/bench --time_based --runtime=120s

The key size is always 128 bits, except for the XTS mode where it is 256 bits (two 128-bit keys under the hood).

If they are properly sampled from a uniform distribution, 128-bit keys are perfectly secure, even against quantum threats.

To create an encrypted volume using the following schemes, use this command template:

cryptsetup -y -v luksFormat --sector-size <sector size> \
-cipher <cipher> --integrity <integrity> --key-size 128 \
<device>

For aes-xts, replace key-size 128 with key-size 256.

Schemes With Integrity

Schemes with integrity

Algorithm Integrity Sector size Read speed Write speed
aegis128-plain64 aead 512 274 91
aegis128-plain64 aead 2048 267 89
aegis128-plain64 aead 4096 307 102
aegis128-random aead 512 223 74
aegis128-random aead 2048 272 91
aegis128-random aead 4096 259 86
aes-gcm-plain64 aead 512 264 88
aes-gcm-plain64 aead 2048 262 87
aes-gcm-plain64 aead 4096 199 66
aes-gcm-random aead 512 295 98
aes-gcm-random aead 2048 278 93
aes-gcm-random aead 4096 288 96

The sector size doesn’t have any practical implications for the security properties of a given scheme.

However, it slightly affects performance, and a larger sector size does not necessarily result in better performance.

Schemes Without Integrity

Schemes without integrity

Algorithm Integrity Sector size Read speed Write speed
aes-ctr-plain64 none 512 423 141
aes-ctr-plain64 none 2048 424 141
aes-ctr-plain64 none 4096 501 167
aes-ctr-random none 512 302 100
aes-ctr-random none 2048 278 93
aes-ctr-random none 4096 225 75
aes-hctr2-plain64 none 512 435 145
aes-hctr2-plain64 none 2048 431 144
aes-hctr2-plain64 none 4096 444 148
aes-xts-plain64 none 512 467 156
aes-xts-plain64 none 2048 536 179
aes-xts-plain64 none 4096 430 143
serpent-xts-plain64 none 512 442 147
serpent-xts-plain64 none 2048 424 141
serpent-xts-plain64 none 4096 460 153

Without integrity, the sector size also makes a noticeable difference in performance.

However, the security guarantees remain independent.

Security Properties

Sectors are divided into blocks, typically 16 bytes in size.

All encryption schemes described here share a common vulnerability: an adversary who can observe the ciphertext at a given location can later rewrite it, causing it to decrypt to the same plaintext. For most encryption schemes, this applies to individual 16-byte blocks, except for aes-hctr2-plain64, where the entire sector must be restored.

  • aes-ctr-plain64: If an adversary can guess the content of a 16-byte block and can both observe and modify its encrypted version, they can manipulate the plaintext freely. Additionally, since encryption at the same location always produces identical ciphertext, an adversary monitoring changes over time can determine when the content reverts to a previous state. Furthermore, if an adversary can inject arbitrary content, observe the resulting ciphertext, and replace it with another ciphertext, they can control that block—but not others.
  • aes-ctr-random: Similar to aes-ctr-plain64, except encryption is non-deterministic, making it harder for an adversary to detect when content is rewritten. However, an adversary who can modify and observe ciphertexts can still control the plaintext within an individual block.
  • aes-hctr2-plain64: Modifying a single bit within a sector affects all blocks in that sector. While this method lacks integrity verification, an attacker can only corrupt encrypted sectors, not selectively modify them. Additionally, unless an entire sector is reused, an adversary cannot determine content reuse.
  • aes-xts-plain64: An adversary who can modify encrypted 16-byte blocks can either corrupt them or restore them to a previously recorded value. They can observe when a previously stored value reappears at the same location. However, without knowing the encryption key, they cannot selectively flip specific bits, even with access to plaintext-ciphertext pairs.
  • serpent-xts-plain64: Behaves similarly to aes-xts-plain64, but may perform better on platforms without hardware AES acceleration, such as QEMU-based CPU emulation.
  • aegis128-plain64: If an adversary can collect around 500 plaintext-ciphertext pairs for a given block, they can eventually manipulate that block’s content. Additionally, encryption at the same location always produces identical ciphertext, making content reuse observable to an attacker.
  • aegis128-random: Unlike aegis128-plain64, this scheme prevents an adversary from identifying content reuse. However, an attacker can still set a block to either garbage or a previously observed value.
  • aes-gcm-plain64: If an adversary can collect as few as two plaintext-ciphertext pairs for a given block, they can manipulate the plaintext. As with other deterministic encryption methods, repeated encryption of the same data at the same location results in identical ciphertexts.
  • aes-gcm-random: Prevents an adversary from detecting content reuse. However, an attacker can still corrupt a block or restore it to a previously observed value. Theoretical attacks require observing 2^64 ciphertexts to enable forgery, but this is not a practical threat in most contexts.
Scheme Granularity Vulnerability Summary
aes-ctr-plain64 16-byte block Identical ciphertext per location enables replay and content tracking.
aes-hctr2-plain64 Entire sector Single-bit change affects the whole sector; selective block modification isn’t possible.
aes-xts-plain64 16-byte block An attacker can restore or corrupt a block but cannot selectively flip bits.
serpent-xts-plain64 16-byte block Behaves like aes-xts; may perform better on non-AES hardware platforms.
aegis128-plain64 16-byte block With ~500 plaintext-ciphertext pairs, an adversary can eventually manipulate a block.
aes-gcm-plain64 16-byte block Only two pairs suffice to enable block manipulation; identical outputs leak reuse.
aes-ctr-random 16-byte block Non-deterministic output hides reuse, but attackers can still control a block.
aegis128-random 16-byte block Prevents reuse detection; attackers can force a block to garbage or a previous state.
aes-gcm-random 16-byte block Hides content reuse; block corruption or replay is possible (forgery requires 2^64 ciphertexts).

What About OPAL?

OPAL is a standard for Self-Encrypting Drives (SEDs) that enables hardware-based full-disk encryption without relying on software solutions like LUKS.

Devices implementing OPAL typically support only aes-xts-plain64, whose strengths and limitations have been discussed above. If stronger security properties are needed, software encryption can be layered on top of OPAL.

Recommendations

There are many trade-offs here!

However, everything must be considered in the context of an actual threat.

Observing the content of a block at multiple times involves stealing a drive, then putting it back multiple times, without this being noticed. A RAID configuration facilitates this, so watch out for RAID-related alerts.

Observing plaintext-ciphertext pairs requires an adversary to be able to store arbitrary data while the system is running and know exactly where it was stored. This is not impossible, though. If the server is running a public caching proxy, an adversary can upload a very large file made only of zeros, then unplug and copy the drive’s content. Later, they can send a very large file made of bits all set to 1. Then unplug and copy the drive’s content. By comparing both images and identifying consecutive blocks that differ, an adversary using aes-ctr-plain64 can set those blocks to arbitrary values. With other schemes, this attack is not effective.

If the threat model is limited to an adversary stealing a drive, not knowing its exact content, and not being able to silently put it back later, then opting for the fastest scheme is perfectly reasonable.

If performance is not critical, opt for more secure options such as aegis128-random or aes-hctr2-plain64.

Different encrypted volumes can use different encryption schemes depending on their individual use cases.

There’s no one-size-fits-all option, but if you’re looking for a reasonable default, aes-xts-plain64 is a good and conservative choice.

The Kiasu block cipher would be a faster drop-in replacement, but it is not yet supported by the Linux kernel.