Skip to main content

veracrypt/
volume.rs

1//! Public API: brute the VeraCrypt header PRF+cipher from a password, recover the
2//! master key, and decrypt the data area.
3
4use std::io::{Read, Seek, SeekFrom};
5
6use crate::crypto::{xts_decrypt, Cipher, Prf};
7use crate::error::{Result, VeraError};
8use crate::header::{
9    Flavor, VeraHeader, HEADER_LEN, HIDDEN_HEADER_OFFSET, NORMAL_HEADER_OFFSET, SALT_LEN,
10    VOLUME_HEADER_LEN,
11};
12
13/// VeraCrypt data-area encryption sector size (bytes).
14const DATA_SECTOR: usize = 512;
15
16/// Namespace for opening a VeraCrypt volume.
17pub struct VeraVolume;
18
19/// The recovered facts about an unlocked volume.
20#[derive(Debug, Clone)]
21pub struct VolumeInfo {
22    /// VeraCrypt or TrueCrypt.
23    pub flavor: Flavor,
24    /// The PRF that decrypted the header.
25    pub prf: Prf,
26    /// The data cipher.
27    pub cipher: Cipher,
28    /// Header format version.
29    pub version: u16,
30    /// Byte offset where the encrypted data area begins.
31    pub encrypted_area_start: u64,
32    /// Size of the encrypted data area in bytes.
33    pub encrypted_area_size: u64,
34}
35
36impl VeraVolume {
37    /// Unlock a VeraCrypt/TrueCrypt volume with `password` (no PIM).
38    ///
39    /// # Errors
40    /// [`VeraError::TooSmall`] if the container is under 512 bytes, or
41    /// [`VeraError::AuthenticationFailed`] if no PRF/cipher yields a valid header.
42    pub fn unlock_with_password<R: Read + Seek>(
43        reader: R,
44        password: &[u8],
45    ) -> Result<DecryptedVolume<R>> {
46        Self::unlock_at(reader, password, 0, NORMAL_HEADER_OFFSET)
47    }
48
49    /// Unlock with an explicit PIM (personal iterations multiplier; 0 = default).
50    ///
51    /// # Errors
52    /// As [`Self::unlock_with_password`].
53    pub fn unlock_with_pim<R: Read + Seek>(
54        reader: R,
55        password: &[u8],
56        pim: u32,
57    ) -> Result<DecryptedVolume<R>> {
58        Self::unlock_at(reader, password, pim, NORMAL_HEADER_OFFSET)
59    }
60
61    /// Unlock the HIDDEN volume with `password` (its header is at 64 KiB). Used to
62    /// access — or prove the presence of — a deniable hidden volume.
63    ///
64    /// # Errors
65    /// As [`Self::unlock_with_password`].
66    pub fn unlock_hidden_with_password<R: Read + Seek>(
67        reader: R,
68        password: &[u8],
69    ) -> Result<DecryptedVolume<R>> {
70        Self::unlock_at(reader, password, 0, HIDDEN_HEADER_OFFSET)
71    }
72
73    /// Unlock the hidden volume with an explicit PIM.
74    ///
75    /// # Errors
76    /// As [`Self::unlock_with_password`].
77    pub fn unlock_hidden_with_pim<R: Read + Seek>(
78        reader: R,
79        password: &[u8],
80        pim: u32,
81    ) -> Result<DecryptedVolume<R>> {
82        Self::unlock_at(reader, password, pim, HIDDEN_HEADER_OFFSET)
83    }
84
85    /// Shared unlock: read the 512-byte volume header at `header_offset`, brute the
86    /// PRF x cipher, and build the decrypting reader.
87    fn unlock_at<R: Read + Seek>(
88        mut reader: R,
89        password: &[u8],
90        pim: u32,
91        header_offset: u64,
92    ) -> Result<DecryptedVolume<R>> {
93        let total_size = reader.seek(SeekFrom::End(0))?;
94        if total_size < header_offset + VOLUME_HEADER_LEN as u64 {
95            return Err(VeraError::TooSmall {
96                got: total_size as usize,
97            });
98        }
99
100        let mut hdr = [0u8; VOLUME_HEADER_LEN];
101        reader.seek(SeekFrom::Start(header_offset))?;
102        read_fill(&mut reader, &mut hdr)?;
103        let salt = &hdr[0..SALT_LEN];
104        let header_ct = &hdr[SALT_LEN..VOLUME_HEADER_LEN];
105
106        for prf in Prf::all() {
107            let hk = prf.derive(password, salt, prf.iterations_pim(pim), 64);
108            for cipher in Cipher::all() {
109                let mut dec = header_ct.to_vec();
110                xts_decrypt(cipher, &hk, &mut dec, HEADER_LEN, 0)?;
111                let Some(h) = VeraHeader::validate(&dec) else {
112                    continue;
113                };
114                let mut master_key = vec![0u8; cipher.key_len()];
115                master_key.copy_from_slice(&h.master_keys[..cipher.key_len()]);
116                return Ok(DecryptedVolume {
117                    reader,
118                    cipher,
119                    master_key,
120                    data_offset: h.encrypted_area_start,
121                    base_unit: u128::from(h.encrypted_area_start / DATA_SECTOR as u64),
122                    total_size,
123                    position: 0,
124                    info: VolumeInfo {
125                        flavor: h.flavor,
126                        prf,
127                        cipher,
128                        version: h.version,
129                        encrypted_area_start: h.encrypted_area_start,
130                        encrypted_area_size: h.encrypted_area_size,
131                    },
132                });
133            }
134        }
135        Err(VeraError::AuthenticationFailed)
136    }
137}
138
139/// A plaintext view of an unlocked VeraCrypt data area.
140pub struct DecryptedVolume<R> {
141    reader: R,
142    cipher: Cipher,
143    master_key: Vec<u8>,
144    data_offset: u64,
145    base_unit: u128,
146    total_size: u64,
147    position: u64,
148    info: VolumeInfo,
149}
150
151impl<R: Read + Seek> DecryptedVolume<R> {
152    /// The recovered volume facts (flavor, PRF, cipher, offsets).
153    #[must_use]
154    pub fn info(&self) -> &VolumeInfo {
155        &self.info
156    }
157
158    /// The recovered master key (sensitive).
159    #[must_use]
160    pub fn master_key(&self) -> &[u8] {
161        &self.master_key
162    }
163
164    /// Size of the decrypted data area in bytes.
165    #[must_use]
166    pub fn data_size(&self) -> u64 {
167        if self.info.encrypted_area_size != 0 {
168            self.info.encrypted_area_size
169        } else {
170            self.total_size.saturating_sub(self.data_offset)
171        }
172    }
173
174    /// Read decrypted data at data-area-relative `offset` into `buf`, filling it
175    /// completely (bytes past the end read back as zero).
176    ///
177    /// # Errors
178    /// Propagates I/O and cipher errors.
179    pub fn read_at(&mut self, offset: u64, buf: &mut [u8]) -> Result<()> {
180        let mut done = 0usize;
181        while done < buf.len() {
182            let pos = offset + done as u64;
183            let unit = pos / DATA_SECTOR as u64;
184            let within = (pos % DATA_SECTOR as u64) as usize;
185            let physical = self.data_offset + unit * DATA_SECTOR as u64;
186
187            let mut ct = [0u8; DATA_SECTOR];
188            self.reader.seek(SeekFrom::Start(physical))?;
189            read_available(&mut self.reader, &mut ct)?;
190            xts_decrypt(
191                self.cipher,
192                &self.master_key,
193                &mut ct,
194                DATA_SECTOR,
195                self.base_unit + u128::from(unit),
196            )?; // cov:unreachable: cipher+64-byte key came from a successful unlock
197
198            let take = (DATA_SECTOR - within).min(buf.len() - done);
199            buf[done..done + take].copy_from_slice(&ct[within..within + take]);
200            done += take;
201        }
202        Ok(())
203    }
204}
205
206impl<R: Read + Seek> Read for DecryptedVolume<R> {
207    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
208        let size = self.data_size();
209        if self.position >= size {
210            return Ok(0);
211        }
212        let n = (buf.len() as u64).min(size - self.position) as usize;
213        self.read_at(self.position, &mut buf[..n])
214            .map_err(|e| std::io::Error::other(e.to_string()))?;
215        self.position += n as u64;
216        Ok(n)
217    }
218}
219
220impl<R: Read + Seek> Seek for DecryptedVolume<R> {
221    fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
222        let size = self.data_size();
223        let new = match pos {
224            SeekFrom::Start(o) => i128::from(o),
225            SeekFrom::End(o) => i128::from(size) + i128::from(o),
226            SeekFrom::Current(o) => i128::from(self.position) + i128::from(o),
227        };
228        if new < 0 {
229            return Err(std::io::Error::new(
230                std::io::ErrorKind::InvalidInput,
231                "seek before start",
232            ));
233        }
234        self.position = new as u64;
235        Ok(self.position)
236    }
237}
238
239fn read_fill<R: Read>(reader: &mut R, buf: &mut [u8]) -> Result<()> {
240    reader.read_exact(buf)?;
241    Ok(())
242}
243
244fn read_available<R: Read>(reader: &mut R, buf: &mut [u8]) -> Result<usize> {
245    let mut filled = 0;
246    while filled < buf.len() {
247        match reader.read(&mut buf[filled..]) {
248            Ok(0) => break,
249            Ok(n) => filled += n,
250            Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => {}
251            Err(e) => return Err(e.into()),
252        }
253    }
254    for b in &mut buf[filled..] {
255        *b = 0;
256    }
257    Ok(filled)
258}
259
260#[cfg(test)]
261mod tests {
262    use std::io::{Cursor, Read as _, Seek as _, SeekFrom};
263
264    use aes::cipher::KeyInit;
265    use aes::Aes256;
266    use xts_mode::Xts128;
267
268    use super::*;
269    use crate::crypto::Prf;
270
271    const PASSWORD: &[u8] = b"correct horse";
272    const DATA_START: u64 = 512; // encrypted area begins right after the 512-byte header
273    const DATA_SECTORS: usize = 3;
274
275    /// AES-256-XTS encrypt (the inverse of `crypto::decrypt_units`) for fixtures.
276    fn xts_encrypt_aes(key64: &[u8; 64], buf: &mut [u8], unit_size: usize, base: u128) {
277        let (k1, k2) = key64.split_at(32);
278        let xts = Xts128::new(Aes256::new(k1.into()), Aes256::new(k2.into()));
279        for (u, chunk) in buf.chunks_mut(unit_size).enumerate() {
280            xts.encrypt_sector(chunk, (base + u as u128).to_le_bytes());
281        }
282    }
283
284    /// Assemble a synthetic AES-256-XTS VeraCrypt container in memory and return
285    /// `(container_bytes, plaintext_data_area)`. The header is a real VeraCrypt
286    /// header (VERA magic + both CRC-32s) XTS-encrypted under the SHA-512 header
287    /// key at PIM 1, so the crate's own brute recovers it. Correctness of the
288    /// crypto itself is proven by the Tier-1 oracle; this only drives the paths.
289    fn build_volume() -> (Vec<u8>, Vec<u8>) {
290        build_volume_with(true)
291    }
292
293    fn build_volume_with(declare_size: bool) -> (Vec<u8>, Vec<u8>) {
294        let salt = [0x11u8; SALT_LEN];
295        let master_key = [0x24u8; 64];
296
297        // Decrypted 448-byte header.
298        let mut dec = [0u8; HEADER_LEN];
299        dec[0..4].copy_from_slice(b"VERA");
300        dec[4..6].copy_from_slice(&5u16.to_be_bytes());
301        let data_size = (DATA_SECTORS * DATA_SECTOR) as u64;
302        dec[36..44].copy_from_slice(&(DATA_START + data_size).to_be_bytes()); // volume size
303        dec[44..52].copy_from_slice(&DATA_START.to_be_bytes()); // encrypted-area start
304                                                                // 0 = "size not declared" ⇒ reader falls back to total_size - data_offset.
305        let declared = if declare_size { data_size } else { 0 };
306        dec[52..60].copy_from_slice(&declared.to_be_bytes()); // encrypted-area size
307        dec[64..68].copy_from_slice(&512u32.to_be_bytes());
308        dec[192..256].copy_from_slice(&master_key); // master-key material
309        let crc_mk = crc32fast::hash(&dec[192..448]);
310        dec[8..12].copy_from_slice(&crc_mk.to_be_bytes());
311        let crc_hdr = crc32fast::hash(&dec[0..188]);
312        dec[188..192].copy_from_slice(&crc_hdr.to_be_bytes());
313
314        // Encrypt the header with the SHA-512 header key (PIM 1 = 16000 iterations).
315        let header_key = Prf::Sha512.derive(PASSWORD, &salt, Prf::Sha512.iterations_pim(1), 64);
316        let mut header_ct = dec.to_vec();
317        let hk: [u8; 64] = header_key.try_into().unwrap();
318        xts_encrypt_aes(&hk, &mut header_ct, HEADER_LEN, 0);
319
320        // Plaintext data area, then encrypt each sector at tweak base_unit + lba.
321        let mut plain = vec![0u8; DATA_SECTORS * DATA_SECTOR];
322        for (i, b) in plain.iter_mut().enumerate() {
323            *b = (i as u8).wrapping_mul(7) ^ 0xa5;
324        }
325        let base_unit = u128::from(DATA_START / DATA_SECTOR as u64);
326        let mut data_ct = plain.clone();
327        xts_encrypt_aes(&master_key, &mut data_ct, DATA_SECTOR, base_unit);
328
329        let mut container = Vec::new();
330        container.extend_from_slice(&salt);
331        container.extend_from_slice(&header_ct);
332        container.extend_from_slice(&data_ct);
333        (container, plain)
334    }
335
336    #[test]
337    fn hermetic_unlock_and_read_roundtrip() {
338        let (container, plain) = build_volume();
339        let mut vol = VeraVolume::unlock_with_pim(Cursor::new(container), PASSWORD, 1)
340            .expect("unlock synthetic volume");
341
342        assert_eq!(vol.info().prf.name(), "sha512");
343        assert_eq!(vol.info().cipher.name(), "aes");
344        assert_eq!(vol.info().flavor, Flavor::VeraCrypt);
345        assert_eq!(vol.info().version, 5);
346        assert_eq!(vol.info().encrypted_area_start, DATA_START);
347        assert_eq!(vol.master_key().len(), 64);
348        assert_eq!(vol.data_size(), plain.len() as u64);
349
350        // Sector-by-sector read_at.
351        for lba in 0..DATA_SECTORS as u64 {
352            let mut buf = [0u8; DATA_SECTOR];
353            vol.read_at(lba * DATA_SECTOR as u64, &mut buf).unwrap();
354            let want = &plain[(lba as usize) * DATA_SECTOR..(lba as usize + 1) * DATA_SECTOR];
355            assert_eq!(&buf[..], want, "sector {lba}");
356        }
357
358        // Unaligned read spanning a sector boundary.
359        let mut span = [0u8; 10];
360        vol.read_at(510, &mut span).unwrap();
361        assert_eq!(&span[..], &plain[510..520]);
362    }
363
364    #[test]
365    fn hermetic_read_and_seek_traits() {
366        let (container, plain) = build_volume();
367        let mut vol =
368            VeraVolume::unlock_with_pim(Cursor::new(container), PASSWORD, 1).expect("unlock");
369
370        // Read the whole data area via the Read impl.
371        let mut all = Vec::new();
372        vol.read_to_end(&mut all).unwrap();
373        assert_eq!(all, plain);
374        // At EOF the Read impl yields 0.
375        assert_eq!(vol.read(&mut [0u8; 16]).unwrap(), 0);
376
377        // Seek from End then Current, and reject a negative seek.
378        let pos = vol.seek(SeekFrom::End(-512)).unwrap();
379        assert_eq!(pos, (plain.len() - 512) as u64);
380        assert_eq!(vol.seek(SeekFrom::Current(0)).unwrap(), pos);
381        assert_eq!(vol.seek(SeekFrom::Start(0)).unwrap(), 0);
382        assert!(vol.seek(SeekFrom::Current(-1)).is_err());
383    }
384
385    #[test]
386    fn hidden_offset_too_small_errors() {
387        // A container large enough for a normal header but not the hidden one.
388        let small = vec![0u8; VOLUME_HEADER_LEN];
389        assert!(matches!(
390            VeraVolume::unlock_hidden_with_password(Cursor::new(small), PASSWORD),
391            Err(VeraError::TooSmall { .. })
392        ));
393    }
394
395    #[test]
396    fn hidden_pim_offset_too_small_errors() {
397        // Big enough for a normal header but not the hidden one at 64 KiB.
398        let small = vec![0u8; VOLUME_HEADER_LEN];
399        assert!(matches!(
400            VeraVolume::unlock_hidden_with_pim(Cursor::new(small), PASSWORD, 1),
401            Err(VeraError::TooSmall { .. })
402        ));
403    }
404
405    #[test]
406    fn too_small_container_errors() {
407        assert!(matches!(
408            VeraVolume::unlock_with_password(Cursor::new(vec![0u8; 100]), PASSWORD),
409            Err(VeraError::TooSmall { got }) if got == 100
410        ));
411    }
412
413    #[test]
414    fn wrong_password_fails() {
415        let (container, _) = build_volume();
416        assert!(matches!(
417            VeraVolume::unlock_with_pim(Cursor::new(container), b"wrong", 1),
418            Err(VeraError::AuthenticationFailed)
419        ));
420    }
421
422    #[test]
423    fn undeclared_size_falls_back_to_container_length() {
424        let (container, plain) = build_volume_with(false);
425        let vol = VeraVolume::unlock_with_pim(Cursor::new(container), PASSWORD, 1).expect("unlock");
426        // encrypted_area_size == 0 ⇒ data_size = total_size - data_offset.
427        assert_eq!(vol.data_size(), plain.len() as u64);
428        assert_eq!(vol.info().encrypted_area_size, 0);
429    }
430
431    #[test]
432    fn read_available_handles_eof_interrupt_and_hard_error() {
433        use std::io;
434
435        // EOF immediately -> break, then zero-fill the whole buffer.
436        struct Eof;
437        impl io::Read for Eof {
438            fn read(&mut self, _: &mut [u8]) -> io::Result<usize> {
439                Ok(0)
440            }
441        }
442        let mut buf = [0xffu8; 8];
443        assert_eq!(read_available(&mut Eof, &mut buf).unwrap(), 0);
444        assert_eq!(buf, [0u8; 8]);
445
446        // Interrupted once, then one byte, then EOF (covers the Interrupted arm).
447        struct Flaky(u8);
448        impl io::Read for Flaky {
449            fn read(&mut self, b: &mut [u8]) -> io::Result<usize> {
450                self.0 += 1;
451                match self.0 {
452                    1 => Err(io::Error::new(io::ErrorKind::Interrupted, "eintr")),
453                    2 => {
454                        b[0] = 0xAB;
455                        Ok(1)
456                    }
457                    _ => Ok(0),
458                }
459            }
460        }
461        let mut b2 = [0u8; 4];
462        assert_eq!(read_available(&mut Flaky(0), &mut b2).unwrap(), 1);
463        assert_eq!(b2[0], 0xAB);
464
465        // A hard error propagates.
466        struct Boom;
467        impl io::Read for Boom {
468            fn read(&mut self, _: &mut [u8]) -> io::Result<usize> {
469                Err(io::Error::other("boom"))
470            }
471        }
472        let mut b3 = [0u8; 4];
473        assert!(read_available(&mut Boom, &mut b3).is_err());
474    }
475}