Skip to main content

veracrypt/
header.rs

1//! VeraCrypt/TrueCrypt volume-header field layout and validation.
2//!
3//! The 512-byte volume header is `salt[64]` followed by a 448-byte header that is
4//! XTS-encrypted with a key derived from the password. Once decrypted, offsets
5//! (relative to the start of the 448-byte decrypted region) are:
6//!
7//! ```text
8//!    0  "VERA" (TrueCrypt: "TRUE")      44  encrypted-area start  u64
9//!    4  format version   u16            52  encrypted-area size   u64
10//!    8  CRC-32 of dec[192..448]         64  sector size           u32
11//!   28  hidden volume size u64         188  CRC-32 of dec[0..188]
12//!   36  volume size      u64           192  master keys[256]
13//! ```
14
15/// Length of the salt prefix.
16pub const SALT_LEN: usize = 64;
17/// Length of the encrypted header following the salt.
18pub const HEADER_LEN: usize = 448;
19/// Total volume-header length (`salt + header`).
20pub const VOLUME_HEADER_LEN: usize = SALT_LEN + HEADER_LEN;
21/// Offset of the standard-volume header in the container.
22pub const NORMAL_HEADER_OFFSET: u64 = 0;
23/// Offset of the hidden-volume header in the container (second 64 KiB).
24pub const HIDDEN_HEADER_OFFSET: u64 = 65_536;
25/// The VeraCrypt magic at decrypted offset 0.
26pub const MAGIC_VERA: &[u8; 4] = b"VERA";
27/// The TrueCrypt magic at decrypted offset 0.
28pub const MAGIC_TRUE: &[u8; 4] = b"TRUE";
29
30fn be_u32(d: &[u8], o: usize) -> u32 {
31    let mut b = [0u8; 4];
32    if let Some(s) = d.get(o..o + 4) {
33        b.copy_from_slice(s);
34    }
35    u32::from_be_bytes(b)
36}
37
38fn be_u64(d: &[u8], o: usize) -> u64 {
39    let mut b = [0u8; 8];
40    if let Some(s) = d.get(o..o + 8) {
41        b.copy_from_slice(s);
42    }
43    u64::from_be_bytes(b)
44}
45
46/// Whether this container is a TrueCrypt (not VeraCrypt) header.
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub enum Flavor {
49    /// VeraCrypt (`VERA` magic).
50    VeraCrypt,
51    /// TrueCrypt (`TRUE` magic).
52    TrueCrypt,
53}
54
55/// The forensically-relevant fields of a decrypted VeraCrypt header.
56#[derive(Debug, Clone)]
57pub struct VeraHeader {
58    /// VeraCrypt or TrueCrypt.
59    pub flavor: Flavor,
60    /// Header format version.
61    pub version: u16,
62    /// Byte offset where the encrypted data area begins.
63    pub encrypted_area_start: u64,
64    /// Size of the encrypted data area in bytes.
65    pub encrypted_area_size: u64,
66    /// Declared volume size in bytes.
67    pub volume_size: u64,
68    /// Hidden-volume size (non-zero only in an outer volume's header).
69    pub hidden_size: u64,
70    /// Sector size (usually 512).
71    pub sector_size: u32,
72    /// The 256-byte concatenated master-key material.
73    pub master_keys: [u8; 256],
74}
75
76impl VeraHeader {
77    /// Validate a candidate 448-byte decrypted header: the magic must be `VERA`
78    /// or `TRUE` *and* both CRC-32 checks must pass. Returns `None` otherwise, so
79    /// a wrong PRF/cipher/password is rejected (no false positives).
80    #[must_use]
81    pub fn validate(dec: &[u8]) -> Option<VeraHeader> {
82        if dec.len() < HEADER_LEN {
83            return None;
84        }
85        let flavor = match dec.get(0..4)? {
86            m if m == MAGIC_VERA => Flavor::VeraCrypt,
87            m if m == MAGIC_TRUE => Flavor::TrueCrypt,
88            _ => return None,
89        };
90        // CRC-32 of the master-key area and of the header fields.
91        if be_u32(dec, 8) != crc32fast::hash(dec.get(192..448)?) {
92            return None;
93        }
94        if be_u32(dec, 188) != crc32fast::hash(dec.get(0..188)?) {
95            return None;
96        }
97        let mut master_keys = [0u8; 256];
98        master_keys.copy_from_slice(dec.get(192..448)?);
99        Some(VeraHeader {
100            flavor,
101            version: (be_u32(dec, 4) >> 16) as u16,
102            encrypted_area_start: be_u64(dec, 44),
103            encrypted_area_size: be_u64(dec, 52),
104            volume_size: be_u64(dec, 36),
105            hidden_size: be_u64(dec, 28),
106            sector_size: be_u32(dec, 64),
107            master_keys,
108        })
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115
116    /// Build a well-formed decrypted header for `magic`, with the two CRC-32 fields
117    /// computed over the correct regions (the same construction VeraCrypt writes).
118    fn valid_header(magic: &[u8; 4]) -> [u8; HEADER_LEN] {
119        let mut dec = [0u8; HEADER_LEN];
120        dec[0..4].copy_from_slice(magic);
121        dec[4..6].copy_from_slice(&5u16.to_be_bytes()); // version 5
122        dec[28..36].copy_from_slice(&4096u64.to_be_bytes()); // hidden size
123        dec[36..44].copy_from_slice(&1_048_576u64.to_be_bytes()); // volume size
124        dec[44..52].copy_from_slice(&131_072u64.to_be_bytes()); // encrypted-area start
125        dec[52..60].copy_from_slice(&36_864u64.to_be_bytes()); // encrypted-area size
126        dec[64..68].copy_from_slice(&512u32.to_be_bytes()); // sector size
127        for (i, b) in dec[192..448].iter_mut().enumerate() {
128            *b = (i as u8) ^ 0x5a; // master-key material
129        }
130        let crc_mk = crc32fast::hash(&dec[192..448]);
131        dec[8..12].copy_from_slice(&crc_mk.to_be_bytes());
132        let crc_hdr = crc32fast::hash(&dec[0..188]);
133        dec[188..192].copy_from_slice(&crc_hdr.to_be_bytes());
134        dec
135    }
136
137    #[test]
138    fn validates_vera_and_true_magic() {
139        let v = VeraHeader::validate(&valid_header(MAGIC_VERA)).expect("VERA header");
140        assert_eq!(v.flavor, Flavor::VeraCrypt);
141        assert_eq!(v.version, 5);
142        assert_eq!(v.encrypted_area_start, 131_072);
143        assert_eq!(v.encrypted_area_size, 36_864);
144        assert_eq!(v.volume_size, 1_048_576);
145        assert_eq!(v.hidden_size, 4096);
146        assert_eq!(v.sector_size, 512);
147
148        let t = VeraHeader::validate(&valid_header(MAGIC_TRUE)).expect("TRUE header");
149        assert_eq!(t.flavor, Flavor::TrueCrypt);
150    }
151
152    #[test]
153    fn rejects_short_input() {
154        assert!(VeraHeader::validate(&[0u8; HEADER_LEN - 1]).is_none());
155    }
156
157    #[test]
158    fn rejects_bad_magic() {
159        let mut dec = valid_header(MAGIC_VERA);
160        dec[0] = b'X'; // corrupt magic (invalidates both CRC and signature)
161        assert!(VeraHeader::validate(&dec).is_none());
162    }
163
164    #[test]
165    fn rejects_master_key_crc_mismatch() {
166        let mut dec = valid_header(MAGIC_VERA);
167        dec[400] ^= 0xff; // flip a master-key byte without fixing crc@8
168        assert!(VeraHeader::validate(&dec).is_none());
169    }
170
171    #[test]
172    fn rejects_header_field_crc_mismatch() {
173        let mut dec = valid_header(MAGIC_VERA);
174        dec[64] ^= 0xff; // flip sector-size (in the crc@188 region) without fixing it
175        assert!(VeraHeader::validate(&dec).is_none());
176    }
177}