luksbox extract --tolerate-errors
Forensic best-effort extract: recover what is still readable from a partly-corrupted file.
luksbox extract <PATH> <INNER> <LOCAL> --tolerate-errors [--report <PATH>] [--unlock-args]
Pulls a file out of the vault like get, but tolerates
per-chunk AEAD failures by writing 4096 zero bytes 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).
Use this only when get fails and you want to recover
what is still readable from a partly-corrupted file. The output
is by definition lossy: damaged chunks become 4 KiB ranges of
zeros at the corresponding offsets.
The --tolerate-errors flag is mandatory. Without it, extract
refuses to run. This is deliberate: a user running the command on
autopilot should never silently capture a zero-padded file thinking
it is the real one.
Examples
Single-file lossy recovery
# `get` is strict - fails on any AEAD error
luksbox get my.lbx /payload broken.bin
# error: crypto: AEAD failure
# `extract --tolerate-errors` recovers the rest of the file
luksbox extract my.lbx /payload recovered.bin --tolerate-errors
# 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.
With a structured failure report
luksbox extract my.lbx /payload recovered.bin --tolerate-errors --report fail.json
cat fail.json
{
"vault": "my.lbx",
"inner": "/payload",
"local": "recovered.bin",
"bytes_written": 12000,
"chunks_ok": 2,
"chunks_bad": 1,
"failures": [
{
"chunk_idx": 1,
"chunk_id": 1,
"generation": 5,
"slot_offset": 1060892,
"error": "crypto: AEAD failure"
}
]
}
After check --json
A common workflow is to first run check --json to discover which
files are affected, then extract each one in turn:
luksbox check my.lbx --json \
| jq -r '.failures | map(.path) | unique[]' \
| while read -r vault_path; do
out=recovered$(echo "$vault_path" | tr / _).bin
luksbox extract my.lbx "$vault_path" "$out" --tolerate-errors --report "$out.report.json"
done
Output details
- File length is preserved: the recovered file is always the same
byte count as the original (truncated to the stored
inode.sizefor normal-mode vaults; left at the 4 KiB-aligned chunk-capacity size for hide-size-mode vaults). - A failed chunk produces 4096 zero bytes in the output at the
matching offset range. There is no marker; the gap is silent in
the file and explicit in the stderr /
--reportlog. - Output file mode is 0600 on Unix, like
get.
Validating the recovery
If you have an external truth source (a HMAC of the original file, a hash database, the original file itself), validate the recovered file against that. Common patterns:
| Truth source | Check |
|---|---|
| You have the original SHA-256 from before damage | sha256sum recovered.bin and compare; it will not match if any chunk was zero-filled |
| The file has internal integrity (e.g. tar with checksums, ZIP central directory, sqlite WAL) | Run the format-specific check (e.g. tar tvf -O recovered.bin, unzip -l recovered.bin) and look for which records are readable |
| The file is a directory archive | Use the report's failures[].chunk_idx and the file's chunk-to-byte mapping (4096 bytes per chunk) to identify which logical record falls in the damaged range |
Limitations
extractonly operates on one inode at a time. Use a shell loop driven bycheck --json(above) to process every affected file.- For a
FLAG_HIDE_SIZE_HEADERvault where chunk 0 is the failed chunk, the real file size is also lost (it lives in the first 8 bytes of chunk 0's plaintext). The recovered file ends up at the padded chunk-capacity size; trim it manually if you know the real size from another source. extractcannot recover a file whose metadata is gone. If the inode itself was wiped from the metadata tree,header-dumpwill not show it andextractcannot find it. The chunks may still be on disk but you would need to identify them blind, which is currently not supported by any LUKSbox subcommand.
Failure modes
| Failure | Cause |
|---|---|
extract is the lossy recovery path: pass --tolerate-errors to acknowledge ... |
The mandatory --tolerate-errors flag was not set |
<inner> is not a file |
Target path resolves to a directory or does not exist |
creating <local>: ... |
Output path could not be created (typically permissions or --no-clobber-style protection) |
See the forensics page for the full recovery flow this subcommand fits into.