VeraCrypt / TrueCrypt format research¶
This is the working reference the implementation is built to. It records the authoritative sources, the on-disk header layout, and the exact unlock pipeline — so the code can be checked against the spec line by line, and the next reader does not have to re-derive VeraCrypt's layout from memory.
Authoritative sources¶
| Source | Used for |
|---|---|
| VeraCrypt documentation — VeraCrypt Volume Format Specification (veracrypt.fr) | Volume-header field layout, salt, CRC-32 fields, master-key area |
VeraCrypt source (Common/Volumes.c, Common/Pkcs5.c, Common/Crypto.c) |
PBKDF2 PRF list + iteration counts, PIM formula, header-decrypt XTS unit, master-key split |
cryptsetup / libcryptsetup (lib/tcrypt/tcrypt.c) |
Independent reimplementation used as a cross-check oracle; aes-xts-plain64 data-unit numbering |
| TrueCrypt 7.1a documentation | Legacy TRUE magic, RIPEMD-160 iteration count, shared header layout |
Volume header (first 512 bytes)¶
The volume header is a 64-byte salt followed by a 448-byte header that is XTS-encrypted with a key derived from the password (data-unit 0, whole 448 bytes as one unit). Once decrypted, offsets (relative to the start of the 448-byte region) are:
0 "VERA" (TrueCrypt: "TRUE") 44 encrypted-area start u64 (BE)
4 format version u16 52 encrypted-area size u64 (BE)
8 CRC-32 of dec[192..448] 64 sector size u32 (BE)
28 hidden-volume size u64 188 CRC-32 of dec[0..188]
36 volume size u64 192 master keys[256]
All multi-byte integers are big-endian. A candidate decryption is accepted
only when the magic is VERA or TRUE and both CRC-32 checks pass:
dec[8..12] == crc32(dec[192..448])(the master-key area), anddec[188..192] == crc32(dec[0..188])(the header fields).
This dual-CRC gate is what lets the reader brute PRF × cipher with no false positive: a wrong key produces random plaintext whose CRCs will not match.
- Normal-volume header: at byte offset 0.
- Hidden-volume header: at byte offset 65 536 (the second 64 KiB). A hidden volume is deniable — the outer volume's own header records a non-zero hidden-volume size at offset 28, which the analyzer surfaces.
Header key derivation — PBKDF2 over five PRFs¶
The header key is PBKDF2-HMAC-<PRF>(password, salt, iterations, 64) — 64 bytes,
i.e. two 256-bit XTS sub-keys. VeraCrypt tries these PRFs:
| PRF | Non-system iterations | PIM formula (non-system) |
|---|---|---|
| SHA-512 | 500 000 | 15000 + PIM*1000 |
| SHA-256 | 500 000 | 15000 + PIM*1000 |
| Whirlpool | 500 000 | 15000 + PIM*1000 |
| Streebog-512 | 500 000 | 15000 + PIM*1000 |
| RIPEMD-160 | 655 331 | PIM*2048 |
A PIM of 0 uses the default (non-system) count above; RIPEMD-160 is the TrueCrypt-compatible legacy PRF. The reader tries every PRF, and within each PRF every cipher, stopping at the first that yields a valid header.
Master key and data-area decryption¶
The decrypted header carries 256 bytes of concatenated master-key material at
offset 192. For a single cipher the reader uses the first key_len bytes — 64
bytes (two 256-bit sub-keys) for AES-256-XTS and Twofish-256-XTS.
The data area is decrypted per 512-byte sector as one XTS data unit. The tweak
(data-unit number) for logical sector LBA is:
i.e. the data-unit numbering is anchored to the physical start of the encrypted
area (aes-xts-plain64 with the area offset folded in), which the cryptsetup
oracle confirms: for vc_1-sha512-xts-aes, encrypted_area_start = 131072, base
data unit 256, so LBA k decrypts with tweak 256 + k.
Ciphers¶
| Cipher | XTS key | Provided by |
|---|---|---|
| AES-256 | two 256-bit sub-keys (64 bytes) | aes + xts-mode (audited RustCrypto) |
| Serpent-256 | two 256-bit sub-keys (64 bytes) | serpent + xts-mode (audited RustCrypto) |
| Twofish-256 | two 256-bit sub-keys (64 bytes) | twofish + xts-mode (audited RustCrypto) |
Cipher cascades¶
VeraCrypt also stacks ciphers into cascades — AES-Twofish, AES-Twofish-Serpent,
Serpent-AES, Serpent-Twofish-AES, Twofish-Serpent. A cascade is applied as nested
XTS layers, and the master-key split follows cryptsetup's TCRYPT_decrypt_hdr
exactly: for an n-cipher cascade given in cryptsetup array order, the cipher at
array index j uses XTS key key[32j .. 32j+32] (primary) ‖ key[32(n+j) ..
32(n+j)+32] (secondary) — i.e. all n primaries first, then all n secondaries —
and the layers are applied in reverse array order (j = n-1 … 0). The same
layout keys both the header brute and the data area, so it is a single code path
(crypto::xts_decrypt_chain). VolumeInfo::cipher_display() renders the chain in
VeraCrypt's own naming (aes-twofish-serpent). Validated byte-for-byte against
real VeraCrypt 1.26.20-minted cascade volumes (see Validation).