Articles / Security

  • linux
  • luks2
  • encryption
  • cryptsetup
  • security
  • usb

Keep Your LUKS2 Header on a USB Key, Not the Disk

Steal a normal LUKS2 disk and you've stolen everything you need to attack its passphrase offline, at your leisure, with as many GPUs as you can afford. That's because the header travels with the data.

The header is the bit that stores the cipher configuration, the salts and the encrypted keyslots. It sits on the first megabyte or so of the encrypted device itself. Convenient: the disk is self-contained, and any machine with the right passphrase can mount it.

A detached header moves that metadata off the disk entirely, typically onto a small USB key you keep separately. Without it, the disk is just high-entropy noise. There's no header to attack, because nothing there says "this is a LUKS volume" at all.

It's genuinely useful for an external backup drive, a removable data volume or a laptop's secondary disk, and it's a five-minute change to your normal LUKS workflow. Two caveats matter enough that I'll come back to both:

  • it is not, on its own, plausible deniability in the VeraCrypt sense;
  • it introduces a new single point of failure you need to plan for from the start.

What a detached header buys you, and what it doesn't

If your disk is seized, imaged, or lost or stolen without the USB key, the attacker has ciphertext with no header. No keyslot to brute-force, no KDF parameters to attack, nothing. They can't even confirm it's a LUKS volume rather than, say, a badly wiped disk full of random bytes.

That's a meaningfully stronger position than "here is a LUKS2 header with argon2id keyslots, please attack them", even though argon2id is expensive to attack in the first place.

It doesn't hide the fact that a device holding an unreadable blob of ciphertext exists. And it doesn't stop an attacker who has both the disk and the USB key at once, for example because they were seized together.

If your threat model is someone taking your laptop and its accessory bag in one go, the detached header buys you very little. The value is in physically separating the two: the key on your person, in a different bag, or left at home when the laptop travels.

Check the target device twice before you start

You need two devices:

  • the target you're encrypting (I'll use /dev/sdb, a whole external disk with no partition table you care about);
  • a USB key with a small filesystem on it to hold the header file, mounted here at /mnt/keyusb.

Double-check /dev/sdb with lsblk and blkid before you touch it. With a detached header, the encrypted payload starts at byte zero of the target device, not after a reserved header region. Point luksFormat at the wrong device and it's destroyed from the very first sector, with no header offset to save you.

Create the header and format the target

cryptsetup creates the header file for you if it doesn't exist, sized to fit the LUKS2 metadata and keyslot areas. That's around 16 MiB by default, trivial for any USB key.

cryptsetup luksFormat --type luks2 \
  --cipher aes-xts-plain64 \
  --key-size 512 \
  --hash sha512 \
  --pbkdf argon2id \
  --iter-time 4000 \
  --header /mnt/keyusb/vault-header.img \
  /dev/sdb

The explicit cipher, hash and pbkdf flags aren't strictly necessary; cryptsetup's LUKS2 defaults are already sane. Writing them out means you know exactly what you got, which is one less thing to guess at when you read this back in two years.

Two knobs worth understanding:

  • --iter-time 4000 tells argon2id to spend about four seconds calibrating its cost. Raise or lower it depending on how fast you want unlocks versus how expensive you want an offline attack to be.
  • --pbkdf-memory is worth checking if the target machine has limited RAM. Recent cryptsetup defaults can ask for a gigabyte of memory during the KDF: fine on a desktop, a problem on something small.

Confirm what actually got written before you rely on it:

cryptsetup luksDump --header /mnt/keyusb/vault-header.img

Check the UUID, cipher and keyslot entries match what you expect. Also note the UUID somewhere now, since blkid won't find it on the target disk once the header is gone.

Back up the header, but not onto the laptop

Lose the USB key with no backup and the data is gone. Not "hard to recover": genuinely gone, because there's no header anywhere to reconstruct the keyslots from. Back it up immediately:

cryptsetup luksHeaderBackup /mnt/keyusb/vault-header.img \
  --header-backup-file ~/secure/vault-header-backup.img

Where you put that backup matters. Leave a copy on your laptop's own disk and you've reintroduced exactly the exposure the detached header was meant to remove: anyone who takes the laptop has the header again, just via a different path.

Put it somewhere with its own protection: an encrypted cloud backup, a GPG-encrypted copy on a different offline medium, whatever fits the rest of your setup. Treat it as at least as sensitive as the live header, because it is the live header.

Test it before you trust it, using --test-passphrase so you're not actually opening the mapping:

cryptsetup open --header ~/secure/vault-header-backup.img \
  --test-passphrase /dev/sdb

Open, format and mount

cryptsetup open --header /mnt/keyusb/vault-header.img /dev/sdb vault
mkfs.ext4 -L vault /dev/mapper/vault
mount /dev/mapper/vault /mnt/vault

And to close it down again:

umount /mnt/vault
cryptsetup close vault

Make it repeatable with crypttab

For a data volume you mount on demand, an /etc/crypttab entry works, using the header= option:

vault UUID=<target-uuid> none luks,header=/mnt/keyusb/vault-header.img,noauto

noauto matters here. Without it, systemd tries to process this entry at boot and stalls waiting for a USB key that probably isn't plugged in yet. Trigger the unlock explicitly, or via a udev rule keyed to the USB key's insertion, once the key's filesystem is actually mounted.

Quick detour: why root on a detached header is a different project

Putting the root filesystem itself behind a detached header on removable media is a considerably harder problem. The header has to be readable from the initramfs, before the normal boot-time device ordering and mount logic exists.

That means baking USB enumeration and a stable device path into your initramfs hooks (dracut or mkinitcpio, depending on distribution) rather than relying on crypttab alone. It's done, but it's specific enough to your distribution and initramfs generator that I won't hand-wave a generic recipe. Treat it as a separate project from encrypting a data volume.

Holes this setup leaves open

  • Stray copies. A copy of the header file in a shell history, a temp directory or an old backup snapshot undermines the whole point as thoroughly as losing the USB key does, except silently.
  • It isn't camouflaged. The header file carries a recognisable magic string at its start, so a stick with vault-header.img on an otherwise empty FAT32 partition hides nothing. Renaming it buys you nothing against anyone who actually inspects the bytes.
  • The passphrase itself. Keylogging, shoulder-surfing or a cold-boot attack against an already-unlocked machine works exactly as well here as against a normal setup.

That last one is worth sitting with. By the time the volume is mounted, the master key is in kernel memory regardless of where the header came from.