Encryption#
Encryption turns plaintext into ciphertext so anyone who reads the storage without the key sees random-looking bytes. It is the lever the operator pulls to keep data confidential when the disk, the backup, or the file is out of their hands. It is not a solution to every threat. Encryption does not stop a running process from being read out of memory, does not stop a logged-in user from copying plaintext, and (on its own) does not prove that the bytes have not been tampered with; that job belongs to hashing and signatures (Hashing).
The Linux ecosystem offers three commonly-mixed categories. At
rest on the disk (LUKS / dm-crypt), per-file inside the
filesystem (eCryptfs, fscrypt), and per-archive (one-off
encryption of a file or backup with gpg, age, or 7z). The
operator can stack them (LUKS under fscrypt under age), or use
just one. The right choice depends on what the operator is
defending and from whom; the Pick your tool section below maps
the common cases.
The rest of the page walks the anatomy of the I/O stack, the vocabulary a newcomer needs to read the tool docs, a decision matrix for picking a tool, then each category in turn (block, file, archive, backup, container, cross-platform), the pitfalls the operator should know on day one, and the references.
Anatomy#
Each encryption category attaches at a different layer of the I/O stack. The edges below name the encryption that lives at that boundary; the layer underneath sees plaintext, the layer above sees ciphertext.
flowchart TB
APP["Application<br/><i>plaintext I/O</i>"]
FS["Filesystem<br/>ext4 / F2FS / Btrfs"]
BLK["Block layer<br/><i>kernel device-mapper</i>"]
DISK["Physical disk<br/>partition / NVMe / SSD"]
APP -->|"per-archive<br/>age / gpg / 7z"| FS
FS -->|"per-file<br/>fscrypt / gocryptfs / eCryptfs"| BLK
BLK -->|"block-device<br/>LUKS / dm-crypt"| DISK
classDef enc stroke:#ff7b72,stroke-width:2px;
linkStyle 0,1,2 stroke:#ff7b72,stroke-width:2px;
What each layer protects and leaks.
Layer |
Protects |
Still leaks |
|---|---|---|
Per-archive (age, gpg, 7z) |
The contents of one file or tarball |
Filename, size, parent directory, mtime |
Per-file (fscrypt, gocryptfs) |
File contents, filenames inside the encrypted tree |
Directory tree structure, file count, sizes (cryfs hides more) |
Block-device (LUKS / dm-crypt) |
Everything on the partition once unmounted |
Disk is in use, total size, that LUKS is present |
Vocabulary#
A short glossary the operator needs to read the rest of the page and the tool documentation that goes with it.
Term |
Meaning |
|---|---|
Plaintext / ciphertext |
The data before / after encryption. |
Key |
The secret the algorithm uses to turn plaintext into ciphertext and back. Usually a fixed-length random byte string the operator never types directly. |
Passphrase |
Something the operator types. A KDF turns it into a key. A weak passphrase makes a strong cipher useless. |
KDF (key derivation function) |
|
Symmetric |
Same key encrypts and decrypts. Fast. The operator must share the key over a secure channel out of band. AES is the workhorse symmetric cipher. |
Asymmetric / public-key |
One public key encrypts, a separate private key
decrypts. The operator hands out the public key freely.
|
AEAD |
Authenticated Encryption with Associated Data. The cipher returns ciphertext and an integrity tag in one shot; AES-GCM and ChaCha20-Poly1305 are the standard modes. Modern tools use AEAD by default; if a tool does not, pair it with a separate signature or HMAC. |
IV / nonce |
A unique non-secret value paired with the key for each encryption. Reusing an IV with the same key breaks the cipher. The tools manage this; the operator just has to know never to “fix” duplicate nonces by hand. |
Keyslot |
LUKS lets up to 8 different keys (a passphrase, a backup key, a TPM-derived key, a YubiKey) all unlock the same master key. Each is held in a keyslot on the header. |
At rest / in transit / in use |
Data on disk / on the wire / in RAM. Encryption-at-rest only covers the first; TLS covers the second; nothing free covers the third (see TPM and memory-safe languages). |
Pick your tool#
Newcomer-friendly decision aid. Pick the row that matches the operator’s goal; the right-hand column points to the section that covers the tool.
Goal |
Reach for |
Section |
|---|---|---|
Encrypt the whole laptop / server disk |
LUKS + |
|
Encrypt one directory on top of an existing filesystem |
|
|
Encrypt a portable volume that has to mount on Windows or macOS |
VeraCrypt container |
|
Send one file to a known recipient |
|
|
Encrypt a backup tree with deduplication and chunking |
|
|
Hide secrets in a Git-tracked YAML / JSON file |
|
|
Read a BitLocker disk from a Linux rescue boot |
|
|
Bind disk unlock to the host’s firmware state |
|
Block-device encryption (LUKS / dm-crypt)#
Encrypts a whole partition or block device; the filesystem on top
sees plaintext after unlock. The standard for full-disk encryption
on Linux. cryptsetup is the only binary the operator needs;
LUKS is the on-disk format it writes by default. LUKS supports up
to 8 active keyslots, so the operator can pair a passphrase with a
recovery file, a YubiKey, or a TPM2 key without re-encrypting.
$ sudo cryptsetup luksFormat /dev/sdb1
$ sudo cryptsetup luksOpen /dev/sdb1 vault
$ sudo mkfs.ext4 /dev/mapper/vault
$ sudo mount /dev/mapper/vault /mnt/vault
$ sudo umount /mnt/vault
$ sudo cryptsetup luksClose vault
Keyslot and header operations every operator should know.
$ sudo cryptsetup luksDump /dev/sdb1 # header + keyslot summary
$ sudo cryptsetup luksAddKey /dev/sdb1 # add a second passphrase
$ sudo cryptsetup luksRemoveKey /dev/sdb1 # rotate one out
$ sudo cryptsetup luksChangeKey /dev/sdb1 -S 0 # replace slot 0
$ sudo cryptsetup luksHeaderBackup /dev/sdb1 \
--header-backup-file /mnt/safe/luks.hdr # off-target copy
$ sudo cryptsetup luksHeaderRestore /dev/sdb1 \
--header-backup-file /mnt/safe/luks.hdr
$ sudo cryptsetup luksSuspend vault # zero keys in RAM
$ sudo cryptsetup luksResume vault # re-prompt and resume
$ sudo cryptsetup status vault # show mapped device state
For boot-time unlock, /etc/crypttab plus the initramfs hooks
the distro ships; on servers, key files under /etc/luks-keys/
or a TPM-backed key (next section).
Where the keys live#
Two copies of the LUKS master key exist at any moment: the encrypted-and-wrapped copy in the on-disk header, and the plaintext copy in the kernel keyring while the device is mapped. Knowing both is what makes the destruction recipes in Common Tasks reliable. Destroying the data means destroying the key; ciphertext is large and slow to wipe, the master key is small and lives in two well-defined places.
Where |
What it holds |
How to destroy it |
|---|---|---|
LUKS header |
On-disk, first ~16 MiB of the device. Holds the eight keyslots; each slot stores an encrypted copy of the master key, wrapped by a key derived from one passphrase / keyfile / TPM key. |
|
Kernel keyring |
In-RAM master key for any currently mapped device
( |
|
swap / hibernate image |
The master key can leak here if swap is unencrypted and the kernel pages out a keyring page. |
Encrypt swap with a random key
( |
A destruction step is only as good as the most accessible copy of the key. A LUKS header backup file on a USB stick in the same bag is not a backup; it is a second copy of the key the operator must also destroy.
TPM-backed unlock (systemd-cryptenroll)#
systemd-cryptenroll binds a LUKS keyslot to the host TPM2, a
FIDO2 token, or a PKCS#11 smartcard. The kernel unlocks
automatically when the host boots into the same firmware and
measurement state the operator enrolled against. Tampered
firmware, a different kernel, or a different host fail unlock and
fall back to the passphrase.
$ sudo systemd-cryptenroll --tpm2-device=auto /dev/sdb1
$ sudo systemd-cryptenroll --tpm2-device=auto \
--tpm2-pcrs=0+7 /dev/sdb1 # bind to firmware + secure-boot
$ sudo systemd-cryptenroll --fido2-device=auto /dev/sdb1
$ sudo systemd-cryptenroll --recovery-key /dev/sdb1
$ sudo systemd-cryptenroll --wipe-slot=tpm2 /dev/sdb1
Per-file encryption (filesystem-level)#
fscrypt, ext4 / F2FS / UBIFS native, per-directory keys, no FUSE overhead.
$ sudo fscrypt setup /mnt/data $ fscrypt encrypt /mnt/data/secret $ fscrypt unlock /mnt/data/secret
gocryptfs, modern FUSE overlay, well-audited, the default choice for an encrypted directory on top of an existing filesystem.
$ gocryptfs -init /mnt/cipher $ gocryptfs /mnt/cipher /mnt/plain $ fusermount -u /mnt/plain
cryfs, FUSE alternative that also hides file sizes and directory structure by storing fixed-size encrypted blocks. Use when metadata leaks matter.
$ cryfs /mnt/cipher /mnt/plain $ cryfs-unmount /mnt/plain
rclone crypt, encrypts data in transit and at rest behind an
rcloneremote (S3, B2, Drive, etc.). Pair with any cloud backend without exposing plaintext to the provider.$ rclone config # create a 'crypt' remote $ rclone copy /local crypt-remote:vault $ rclone mount crypt-remote: /mnt/cloud
eCryptfs, per-file overlay, legacy Ubuntu home-encrypt. Avoid for new deployments; gocryptfs replaces it.
EncFS, FUSE; known security caveats, do not use for new work.
One-off file / archive encryption#
age (modern, simple, the operator’s default for ad-hoc asymmetric encryption; SSH keys work as recipients).
$ age -r RECIPIENT_PUBKEY -o secret.age secret.txt $ age -d -i ~/.ssh/id_ed25519 secret.age > secret.txt $ age -p -o secret.age secret.txt # passphrase (symmetric)
gpg (GnuPG, broadly deployed, key-management heavy).
$ gpg -c file # symmetric, passphrase $ gpg -e -r user@example.com file # asymmetric $ gpg --decrypt file.gpg
openssl enc (works in a pinch, somewhat legacy CLI).
$ openssl enc -aes-256-gcm -pbkdf2 -in plain -out cipher $ openssl enc -d -aes-256-gcm -pbkdf2 -in cipher -out plain
7z with AES-256 (cross-platform; handles archive plus password in one shot; preferred over
zip -ewhose default ZipCrypto is weak).$ 7z a -p -mhe=on bundle.7z dir/ # encrypts names too $ 7z x bundle.7z
ccrypt (single-key symmetric encryption, stream-friendly, drop-in replacement for the ancient
crypt).$ ccrypt file # produces file.cpt $ ccrypt -d file.cpt
scrypt (the
scryptCLI built on the password-hashing KDF; an expensive memory cost frustrates GPU-driven brute force).$ scrypt enc plain cipher $ scrypt dec cipher plain
sops (Mozilla SOPS, encrypts YAML / JSON / env values with age, gpg, AWS KMS, GCP KMS, or Vault). The default for encrypted secrets in IaC trees.
$ sops --age $AGE_RECIPIENT --encrypt secrets.yaml > secrets.enc.yaml $ sops --decrypt secrets.enc.yaml
minisign / signify (detached signatures, not encryption. Pair with any of the above when the operator must verify the source as well as the integrity).
$ minisign -S -s key.sec -m payload.tar $ minisign -V -p key.pub -m payload.tar
Compress before encrypting#
Always compress before encrypting. Encrypted data is indistinguishable from random and won’t compress; compressing first saves space and hides patterns from a chosen-plaintext observer.
$ tar --zstd -cf - dir/ | age -r KEY > dir.tar.zst.age
$ age -d -i ~/.ssh/id_ed25519 < dir.tar.zst.age | tar --zstd -xf -
Backup encryption#
VeraCrypt containers#
veracrypt produces a single encrypted file (or raw partition)
that mounts as a virtual block device on Linux, macOS, and
Windows. The standard choice for portable encrypted volumes that
have to round-trip across operating systems. Supports a
plausible-deniability “hidden volume” mode (a second encrypted
volume placed in the slack space of the first).
$ veracrypt -c /path/vault.hc # create container, interactive
$ veracrypt /path/vault.hc /mnt/vault # mount
$ veracrypt -d /mnt/vault # unmount
$ veracrypt -t -l # list mounted volumes
Cross-platform options#
macOS, FileVault (full-volume),
hdiutilfor encrypted.dmgarchives.Windows, BitLocker (full-volume), EFS for per-file (legacy).
dislocker, reads (and optionally mounts read-write) BitLocker drives from Linux. Useful for forensics, triage, or dual-boot estates where the operator needs the Windows volume from a Linux rescue boot.
$ sudo dislocker -V /dev/sdb1 -u -- /mnt/bdimage # passphrase, interactive $ sudo mount -o loop,ro /mnt/bdimage/dislocker-file /mnt/bl
VeraCrypt, see the standalone section above; the most common cross-OS encrypted container.
Common Tasks#
Suspend a mounted LUKS volume (zero the key in RAM, keep the mapping so I/O resumes on a fresh passphrase).
$ sudo cryptsetup luksSuspend vault
$ sudo cryptsetup luksResume vault
Close a LUKS volume completely (zero kernel key and remove the ``/dev/mapper`` device).
$ sudo umount /mnt/vault
$ sudo cryptsetup luksClose vault
Revoke one credential (a lost YubiKey, a leaked passphrase).
$ sudo cryptsetup luksDump /dev/sdb1
$ sudo cryptsetup luksKillSlot /dev/sdb1 2
Make a LUKS volume permanently unreadable (wipe every keyslot, ciphertext becomes unrecoverable without a header backup).
$ sudo cryptsetup luksClose vault
$ sudo cryptsetup luksErase /dev/sdb1 # confirms with YES (uppercase)
Hard-overwrite the LUKS header (faster than ``luksErase``, same irreversibility; pair with ``blkdiscard`` on SSD / NVMe so wear leveling cannot leave old header sectors behind).
$ sudo dd if=/dev/urandom of=/dev/sdb1 bs=1M count=16 conv=fsync
$ sudo blkdiscard -f /dev/sdb1
Destroy every LUKS header backup the operator can find (each one is a full unlock key).
$ shred -uvz /mnt/safe/luks.hdr
$ find / -name '*luks*hdr*' -o -name '*.luks-bak' 2>/dev/null
Pre-arm a panic script (close every mapping, erase every LUKS header on the box, sync). Wire it to a hot key, a USB-eject ``udev`` rule, or a button.
$ sudo tee /usr/local/sbin/panic-luks >/dev/null <<'EOF'
#!/bin/sh
set -eu
for m in $(ls /dev/mapper/ | grep -v control); do
cryptsetup luksClose "$m" 2>/dev/null || true
done
for d in /dev/sd? /dev/nvme?n?; do
cryptsetup isLuks "$d" 2>/dev/null && cryptsetup luksErase -q "$d"
done
sync
EOF
$ sudo chmod 0700 /usr/local/sbin/panic-luks
$ sudo chown root:root /usr/local/sbin/panic-luks
Back up a LUKS header off-target (insurance against accidental header corruption; treat the backup file like the device itself).
$ sudo cryptsetup luksHeaderBackup /dev/sdb1 \
--header-backup-file /mnt/safe/luks.hdr
Restore a LUKS header from backup (after accidental ``luksErase`` or header corruption).
$ sudo cryptsetup luksHeaderRestore /dev/sdb1 \
--header-backup-file /mnt/safe/luks.hdr
Add a recovery key to an existing LUKS volume.
$ sudo cryptsetup luksAddKey /dev/sdb1
Bind unlock to the host TPM2 (auto-unlock as long as firmware and Secure Boot state are unchanged).
$ sudo systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0+7 /dev/sdb1
Mount a BitLocker disk from a Linux rescue boot (triage, forensics, dual-boot recovery).
$ sudo dislocker -V /dev/sdb1 -u -- /mnt/bdimage
$ sudo mount -o loop,ro /mnt/bdimage/dislocker-file /mnt/bl
Pitfalls#
Lost passphrase = lost data. Use a backup keyfile, a recovery key, or a second keyslot stored on a separate medium.
Plaintext tmp files. Many tools spill plaintext to
/tmpduring decryption. Useshred/tmpfs/ ramdisks where it matters.Metadata leaks. LUKS protects file contents, not access patterns; an attacker with multiple point-in-time images of an encrypted disk can sometimes deduce activity. cryfs and similar hide more metadata at the cost of speed.
Key rotation. LUKS supports up to 8 keyslots; rotate periodically and after any device that held the key is lost.
Hibernation. Swap files and partitions need encryption too, otherwise hibernated RAM ends up plaintext on disk.
Hash before, not after. Pair encryption with a content hash of the plaintext (see Hashing); a ciphertext hash only proves bit-for-bit storage of the encrypted blob, not that the plaintext is what the operator expects on decrypt.
Panic steps are irreversible.
luksEraseand theddheader overwrite cannot be undone without a separate header backup. Test the panic path on a throwaway device first.Wear leveling defeats overwrites on SSD / NVMe. Old header sectors can survive. Pair the overwrite with
blkdiscard, or use the drive’s ATA Secure Erase / NVMe Format with Secure Erase when time allows.Hard power-cut leaks kernel state. The kernel keyring is zeroed on
luksCloseonly after I/O drains; a forced power loss while writes are in flight may leave recoverable bits in the controller cache.Plain ``dm-crypt`` has no header. No keyslots, nothing to
luksErase. Destroying access there means forgetting the passphrase or overwriting enough of the data area to make recovery worthless.
References#
man 8 cryptsetup,man 5 crypttab(LUKS / dm-crypt setup and boot-time unlock).man 8 systemd-cryptenroll(TPM2 / FIDO2 / PKCS#11 keyslot binding).man 8 fscrypt,man 5 ecryptfs(filesystem-level per-file encryption).man 1 gpg,man 1 age,man 1 openssl,man 1 7z(one-off file and archive encryption).man 1 ccrypt,man 1 scrypt(single-key symmetric tools).man 1 sops(encrypted IaC secrets).man 1 minisign(detached signatures).man 1 veracrypt,man 1 dislocker(cross-OS containers and BitLocker access).Hashing for fingerprinting and integrity, the pair to encryption.
Filesystem for the surrounding filesystem and storage layer.
Permissions for the DAC and capability layer that gates plaintext access.