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)

argon2, scrypt, PBKDF2. Stretches a passphrase into a key and forces an attacker to pay CPU / memory per guess. LUKS2 uses argon2id by default.

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. age and gpg are the common CLI front-ends.

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 + cryptsetup (with TPM unlock on a laptop)

Block-device encryption (LUKS / dm-crypt)

Encrypt one directory on top of an existing filesystem

gocryptfs

Per-file encryption (filesystem-level)

Encrypt a portable volume that has to mount on Windows or macOS

VeraCrypt container

VeraCrypt containers

Send one file to a known recipient

age -r <pubkey>

One-off file / archive encryption

Encrypt a backup tree with deduplication and chunking

restic, borg, or kopia

Backup encryption

Hide secrets in a Git-tracked YAML / JSON file

sops

One-off file / archive encryption

Read a BitLocker disk from a Linux rescue boot

dislocker

Cross-platform options

Bind disk unlock to the host’s firmware state

systemd-cryptenroll (TPM2)

TPM-backed unlock (systemd-cryptenroll)

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.

cryptsetup luksErase (wipes all slots); cryptsetup luksKillSlot N (one slot); dd over the first sectors (full header overwrite, fastest, irreversible without a backup).

Kernel keyring

In-RAM master key for any currently mapped device (/dev/mapper/<name>). Created on luksOpen, dropped on luksClose.

cryptsetup luksSuspend <name> (zero key, keep mapping); cryptsetup luksClose <name> (remove mapping entirely). A subsequent reopen demands a passphrase / token again.

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 (/dev/mapper/cryptswap); or set vm.swappiness=0 plus mlock on sensitive processes; or disable hibernate.

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 rclone remote (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 -e whose 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 scrypt CLI 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#

  • restic, chunked, deduplicating, AES-256.

  • borg, similar approach with a long-running community.

  • kopia, modern Go-based backup with client-side encryption and pluggable storage backends.

  • duplicity, GPG-based.

  • age plus rclone, a simple cloud-backup pattern.

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), hdiutil for encrypted .dmg archives.

  • 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 /tmp during decryption. Use shred / 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. luksErase and the dd header 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 luksClose only 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.

  • LUKS on-disk format specification

  • age encryption

  • fscrypt user guide

  • gocryptfs, cryfs

  • Mozilla SOPS

  • VeraCrypt, dislocker

  • restic, borg, kopia, duplicity (encrypted backup tooling).