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.
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.
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.
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.
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.