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