Validation¶
Correctness is proven against an independent third-party oracle —
cryptsetup 2.7.0, the reference LUKS implementation — never against a
self-authored round-trip (which would only prove self-consistency, the LZNT1
trap). LUKS decryption produces a value an independent oracle can check
byte-for-byte, so an oracle is mandatory, and we have one for both format
versions.
These are Tier-2 oracles: we minted the containers on an Ubuntu 24.04 guest
with cryptsetup luksFormat, but the ground-truth plaintext is derived
independently by cryptsetup opening the same image with the same passphrase.
The scenario is ours; the answer key is not. Both images are env-gated and not
committed; the tests skip cleanly when the env var is unset.
Tier-2 — LUKS1 luks1.img vs cryptsetup¶
- Artifact:
luks1.img, 32 MiB, md5409f986e0bf5013a9c93661fe3c39589. - Header: cipher
aes/xts-plain64, master-key 512 bits (AES-256-XTS), hashsha256, MK iterations 1000 (forced low for a fast oracle), payload offset 4096 sectors (2 MiB), UUIDb22690e1-a392-4ecc-83b1-c1cf21200116. - Passphrase:
luks-TEST. - Unlock chain:
PBKDF2-HMAC-sha256(passphrase, keyslot.salt, keyslot.iterations)→ keyslot key → AES-XTS decrypt of the keyslot key material → AF-merge (anti-forensic, sha256, 4000 stripes) → master key → verifyPBKDF2(master, mk-digest-salt, mk-digest-iter) == mk-digest→ decrypt payload sectorsaes-xts-plain64(tweak = payload-relative sector number).
The env-gated test core/tests/oracle_luks1.rs (LUKS1_ORACLE) unlocks the image
and asserts these decrypted-sector SHA-256 digests against the cryptsetup
mapper output:
| Payload LBA | SHA-256 |
|---|---|
| 0 | c9d8e3352f9f790d8b0be13cb1c18ed7963009888be04acc065ee5efbd934076 |
| 1 | cb287e82f2af042cd65f1e72f3e4738a0a6dba5d9941b0dfd476fd5c8ea27cb0 |
| 2 | 492524d6fe4f2fe9309718c3d531078342c32bde0da7dd9d111bd17812f3cbe9 |
| 16 | 037f30c89ab88aa9d58417d2d95adb10b22f8691a17f7db71169ad3194ace706 |
| 100 | d3c7f1b00475d01452cdbfcb4d9666dddecb3c633e67206016be05980293b57c |
| 199 | 3e9dc47b357b72b7256d9fc953ee61be87d391ce34b6d3932ac5421294a84ae2 |
LUKS1_ORACLE=/tmp/luks-oracle/luks1.img \
cargo test -p luks-core --test oracle_luks1 -- --nocapture
A passing run is the end-to-end proof of the whole LUKS1 chain: a wrong keyslot KDF, AF-merge, or master key fails the mk-digest check and never reaches a matching plaintext.
Tier-2 — LUKS2 luks2.img vs cryptsetup¶
- Artifact:
luks2.img, 48 MiB, md510fdf8a4b4b5914a0e05ac5fb5c9f06d. - Header: cipher
aes-xts-plain64, key 512 bits (AES-256-XTS), sector size 4096, data offset 16 MiB (16777216). - KDF: Argon2id (time 4, memory 32 KiB, cpus 1); AF
luks1splitter with 4000 stripes / sha256; digestpbkdf2sha256, 1000 iterations. - Passphrase:
luks-TEST. - KEY XTS insight: the 4096-byte data units use the 512-based
plain64tweak =block * 8(the dm-crypt default, noiv_large_sectors), so a 512-byte read at LBA n decrypts within the 4096-byte data unit at the correct offset.
The env-gated test core/tests/oracle_luks2.rs (LUKS2_ORACLE) unlocks the image
through the unified unlock_with_passphrase entry point (which auto-detects
version 2) and asserts these decrypted-sector SHA-256 digests, in 512-byte units,
against cryptsetup:
| Payload LBA (512 B) | SHA-256 |
|---|---|
| 0 | 205671f7bc4ba5c0589baa514222f66e6e2c62e464f7ed0a00f7f451439c4bac |
| 1 | 091344e5c2087f37fad9fc83b68167ae7136cb88f97310393c2289f40079ce5a |
| 2 | 510bc5bda3f1659b0fc8a7235ec927025d4363c823964acaa1c6795f9186080c |
| 16 | 3d4ac612bd846109b5e6c7ebd664e3a2f29883b1b9a87fff39643aee2efdb36e |
| 100 | 0dd58d3c600646aa31968daa1a4231d7b1067ccbe94d2806d94579938271334c |
| 199 | dd0399377354f80af98f0c511142ad5b59b16d04754bb0c97ed0b98a2114283a |
LUKS2_ORACLE=/tmp/luks-oracle/luks2.img \
cargo test -p luks-core --test oracle_luks2 -- --nocapture
A passing run proves the LUKS2 JSON metadata parse, the Argon2id keyslot derivation, the AF-merge, the digest verification, and the 4096-byte-sector XTS read path together.
Both images were minted with cryptsetup luksFormat on the "Ubuntu 24.04 (with
Rosetta)" Parallels guest; provenance is recorded in
tests/data/README.md.
Tier-3 — structural unit tests¶
LUKS1 phdr field parsing and keyslot active/disabled filtering, LUKS2
JSON keyslot/segment/digest parsing (PBKDF2 and Argon2 variants), and the
AF split/merge round-trip (sha1 / sha256 / sha512, full and short final chunk)
are exercised over hand-built byte buffers. These are regression scaffolding
under the Tier-2 oracles — a round-trip proves self-consistency only; the real
correctness proof for the full pipeline is the cryptsetup oracle.
Fuzzing¶
core/fuzz/fuzz_targets/ drives the untrusted-input parsers over arbitrary
bytes; invariant: never panic.
| Target | Surface |
|---|---|
luks1_header |
Luks1Header::parse |
luks2_header |
Luks2Header::parse (binary header + JSON area) |
af_merge |
the anti-forensic merge over crafted material / stripe counts |
unlock |
the full LuksVolume::unlock_with_passphrase pipeline |