Cryptography
The primitives LUKSbox uses and where each one comes from.
Primitives reference
| Purpose | Primitive | Source crate |
|---|---|---|
| File / metadata AEAD | AES-256-GCM-SIV (default; RFC 8452, nonce-misuse-resistant) | RustCrypto aes-gcm-siv |
| Alternate AEAD | AES-256-GCM (legacy compat) | RustCrypto aes-gcm |
| Alternate AEAD | ChaCha20-Poly1305 (constant-time on every CPU) | RustCrypto chacha20poly1305 |
| Header MAC | HMAC-SHA256 | RustCrypto hmac + sha2 |
| Subkey derivation | HKDF-SHA256 with per-purpose info strings |
RustCrypto hkdf |
| Passphrase stretching | Argon2id (interactive: 256 MiB / t=3 / p=4; sensitive: 1 GiB / t=4 / p=8) | RustCrypto argon2 |
| FIDO2 hmac-secret transport | ECDH-P256 + AES-256-CBC + HMAC-SHA256 (CTAP2 section 6.5) | hand-rolled in luksbox-fido2/src/protocol.rs (verified against OpenSSL test vectors); libfido2 / webauthn.dll for the device side |
| Post-quantum KEM (hybrid mode) | ML-KEM-768 or ML-KEM-1024 (FIPS 203) | RustCrypto ml-kem |
| RNG | OS RNG (getrandom / BCryptGenRandom / SecRandomCopyBytes) |
rand_core::OsRng |
Why these specific choices
AES-256-GCM-SIV as default
Random 96-bit nonces under vanilla AES-GCM have a NIST-recommended bound of 2^32 messages per key. AES-GCM-SIV (RFC 8452) is nonce-misuse-resistant: a nonce collision under the same key reveals only that two messages had identical (key, nonce, AAD, plaintext) tuples - never the GHASH key. Same 12-byte nonce + 16-byte tag wire shape as vanilla GCM, so on-disk format is byte-identical regardless of which AES variant a vault was created with.
Argon2id for passphrase stretching
Memory-hard, side-channel-resistant, NIST-recommended (SP 800-63B allows it). The "interactive" preset is calibrated for 540 ms on a modern CPU, the "sensitive" preset for 3.2 sec. See Passphrase keyslots for the math behind these choices.
HKDF-SHA256 with explicit info strings
Every per-purpose subkey derivation uses a unique info string
(lbx:header-mac/v1, lbx:metadata-key/v1, lbx:file/v1: || file_id,
etc.) so subkeys for different purposes are domain-separated even
when derived from the same MVK. The full list of info strings is
in docs/CRYPTO_SPEC.md Section 3.3.
ML-KEM (FIPS 203) for the post-quantum layer
NIST-standardised in August 2024. ML-KEM-768 is security category 3 (approximately AES-192 strength); ML-KEM-1024 is category 5 (approximately AES-256). The hybrid construction combines the classical Argon2id-derived KEK with the ML-KEM shared secret via HKDF-SHA256, so a future cryptanalytic break in EITHER family doesn't break the vault - only a break in BOTH does.
Key derivation tree
Everything in the vault traces back to the Master Volume Key (MVK) by HKDF-SHA256 with a unique label per purpose:
flowchart TD
RNG["OS RNG (getrandom / getentropy / BCryptGenRandom)"] --> MVK
MVK["Master Volume Key (MVK)
32 random bytes
in-memory only
(Linux: memfd_secret)"]
MVK -->|HKDF info = lbx:header-mac/v1| MAC["mac_key
HMAC over the 8 KiB header"]
MVK -->|HKDF info = lbx:metadata-key/v1| META["meta_key
AEAD over the metadata blob"]
MVK -->|HKDF info = lbx:file/v1: + file_id| FK["file_key (one per file)
AEAD over each chunk"]
MVK -->|HKDF info = lbx:anchor-mac/v1| ANC["anchor_key
HMAC over the rollback anchor"]
classDef root fill:#fff4e6,stroke:#d97706,stroke-width:2px;
classDef leaf fill:#ecfdf5,stroke:#059669;
class MVK root;
class MAC,META,FK,ANC leaf;
The four arrows out of the MVK use four distinct info strings,
so the four subkeys are independent under the HKDF security
argument: a leak of any one subkey does not let an attacker
recover the MVK or any sibling subkey. The per-file file_key
also mixes the file's file_id into its info string, so two
files in the same vault have unrelated AEAD keys even if their
plaintext is identical.
For the full per-operation crypto walkthrough (15 scenarios from vault
creation through tampering detection), see
docs/CRYPTO_SPEC.md
in the source repository.
What we DON'T use (and why)
| Not used | Why |
|---|---|
| SHA-1, MD5 | Cryptographically broken |
| AES-XTS (LUKS-style) | XTS leaks more than AEAD on tamper - we want authenticated encryption |
| RSA | Quantum-broken; ML-KEM is the modern KEM |
| PBKDF2 | Not memory-hard; Argon2id is strictly better |
| scrypt | Memory-hard but Argon2id won the PHC competition specifically for this use case |
| BLAKE3 | Excellent hash, but SHA-256 has wider hardware acceleration + standardisation |