rar_stream/crypto/
rar5.rs1use aes::Aes256;
11use cbc::cipher::{BlockDecryptMut, KeyIvInit};
12use pbkdf2::pbkdf2_hmac;
13use sha2::Sha256;
14
15type Aes256CbcDec = cbc::Decryptor<Aes256>;
16
17pub const SIZE_SALT50: usize = 16;
19pub const SIZE_INITV: usize = 16;
20pub const SIZE_PSWCHECK: usize = 8;
21pub const SIZE_PSWCHECK_CSUM: usize = 4;
22pub const CRYPT_BLOCK_SIZE: usize = 16;
23
24#[allow(dead_code)]
27pub const CRYPT5_KDF_LG2_COUNT: u32 = 15;
28
29#[allow(dead_code)]
31pub const CRYPT5_KDF_LG2_COUNT_MAX: u32 = 24;
32
33#[derive(Debug, Clone, PartialEq, Eq)]
35pub struct Rar5EncryptionInfo {
36 pub version: u8,
38 pub flags: u8,
40 pub lg2_count: u8,
42 pub salt: [u8; SIZE_SALT50],
44 pub init_v: [u8; SIZE_INITV],
46 pub psw_check: Option<[u8; SIZE_PSWCHECK]>,
48 pub psw_check_sum: Option<[u8; SIZE_PSWCHECK_CSUM]>,
50}
51
52impl Rar5EncryptionInfo {
53 pub fn parse(data: &[u8]) -> Result<Self, super::CryptoError> {
64 use crate::parsing::rar5::VintReader;
65
66 let mut reader = VintReader::new(data);
67
68 let version = reader.read().ok_or(super::CryptoError::InvalidHeader)? as u8;
69 if version != 0 {
70 return Err(super::CryptoError::UnsupportedVersion(version));
71 }
72
73 let flags = reader.read().ok_or(super::CryptoError::InvalidHeader)? as u8;
74
75 let lg2_bytes = reader
76 .read_bytes(1)
77 .ok_or(super::CryptoError::InvalidHeader)?;
78 let lg2_count = lg2_bytes[0];
79
80 let salt_bytes = reader
81 .read_bytes(SIZE_SALT50)
82 .ok_or(super::CryptoError::InvalidHeader)?;
83 let mut salt = [0u8; SIZE_SALT50];
84 salt.copy_from_slice(salt_bytes);
85
86 let iv_bytes = reader
87 .read_bytes(SIZE_INITV)
88 .ok_or(super::CryptoError::InvalidHeader)?;
89 let mut init_v = [0u8; SIZE_INITV];
90 init_v.copy_from_slice(iv_bytes);
91
92 let (psw_check, psw_check_sum) = if flags & 0x01 != 0 {
93 let check_bytes = reader
94 .read_bytes(SIZE_PSWCHECK)
95 .ok_or(super::CryptoError::InvalidHeader)?;
96 let mut check = [0u8; SIZE_PSWCHECK];
97 check.copy_from_slice(check_bytes);
98
99 let sum_bytes = reader
100 .read_bytes(SIZE_PSWCHECK_CSUM)
101 .ok_or(super::CryptoError::InvalidHeader)?;
102 let mut sum = [0u8; SIZE_PSWCHECK_CSUM];
103 sum.copy_from_slice(sum_bytes);
104
105 use sha2::{Digest, Sha256};
107 let hash = Sha256::digest(check);
108 if hash[..SIZE_PSWCHECK_CSUM] != sum {
109 return Err(super::CryptoError::InvalidHeader);
110 }
111
112 (Some(check), Some(sum))
113 } else {
114 (None, None)
115 };
116
117 Ok(Self {
118 version,
119 flags,
120 lg2_count,
121 salt,
122 init_v,
123 psw_check,
124 psw_check_sum,
125 })
126 }
127}
128
129#[derive(Clone, Debug)]
131pub struct Rar5Crypto {
132 key: [u8; 32],
134 psw_check_value: [u8; 32],
136}
137
138impl Rar5Crypto {
139 pub fn derive_key(password: &str, salt: &[u8; SIZE_SALT50], lg2_count: u8) -> Self {
149 let lg2_count = lg2_count.min(CRYPT5_KDF_LG2_COUNT_MAX as u8);
153 let iterations = 1u32 << lg2_count;
154
155 let mut key = [0u8; 32];
159 pbkdf2_hmac::<Sha256>(password.as_bytes(), salt, iterations, &mut key);
160
161 let mut psw_check_value = [0u8; 32];
164 pbkdf2_hmac::<Sha256>(
165 password.as_bytes(),
166 salt,
167 iterations + 32,
168 &mut psw_check_value,
169 );
170
171 Self {
172 key,
173 psw_check_value,
174 }
175 }
176
177 pub fn verify_password(&self, expected: &[u8; SIZE_PSWCHECK]) -> bool {
179 let mut check = [0u8; SIZE_PSWCHECK];
182 for (i, &byte) in self.psw_check_value.iter().enumerate() {
183 check[i % SIZE_PSWCHECK] ^= byte;
184 }
185 let mut diff = 0u8;
187 for (a, b) in check.iter().zip(expected.iter()) {
188 diff |= a ^ b;
189 }
190 diff == 0
191 }
192
193 pub fn decrypt(
195 &self,
196 iv: &[u8; SIZE_INITV],
197 data: &mut [u8],
198 ) -> Result<(), super::CryptoError> {
199 if data.len() % CRYPT_BLOCK_SIZE != 0 {
201 return Err(super::CryptoError::DecryptionFailed);
202 }
203
204 let decryptor = Aes256CbcDec::new_from_slices(&self.key, iv)
205 .map_err(|_| super::CryptoError::DecryptionFailed)?;
206
207 decryptor
208 .decrypt_padded_mut::<cbc::cipher::block_padding::NoPadding>(data)
209 .map_err(|_| super::CryptoError::DecryptionFailed)?;
210
211 Ok(())
212 }
213
214 pub fn decrypt_to_vec(
216 &self,
217 iv: &[u8; SIZE_INITV],
218 data: &[u8],
219 ) -> Result<Vec<u8>, super::CryptoError> {
220 let mut output = data.to_vec();
221 self.decrypt(iv, &mut output)?;
222 Ok(output)
223 }
224}
225
226impl Drop for Rar5Crypto {
227 fn drop(&mut self) {
228 for byte in &mut self.key {
231 unsafe { std::ptr::write_volatile(byte, 0) };
232 }
233 for byte in &mut self.psw_check_value {
234 unsafe { std::ptr::write_volatile(byte, 0) };
235 }
236 }
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242
243 #[test]
244 fn test_derive_key() {
245 let salt = [0u8; SIZE_SALT50];
247 let crypto = Rar5Crypto::derive_key("password", &salt, 15);
248
249 let crypto2 = Rar5Crypto::derive_key("password", &salt, 15);
251 assert_eq!(crypto.key, crypto2.key);
252 assert_eq!(crypto.psw_check_value, crypto2.psw_check_value);
253
254 let crypto3 = Rar5Crypto::derive_key("different", &salt, 15);
256 assert_ne!(crypto.key, crypto3.key);
257 }
258
259 #[test]
260 fn test_parse_encryption_info() {
261 let mut data = vec![0u8; 35];
263 data[0] = 0; data[1] = 0; data[2] = 15; let info = Rar5EncryptionInfo::parse(&data).unwrap();
269 assert_eq!(info.version, 0);
270 assert_eq!(info.flags, 0);
271 assert_eq!(info.lg2_count, 15);
272 assert!(info.psw_check.is_none());
273 }
274
275 #[test]
276 fn test_parse_encryption_info_with_check() {
277 use sha2::{Digest, Sha256};
278
279 let mut data = vec![0u8; 47];
281 data[0] = 0; data[1] = 1; data[2] = 15; for i in 35..43 {
286 data[i] = i as u8;
287 }
288 let hash = Sha256::digest(&data[35..43]);
290 data[43..47].copy_from_slice(&hash[..4]);
291
292 let info = Rar5EncryptionInfo::parse(&data).unwrap();
293 assert_eq!(info.flags, 1);
294 assert!(info.psw_check.is_some());
295 assert!(info.psw_check_sum.is_some());
296 }
297
298 #[test]
299 fn test_decrypt_encrypted_rar5() {
300 use crate::parsing::rar5::file_header::Rar5FileHeaderParser;
301 use crate::parsing::rar5::VintReader;
302
303 let data = std::fs::read("__fixtures__/encrypted/rar5-encrypted-v5.rar").unwrap();
305
306 let _after_sig = &data[8..];
308
309 let mut pos = 8; loop {
312 assert!(pos + 7 <= data.len(), "Could not find file header");
313
314 let mut reader = VintReader::new(&data[pos + 4..]);
316 let header_size = reader.read().unwrap();
317 let header_type = reader.read().unwrap();
318
319 if header_type == 2 {
320 let (file_header, _) = Rar5FileHeaderParser::parse(&data[pos..]).unwrap();
322
323 if file_header.is_encrypted() {
324 let enc_data = file_header.encryption_info().unwrap();
325
326 let enc_info = Rar5EncryptionInfo::parse(enc_data).unwrap();
327
328 let crypto =
330 Rar5Crypto::derive_key("testpass", &enc_info.salt, enc_info.lg2_count);
331
332 if let Some(ref check) = enc_info.psw_check {
334 let valid = crypto.verify_password(check);
335 assert!(valid, "Password verification failed");
336 }
337
338 let header_total_size = 4 + 1 + header_size as usize; let data_start = pos + header_total_size;
342 let data_end = data_start + file_header.packed_size as usize;
343
344 if data_end <= data.len() {
345 let encrypted_data = &data[data_start..data_end];
346
347 let decrypted = crypto
349 .decrypt_to_vec(&enc_info.init_v, encrypted_data)
350 .unwrap();
351
352 assert_eq!(decrypted.len(), encrypted_data.len());
355
356 }
359 }
360 break;
361 }
362
363 let size_vint_len = {
365 let mut r = VintReader::new(&data[pos + 4..]);
366 r.read().unwrap();
367 r.position()
368 };
369 pos += 4 + size_vint_len + header_size as usize;
370 }
371 }
372
373 #[test]
374 fn test_decrypt_stored_file() {
375 use crate::parsing::rar5::file_header::Rar5FileHeaderParser;
376 use crate::parsing::rar5::VintReader;
377
378 let data = std::fs::read("__fixtures__/encrypted/rar5-encrypted-stored.rar").unwrap();
380
381 let mut pos = 8; loop {
384 assert!(pos + 7 <= data.len(), "Could not find file header");
385
386 let mut reader = VintReader::new(&data[pos + 4..]);
387 let header_size = reader.read().unwrap();
388 let header_type = reader.read().unwrap();
389
390 if header_type == 2 {
391 let (file_header, consumed) = Rar5FileHeaderParser::parse(&data[pos..]).unwrap();
392
393 assert!(file_header.is_encrypted());
394 assert!(
395 file_header.is_stored(),
396 "File should be stored (uncompressed)"
397 );
398
399 let enc_data = file_header.encryption_info().unwrap();
400 let enc_info = Rar5EncryptionInfo::parse(enc_data).unwrap();
401
402 let crypto = Rar5Crypto::derive_key("testpass", &enc_info.salt, enc_info.lg2_count);
403
404 if let Some(ref check) = enc_info.psw_check {
406 assert!(
407 crypto.verify_password(check),
408 "Password verification failed"
409 );
410 }
411
412 let data_start = pos + consumed;
414 let data_end = data_start + file_header.packed_size as usize;
415 let encrypted_data = &data[data_start..data_end];
416
417 let decrypted = crypto
418 .decrypt_to_vec(&enc_info.init_v, encrypted_data)
419 .unwrap();
420
421 let expected = b"Hello, encrypted world!\n";
425 assert!(
426 decrypted.starts_with(expected),
427 "Decrypted content doesn't match. Got: {:?}",
428 String::from_utf8_lossy(&decrypted[..expected.len().min(decrypted.len())])
429 );
430 break;
431 }
432
433 let size_vint_len = {
434 let mut r = VintReader::new(&data[pos + 4..]);
435 r.read().unwrap();
436 r.position()
437 };
438 pos += 4 + size_vint_len + header_size as usize;
439 }
440 }
441}