Skip to main content

veracrypt/
crypto.rs

1//! VeraCrypt key derivation (PBKDF2 over five PRFs) and AES/Serpent/Twofish XTS
2//! decryption. Every primitive is an audited RustCrypto crate.
3
4use aes::cipher::KeyInit;
5use aes::Aes256;
6use serpent::Serpent;
7use twofish::Twofish;
8use xts_mode::Xts128;
9
10use crate::error::{Result, VeraError};
11
12/// A VeraCrypt header PRF (the PBKDF2 hash), with its non-system iteration count.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum Prf {
15    /// SHA-512 (500000 iterations).
16    Sha512,
17    /// SHA-256 (500000 iterations).
18    Sha256,
19    /// Whirlpool (500000 iterations).
20    Whirlpool,
21    /// Streebog-512 (500000 iterations).
22    Streebog,
23    /// RIPEMD-160 (655331 iterations, TrueCrypt-compatible).
24    Ripemd160,
25}
26
27impl Prf {
28    /// All PRFs, in VeraCrypt's try order.
29    #[must_use]
30    pub fn all() -> [Prf; 5] {
31        [
32            Prf::Sha512,
33            Prf::Sha256,
34            Prf::Whirlpool,
35            Prf::Streebog,
36            Prf::Ripemd160,
37        ]
38    }
39
40    /// Human name.
41    #[must_use]
42    pub fn name(self) -> &'static str {
43        match self {
44            Prf::Sha512 => "sha512",
45            Prf::Sha256 => "sha256",
46            Prf::Whirlpool => "whirlpool",
47            Prf::Streebog => "streebog",
48            Prf::Ripemd160 => "ripemd160",
49        }
50    }
51
52    /// Non-system, no-PIM iteration count.
53    #[must_use]
54    pub fn iterations(self) -> u32 {
55        match self {
56            Prf::Ripemd160 => 655_331,
57            _ => 500_000,
58        }
59    }
60
61    /// Iterations for an explicit PIM (personal iterations multiplier). PIM 0 uses
62    /// the default. For SHA-family/Whirlpool/Streebog: `15000 + PIM*1000`;
63    /// RIPEMD-160: `PIM*2048` — matching VeraCrypt's non-system formula.
64    #[must_use]
65    pub fn iterations_pim(self, pim: u32) -> u32 {
66        if pim == 0 {
67            return self.iterations();
68        }
69        match self {
70            Prf::Ripemd160 => pim.saturating_mul(2048),
71            _ => 15_000u32.saturating_add(pim.saturating_mul(1000)),
72        }
73    }
74
75    /// Derive `out_len` bytes with PBKDF2-HMAC using this PRF.
76    pub fn derive(self, password: &[u8], salt: &[u8], iterations: u32, out_len: usize) -> Vec<u8> {
77        let mut out = vec![0u8; out_len];
78        let it = iterations.max(1);
79        match self {
80            Prf::Sha512 => pbkdf2::pbkdf2_hmac::<sha2::Sha512>(password, salt, it, &mut out),
81            Prf::Sha256 => pbkdf2::pbkdf2_hmac::<sha2::Sha256>(password, salt, it, &mut out),
82            Prf::Whirlpool => {
83                pbkdf2::pbkdf2_hmac::<whirlpool::Whirlpool>(password, salt, it, &mut out);
84            }
85            Prf::Streebog => {
86                pbkdf2::pbkdf2_hmac::<streebog::Streebog512>(password, salt, it, &mut out);
87            }
88            Prf::Ripemd160 => {
89                pbkdf2::pbkdf2_hmac::<ripemd::Ripemd160>(password, salt, it, &mut out);
90            }
91        }
92        out
93    }
94}
95
96/// A VeraCrypt data cipher (single-cipher; cipher cascades are a future
97/// extension). All are 256-bit keys in XTS mode.
98#[derive(Debug, Clone, Copy, PartialEq, Eq)]
99pub enum Cipher {
100    /// AES-256 (XTS).
101    Aes,
102    /// Serpent-256 (XTS).
103    Serpent,
104    /// Twofish-256 (XTS).
105    Twofish,
106}
107
108impl Cipher {
109    /// All single ciphers, in VeraCrypt's try order.
110    #[must_use]
111    pub fn all() -> [Cipher; 3] {
112        [Cipher::Aes, Cipher::Serpent, Cipher::Twofish]
113    }
114
115    /// Human name.
116    #[must_use]
117    pub fn name(self) -> &'static str {
118        match self {
119            Cipher::Aes => "aes",
120            Cipher::Serpent => "serpent",
121            Cipher::Twofish => "twofish",
122        }
123    }
124
125    /// XTS key length in bytes (two 256-bit subkeys = 64).
126    #[must_use]
127    pub fn key_len(self) -> usize {
128        64
129    }
130}
131
132/// Decrypt `buffer` in place as XTS with `cipher`, split into `unit_size`-byte
133/// data units; data unit `u` uses tweak `base_unit + u` (little-endian).
134///
135/// # Errors
136/// [`VeraError::Crypto`] if `key` is not 64 bytes.
137pub fn xts_decrypt(
138    cipher: Cipher,
139    key: &[u8],
140    buffer: &mut [u8],
141    unit_size: usize,
142    base_unit: u128,
143) -> Result<()> {
144    if key.len() != 64 {
145        return Err(VeraError::Crypto {
146            what: "xts key must be 64 bytes",
147        });
148    }
149    let (k1, k2) = key.split_at(32);
150    match cipher {
151        Cipher::Aes => decrypt_units(
152            &Xts128::new(Aes256::new(k1.into()), Aes256::new(k2.into())),
153            buffer,
154            unit_size,
155            base_unit,
156        ),
157        Cipher::Serpent => {
158            // The typed Serpent::new() is fixed to 16-byte keys, but new_from_slice
159            // accepts 16..=32 and uses the full 256-bit key schedule (VeraCrypt's).
160            // k1/k2 are exactly 32 bytes here (key.len()==64 checked above), and
161            // new_from_slice accepts 16..=32, so the InvalidLength arm is unreachable.
162            let c1 = Serpent::new_from_slice(k1).map_err(|_| VeraError::Crypto {
163                what: "serpent 256-bit key",
164            })?; // cov:unreachable: k1 is 32 bytes
165            let c2 = Serpent::new_from_slice(k2).map_err(|_| VeraError::Crypto {
166                what: "serpent 256-bit key",
167            })?; // cov:unreachable: k2 is 32 bytes
168            decrypt_units(&Xts128::new(c1, c2), buffer, unit_size, base_unit);
169        }
170        Cipher::Twofish => decrypt_units(
171            &Xts128::new(Twofish::new(k1.into()), Twofish::new(k2.into())),
172            buffer,
173            unit_size,
174            base_unit,
175        ),
176    }
177    Ok(())
178}
179
180fn decrypt_units<C>(xts: &Xts128<C>, buffer: &mut [u8], unit_size: usize, base: u128)
181where
182    C: aes::cipher::BlockCipher + aes::cipher::BlockEncrypt + aes::cipher::BlockDecrypt,
183{
184    for (u, chunk) in buffer.chunks_mut(unit_size).enumerate() {
185        if chunk.len() < 16 {
186            continue; // cov:unreachable: reads are unit-aligned (>= 16)
187        }
188        let tweak = (base + u as u128).to_le_bytes();
189        xts.decrypt_sector(chunk, tweak);
190    }
191}
192
193#[cfg(test)]
194mod tests {
195    use super::*;
196
197    #[test]
198    fn prf_iterations_and_names() {
199        assert_eq!(Prf::Sha512.iterations(), 500_000);
200        assert_eq!(Prf::Ripemd160.iterations(), 655_331);
201        assert_eq!(Prf::Sha512.iterations_pim(0), 500_000);
202        assert_eq!(Prf::Sha512.iterations_pim(10), 25_000);
203        assert_eq!(Prf::Ripemd160.iterations_pim(10), 20_480);
204        assert_eq!(Prf::all().len(), 5);
205        assert_eq!(
206            Cipher::all().map(Cipher::name),
207            ["aes", "serpent", "twofish"]
208        );
209    }
210
211    #[test]
212    fn every_prf_has_a_name_and_derives() {
213        // Names for the four non-SHA512 PRFs (SHA-512 is covered by the oracle).
214        assert_eq!(Prf::Sha256.name(), "sha256");
215        assert_eq!(Prf::Whirlpool.name(), "whirlpool");
216        assert_eq!(Prf::Streebog.name(), "streebog");
217        assert_eq!(Prf::Ripemd160.name(), "ripemd160");
218        // Each PRF derives the requested number of bytes (1 iteration for speed).
219        for prf in Prf::all() {
220            let k = prf.derive(b"password", b"salt", 1, 64);
221            assert_eq!(k.len(), 64, "prf {} derive length", prf.name());
222        }
223    }
224
225    #[test]
226    fn cipher_key_len_is_two_256bit_subkeys() {
227        assert_eq!(Cipher::Aes.key_len(), 64);
228        assert_eq!(Cipher::Twofish.key_len(), 64);
229    }
230
231    #[test]
232    fn pbkdf2_sha512_matches_python() {
233        // PBKDF2-HMAC-SHA512("password","salt",1,32) — cross-checked vs Python.
234        let k = Prf::Sha512.derive(b"password", b"salt", 1, 32);
235        assert_eq!(
236            hex(&k),
237            "867f70cf1ade02cff3752599a3a53dc4af34c7a669815ae5d513554e1c8cf252"
238        );
239    }
240
241    #[test]
242    fn xts_roundtrips_for_each_cipher() {
243        for cipher in Cipher::all() {
244            let key = [0x24u8; 64];
245            let mut buf = vec![0u8; 512];
246            for (i, b) in buf.iter_mut().enumerate() {
247                *b = (i as u8) ^ 0x3c;
248            }
249            let plain = buf.clone();
250            // encrypt via the same primitive at unit base 256, then decrypt
251            let (k1, k2) = key.split_at(32);
252            match cipher {
253                Cipher::Aes => encrypt_one(
254                    &Xts128::new(Aes256::new(k1.into()), Aes256::new(k2.into())),
255                    &mut buf,
256                    256,
257                ),
258                Cipher::Serpent => encrypt_one(
259                    &Xts128::new(
260                        Serpent::new_from_slice(k1).unwrap(),
261                        Serpent::new_from_slice(k2).unwrap(),
262                    ),
263                    &mut buf,
264                    256,
265                ),
266                Cipher::Twofish => encrypt_one(
267                    &Xts128::new(Twofish::new(k1.into()), Twofish::new(k2.into())),
268                    &mut buf,
269                    256,
270                ),
271            }
272            xts_decrypt(cipher, &key, &mut buf, 512, 256).unwrap();
273            assert_eq!(buf, plain, "cipher {}", cipher.name());
274        }
275    }
276
277    #[test]
278    fn xts_rejects_bad_key_len() {
279        let mut b = [0u8; 512];
280        assert!(matches!(
281            xts_decrypt(Cipher::Aes, &[0u8; 48], &mut b, 512, 0),
282            Err(VeraError::Crypto { .. })
283        ));
284    }
285
286    fn encrypt_one<C>(xts: &Xts128<C>, buf: &mut [u8], unit: u128)
287    where
288        C: aes::cipher::BlockCipher + aes::cipher::BlockEncrypt + aes::cipher::BlockDecrypt,
289    {
290        xts.encrypt_sector(buf, unit.to_le_bytes());
291    }
292
293    fn hex(b: &[u8]) -> String {
294        use std::fmt::Write;
295        b.iter().fold(String::new(), |mut s, x| {
296            let _ = write!(s, "{x:02x}");
297            s
298        })
299    }
300}