/ Documentation / Operations / Forensics and partial recovery

Forensics and partial recovery

Investigate a damaged vault, salvage what is still readable, and rebuild a corrupted header from a backup.

This page covers the five forensic / partial-recovery subcommands. They exist for the case the recovery page does not cover: you still have the unlock material (passphrase, FIDO2, TPM), but the on-disk bytes themselves got damaged. A single bit flip in a chunk's ciphertext is enough to fail AEAD verification and make luksbox get or luksbox mount refuse to read the affected file.

Without dedicated tooling, the user is left with dd and a hex editor. With it, the workflow is :

Question Command
Is the vault even openable today ? info (header parse) then ls
What does the metadata tree actually look like, decrypted ? header-dump
Which chunks are still AEAD-valid and which ones are not ? check
Can I recover this one file with the bad chunks zero-filled ? extract --tolerate-errors
The header itself is corrupted; can I roll back to a backup ? header-restore
Should I keep a header backup as a routine pre-rotation step ? header-backup

All five subcommands are read-only with the single exception of header-restore, which rewrites the 8 KiB header bytes (in place for inline mode, atomic temp+rename for detached mode).

1. The mental model

A LUKSbox vault is a single .lbx file (plus optional .hdr, .anchor, .hybrid, .kyber sidecars). On disk, that one file is laid out as :


flowchart LR
    A["bytes 0..8192
HEADER
(8 KiB, HMAC at the tail)"] --> B B["bytes 8192..1056768
METADATA REGION
AEAD-encrypted file tree"] --> C C["bytes 1056768..EOF
DATA REGION
4 KiB chunks, each AEAD-sealed independently"]

Three classes of damage map to three classes of recovery :

Damage Symptom Recovery
Bytes 0..8192 (header) flipped luksbox info may still parse, but unlock fails with header authentication failed. header-restore from a previously-saved header-backup
One AEAD chunk flipped (anywhere in the data region) mount works, ls works, but reading the affected file fails with AEAD failure. Other files in the same vault are untouched. check to locate, extract --tolerate-errors to salvage the rest of that file
Metadata region flipped The vault opens through the keyslot, but the encrypted file-tree blob fails AEAD or structural validation, so the whole tree is gone. No automatic recovery yet. The chunks are still on disk and AEAD-valid, but you have no map. This is the gap LUKSbox does not yet close (LUKS2 has a redundant header for the same reason).

2. The forensic flow

The order in which you run the subcommands matters when you are investigating a damaged vault you have never opened before :


flowchart TD
    Start([damaged vault]) --> Info["luksbox info vault.lbx
(does the header even parse ?)"] Info -->|no, magic missing| Restore["luksbox header-restore
--no-verify"] Info -->|yes| Open{can you unlock ?} Open -->|no, header HMAC bad| Restore Open -->|yes| Dump["luksbox header-dump --pretty
(decrypted tree, chunk refs, generations)"] Dump --> Check["luksbox check vault.lbx --json
(per-chunk AEAD status)"] Check -->|all OK| Done([no chunk damage]) Check -->|N chunks bad| Extract["luksbox extract --tolerate-errors
per affected file"] Restore --> Open Extract --> Done

3. The five subcommands at a glance

header-backup

Reads the 8 KiB header bytes (offsets, keyslots, salts, HMAC) and writes them to a separate file. No unlock material required: this is just a dd of the first 8 KiB, packaged as a CLI with the right permissions (mode 0600 on Unix) and with a structural sanity parse before saving.

luksbox header-backup vault.lbx /media/usb/vault.hdrbak
# wrote 8192 bytes from vault.lbx to /media/usb/vault.hdrbak
#   cipher: Aes256GcmSiv    metadata at offset 8192 (1048576 B)    data at offset 1056768
#   populated keyslots: 1 / 8

Run it after every luksbox enroll or luksbox rotate-mvk. Keep the backup on separate trusted storage: anyone who has both the .lbx and a backup of its header has the same offline brute-force surface as anyone with the original vault.

For detached-header vaults, point the backup at the sidecar :

luksbox header-backup vault.lbx vault.hdrbak --header /media/usb/vault.hdr

See the header-backup page for the full flag reference.

header-restore

Replaces the on-disk header bytes with the contents of a previously saved backup. By default it requires unlock material so it can HMAC-verify the new header against the current MVK before writing. That check matters: without it, an attacker who can replace your backup file could install a header that authenticates under their MVK on the next restore. Skip it only with --no-verify when the on-disk header is itself too damaged to unlock with.

# default: HMAC-verify the backup against the live MVK before writing
luksbox header-restore vault.lbx vault.hdrbak

# only when the on-disk header is too damaged to unlock
luksbox header-restore vault.lbx vault.hdrbak --no-verify

In inline mode this rewrites bytes 0..8192 of the vault file in place (then fsync); in detached mode (--header /path/to.hdr) it atomically replaces the sidecar via temp + rename + parent-dir fsync.

See the header-restore page for the full flag reference and a step-by-step damaged-header walkthrough.

header-dump

Decrypts the metadata blob and emits a JSON tree of every inode, chunk reference, generation counter, and keyslot summary. Read-only. Requires unlock material, so this is post-unlock forensics: you already have the MVK and want to inspect what the format normally hides.

luksbox header-dump vault.lbx --pretty | jq

The output includes :

This is the command you pipe into jq when an audit asks "what does the encrypted file tree actually look like decrypted?".

See the header-dump page for output shape and example queries.

check

Walks every used chunk in the vault, AEAD-decrypts it, and reports per-chunk status (ok or aead_fail). Surfaces the exact (file_path, chunk_idx, on-disk slot_offset, generation) of every chunk the runtime would refuse to decrypt at mount time. Read-only. Exit code is non-zero if any chunk fails.

luksbox check vault.lbx
# BAD  /payload chunk_idx=1 chunk_id=1 gen=5 off=1060892 : crypto: AEAD failure
#
# checked 1 file(s), 3 chunk(s): 2 OK, 1 BAD

Or, for tooling consumption :

luksbox check vault.lbx --json | jq '.failures[] | {path, chunk_idx, slot_offset}'

check does not modify the vault. It is safe to run on a vault you suspect has been tampered with: at worst it tells you which chunks will fail at mount time.

See the check page for output shape, exit codes, and the JSON schema.

extract --tolerate-errors

Pulls a single file out of the vault like luksbox get, but tolerates per-chunk AEAD failures by writing 4 KiB of zeros in place of each unrecoverable chunk and continuing. Prints the chunk_idx and on-disk offset of every failure to stderr (and to --report <path> as JSON, if set).

# Fails on a vault with one bad chunk: standard get is strict
luksbox get vault.lbx /payload broken.bin
# error: crypto: AEAD failure

# Forensic recovery: lossy mode, must be acknowledged with --tolerate-errors
luksbox extract vault.lbx /payload recovered.bin --tolerate-errors --report fail.json
# chunk_idx=1 chunk_id=1 gen=5 off=1060892 FAILED (crypto: AEAD failure); writing 4096 zero bytes
# wrote 12000 bytes to recovered.bin (2 chunks OK, 1 chunks zero-filled)
# warning: 1 chunk(s) were unrecoverable. The output file has 4096-byte zero ranges at the corresponding offsets.

The --tolerate-errors flag is mandatory to opt into the lossy path: without it, extract refuses to run. This is deliberate. A user who runs the command on autopilot should never silently get a zero-padded file back thinking it is the real one.

extract does not modify the vault. The only side-effect is the output file (and the optional report file).

See the extract page for the full flag reference and post-recovery validation tips.

4. What this tooling does NOT do

It does not invent ciphertext. The recovery story is bounded by what is still authenticated on disk :

The honest summary: LUKSbox now ships an investigation toolkit that is on par with what cryptsetup gives you for a damaged LUKS2 volume (luksHeaderBackup, luksDump, repair). It does not yet provide redundant on-disk metadata; that is on the roadmap and documented as a known limitation in the security architecture page.

5. Operational habits that prevent the worst cases