Vivian Voss

GELI vs LUKS

freebsd linux security unix

The Unix Way ◆ Episode 18

A laptop is left on a train. With full-disk encryption, the person who finds it has an expensive paperweight and a drive full of noise. Without it, they have your SSH keys, your mail, your password store and your customers' data. The stakes are not subtle. FreeBSD and Linux both solve this problem properly, with mature, audited tooling and the same underlying cipher. They arrive at the solution by rather different routes, and the routes are the interesting part.

FreeBSD: GELI

GELI is FreeBSD's disk-encryption framework, and the first thing to understand is that it is not a standalone product bolted onto the system. It is a GEOM class. GEOM is FreeBSD's modular block-storage framework, in which every transformation of a disk (mirroring, striping, labelling, encryption) is a class that consumes one or more providers and presents a new provider. Encryption, in this model, is simply one more transform in the stack.

You initialise a provider, choosing the cipher, key length and, optionally, a data-authentication algorithm:

geli init -e AES-XTS -l 256 -a HMAC/SHA256 /dev/ada1
geli attach /dev/ada1
newfs /dev/ada1.eli

geli init writes a small metadata block to the last sector of the provider and sets up the master key, encrypted under your passphrase (and, optionally, one or more key files). The default cipher is AES-XTS; the default key length for AES-XTS is 128 bits, so -l 256 is worth specifying for AES-256-XTS. Key strengthening uses PKCS#5v2 (PBKDF2), with the iteration count auto-tuned to the host.

geli attach prompts for the passphrase and creates a new provider, /dev/ada1.eli. Everything written to .eli is encrypted on its way to /dev/ada1; everything read is decrypted on the way back. You can put UFS on it with newfs, or hand it to ZFS as a vdev (zpool create tank /dev/ada1.eli), at which point you have an encrypted ZFS pool with all of ZFS's checksumming and snapshots intact.

The -a HMAC/SHA256 flag is the quietly elegant part. Without it, GELI provides confidentiality only: an attacker who flips bits on the raw disk cannot read your data, but the corrupted ciphertext will decrypt into corrupted plaintext, silently. With it, GELI stores a keyed HMAC for each sector and verifies it on read. A tampered or degraded sector is detected and reported, not silently served as rubbish. This is authenticated encryption, and it lives in the same tool, behind one flag.

GELI also handles encrypted swap cleanly with one-time keys: each boot, swap is encrypted under a fresh random key that is discarded at shutdown, so swapped-out secrets never persist. This is configured in rc.conf via the geom_eli mechanism, with no separate subsystem involved.

GELI Is Just Another GEOM Class UFS, or ZFS vdev /dev/ada1.eli attached provider one uniform interface: consume a provider, present a provider geli encrypt + HMAC gmirror mirroring gstripe striping glabel labelling Integrity is a flag on the encryption class, not a separate subsystem.

Linux: LUKS

LUKS (Linux Unified Key Setup) is the standard, and it is genuinely excellent. The user-facing tool is cryptsetup:

cryptsetup luksFormat /dev/sdb
cryptsetup open /dev/sdb secret
mkfs.ext4 /dev/mapper/secret

cryptsetup luksFormat writes a LUKS2 header (LUKS2 has been the default format since cryptsetup 2.1, 2019) to the start of the device. The header is the part that distinguishes LUKS from raw dm-crypt: it is a documented metadata format holding the cipher parameters and up to thirty-two key slots, so multiple passphrases or key files can unlock the same master key, and any one can be revoked without re-encrypting.

The defaults are well chosen and, in one respect, ahead of GELI. The default cipher is aes-xts-plain64 with a 256-bit key per XTS half, which is AES-256-XTS. The default key-derivation function in LUKS2 is argon2id, a memory-hard function (roughly one gibibyte of memory and several hundred milliseconds per attempt by default). Memory-hardness is the property that makes large-scale brute-forcing expensive even on GPUs and custom hardware, because the attacker must provision memory per guess, not merely compute. GELI's PBKDF2 is sound but not memory-hard. On the key-derivation question, LUKS2 is the stronger default, and it is worth saying so plainly.

Compute-Hard vs Memory-Hard PBKDF2 (GELI) compute-hard, cheap to parallelise compute ~0 memory argon2id (LUKS2) memory-hard, dear to parallelise ~1 GiB per guess attacker must provision memory per guess Memory is what makes brute-forcing dear. This is LUKS2's genuine advantage.

cryptsetup open decrypts the master key with your passphrase and creates /dev/mapper/secret through device-mapper, the kernel's generic framework for stacking virtual block devices. The underlying crypto target is dm-crypt. If you want authenticated encryption, the equivalent of GELI's HMAC flag, you enable it with cryptsetup's --integrity option, which adds a second device-mapper target, dm-integrity, beneath dm-crypt. It works well; it is a second layer rather than a flag on the first.

Persistence is via /etc/crypttab and the initramfs, and LUKS composes with LVM in either order (LVM-on-LUKS or LUKS-on-LVM) depending on whether you want one passphrase for several logical volumes or separate encryption per volume.

The Shape

Here is the difference, and it is architectural rather than a matter of which is better.

On FreeBSD, encryption is a GEOM class. So is mirroring (gmirror), striping (gstripe), labelling (glabel), and journalling (gjournal). Each consumes providers and presents providers, with a uniform interface, so they stack in any sensible order: a mirror of two encrypted disks, or an encrypted mirror, is the same two classes composed two ways, with the same command vocabulary. The block layer is a single framework of interchangeable transforms, and geli is one of them. Integrity is not a separate subsystem; it is a flag on the encryption class.

On Linux, the same capabilities exist and are at least as powerful, but they are assembled from separate subsystems with separate tooling: the LUKS header format, dm-crypt for encryption, dm-integrity for authentication, dm-verity for read-only integrity, LVM for volume management, mdadm for RAID. Each is a different device-mapper target or a different tool, with its own configuration surface, composed into a stack. The power is there; the uniformity is not. You compose specialised parts rather than uniform ones.

One Framework, or Many Subsystems FreeBSD: GEOM one framework, one vocabulary geli gmirror gstripe glabel Linux: a stack filesystem (ext4) LUKS2 header dm-crypt dm-integrity LVM / mdadm each a device-mapper target Both reach the same secure drive. The power is equal; the uniformity is not. One framework of uniform parts, or several specialised parts composed.

Neither approach is wrong, and the trade-off is real. The Linux ecosystem's modularity is part of why LUKS2 could adopt argon2id ahead of GELI: dm-crypt's key handling is one focused subsystem with its own maintainers and its own release cadence. FreeBSD's coherence is part of why the answer to "encrypt this, mirror it, and detect tampering" is three flags and one mental model rather than three subsystems.

The Point

The honest summary for a working engineer in 2026 is this. This series prizes composability, and on that measure GELI is the more Unix-aligned answer: encryption, per-sector authentication and one-time-keyed swap are one GEOM class with one vocabulary, composing uniformly with the rest of the FreeBSD block layer, so "encrypt it, mirror it, detect tampering" is three flags and one mental model. LUKS reaches the same secure drive that turns a stolen disk into noise, and on key derivation it is genuinely ahead: LUKS2's argon2id is memory-hard where GELI's PBKDF2 is not, a real reason to prefer it where passphrase brute-force resistance dominates the threat model. But LUKS assembles its result from separate specialised subsystems, a LUKS header over dm-crypt over dm-integrity beside LVM, each a different device-mapper target with its own tooling.

Same Cipher, Two Shapes GELI (FreeBSD) LUKS (Linux) cipher AES-256-XTS AES-256-XTS key derivation PBKDF2 argon2id (memory-hard, ahead) integrity HMAC flag, one class dm-integrity, second layer shape one GEOM class stack of dm subsystems stolen drive noise noise Both arrive at the same place. The Unix way prefers the version you can hold in one hand.

One framework of uniform parts, or several specialised parts composed. Both arrive at the same place: a drive that is, to anyone without the key, honestly just noise.

Both turn a stolen drive into noise. GELI's edge is coherence: one GEOM class for encryption plus integrity, composing uniformly with mirroring and striping. LUKS2's edge is its argon2id key derivation, memory-hard where GELI's PBKDF2 is not. The Unix way prefers the version you can hold in one hand.