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 (Some(check), Some(sum))
106 } else {
107 (None, None)
108 };
109
110 Ok(Self {
111 version,
112 flags,
113 lg2_count,
114 salt,
115 init_v,
116 psw_check,
117 psw_check_sum,
118 })
119 }
120}
121
122#[derive(Clone, Debug)]
124pub struct Rar5Crypto {
125 key: [u8; 32],
127 psw_check_value: [u8; 32],
129}
130
131impl Rar5Crypto {
132 pub fn derive_key(password: &str, salt: &[u8; SIZE_SALT50], lg2_count: u8) -> Self {
139 let iterations = 1u32 << lg2_count;
140
141 let mut key = [0u8; 32];
146 pbkdf2_hmac::<Sha256>(password.as_bytes(), salt, iterations, &mut key);
147
148 let mut psw_check_value = [0u8; 32];
151 pbkdf2_hmac::<Sha256>(
152 password.as_bytes(),
153 salt,
154 iterations + 32,
155 &mut psw_check_value,
156 );
157
158 Self {
159 key,
160 psw_check_value,
161 }
162 }
163
164 pub fn verify_password(&self, expected: &[u8; SIZE_PSWCHECK]) -> bool {
166 let mut check = [0u8; SIZE_PSWCHECK];
169 for (i, &byte) in self.psw_check_value.iter().enumerate() {
170 check[i % SIZE_PSWCHECK] ^= byte;
171 }
172 check == *expected
173 }
174
175 pub fn decrypt(
177 &self,
178 iv: &[u8; SIZE_INITV],
179 data: &mut [u8],
180 ) -> Result<(), super::CryptoError> {
181 if data.len() % CRYPT_BLOCK_SIZE != 0 {
183 return Err(super::CryptoError::DecryptionFailed);
184 }
185
186 let decryptor = Aes256CbcDec::new_from_slices(&self.key, iv)
187 .map_err(|_| super::CryptoError::DecryptionFailed)?;
188
189 decryptor
190 .decrypt_padded_mut::<cbc::cipher::block_padding::NoPadding>(data)
191 .map_err(|_| super::CryptoError::DecryptionFailed)?;
192
193 Ok(())
194 }
195
196 pub fn decrypt_to_vec(
198 &self,
199 iv: &[u8; SIZE_INITV],
200 data: &[u8],
201 ) -> Result<Vec<u8>, super::CryptoError> {
202 let mut output = data.to_vec();
203 self.decrypt(iv, &mut output)?;
204 Ok(output)
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211
212 #[test]
213 fn test_derive_key() {
214 let salt = [0u8; SIZE_SALT50];
216 let crypto = Rar5Crypto::derive_key("password", &salt, 15);
217
218 let crypto2 = Rar5Crypto::derive_key("password", &salt, 15);
220 assert_eq!(crypto.key, crypto2.key);
221 assert_eq!(crypto.psw_check_value, crypto2.psw_check_value);
222
223 let crypto3 = Rar5Crypto::derive_key("different", &salt, 15);
225 assert_ne!(crypto.key, crypto3.key);
226 }
227
228 #[test]
229 fn test_parse_encryption_info() {
230 let mut data = vec![0u8; 35];
232 data[0] = 0; data[1] = 0; data[2] = 15; let info = Rar5EncryptionInfo::parse(&data).unwrap();
238 assert_eq!(info.version, 0);
239 assert_eq!(info.flags, 0);
240 assert_eq!(info.lg2_count, 15);
241 assert!(info.psw_check.is_none());
242 }
243
244 #[test]
245 fn test_parse_encryption_info_with_check() {
246 let mut data = vec![0u8; 47];
248 data[0] = 0; data[1] = 1; data[2] = 15; for i in 35..43 {
253 data[i] = i as u8;
254 }
255
256 let info = Rar5EncryptionInfo::parse(&data).unwrap();
257 assert_eq!(info.flags, 1);
258 assert!(info.psw_check.is_some());
259 assert!(info.psw_check_sum.is_some());
260 }
261
262 #[test]
263 fn test_decrypt_encrypted_rar5() {
264 use crate::parsing::rar5::file_header::Rar5FileHeaderParser;
265 use crate::parsing::rar5::VintReader;
266
267 let data = std::fs::read("__fixtures__/encrypted/rar5-encrypted-v5.rar").unwrap();
269
270 let _after_sig = &data[8..];
272
273 let mut pos = 8; loop {
276 assert!(pos + 7 <= data.len(), "Could not find file header");
277
278 let mut reader = VintReader::new(&data[pos + 4..]);
280 let header_size = reader.read().unwrap();
281 let header_type = reader.read().unwrap();
282
283 if header_type == 2 {
284 let (file_header, _) = Rar5FileHeaderParser::parse(&data[pos..]).unwrap();
286
287 if file_header.is_encrypted() {
288 let enc_data = file_header.encryption_info().unwrap();
289
290 let enc_info = Rar5EncryptionInfo::parse(enc_data).unwrap();
291
292 let crypto =
294 Rar5Crypto::derive_key("testpass", &enc_info.salt, enc_info.lg2_count);
295
296 if let Some(ref check) = enc_info.psw_check {
298 let valid = crypto.verify_password(check);
299 assert!(valid, "Password verification failed");
300 }
301
302 let header_total_size = 4 + 1 + header_size as usize; let data_start = pos + header_total_size;
306 let data_end = data_start + file_header.packed_size as usize;
307
308 if data_end <= data.len() {
309 let encrypted_data = &data[data_start..data_end];
310
311 let decrypted = crypto
313 .decrypt_to_vec(&enc_info.init_v, encrypted_data)
314 .unwrap();
315
316 assert_eq!(decrypted.len(), encrypted_data.len());
319
320 }
323 }
324 break;
325 }
326
327 let size_vint_len = {
329 let mut r = VintReader::new(&data[pos + 4..]);
330 r.read().unwrap();
331 r.position()
332 };
333 pos += 4 + size_vint_len + header_size as usize;
334 }
335 }
336
337 #[test]
338 fn test_decrypt_stored_file() {
339 use crate::parsing::rar5::file_header::Rar5FileHeaderParser;
340 use crate::parsing::rar5::VintReader;
341
342 let data = std::fs::read("__fixtures__/encrypted/rar5-encrypted-stored.rar").unwrap();
344
345 let mut pos = 8; loop {
348 assert!(pos + 7 <= data.len(), "Could not find file header");
349
350 let mut reader = VintReader::new(&data[pos + 4..]);
351 let header_size = reader.read().unwrap();
352 let header_type = reader.read().unwrap();
353
354 if header_type == 2 {
355 let (file_header, consumed) = Rar5FileHeaderParser::parse(&data[pos..]).unwrap();
356
357 assert!(file_header.is_encrypted());
358 assert!(
359 file_header.is_stored(),
360 "File should be stored (uncompressed)"
361 );
362
363 let enc_data = file_header.encryption_info().unwrap();
364 let enc_info = Rar5EncryptionInfo::parse(enc_data).unwrap();
365
366 let crypto = Rar5Crypto::derive_key("testpass", &enc_info.salt, enc_info.lg2_count);
367
368 if let Some(ref check) = enc_info.psw_check {
370 assert!(
371 crypto.verify_password(check),
372 "Password verification failed"
373 );
374 }
375
376 let data_start = pos + consumed;
378 let data_end = data_start + file_header.packed_size as usize;
379 let encrypted_data = &data[data_start..data_end];
380
381 let decrypted = crypto
382 .decrypt_to_vec(&enc_info.init_v, encrypted_data)
383 .unwrap();
384
385 let expected = b"Hello, encrypted world!\n";
389 assert!(
390 decrypted.starts_with(expected),
391 "Decrypted content doesn't match. Got: {:?}",
392 String::from_utf8_lossy(&decrypted[..expected.len().min(decrypted.len())])
393 );
394 break;
395 }
396
397 let size_vint_len = {
398 let mut r = VintReader::new(&data[pos + 4..]);
399 r.read().unwrap();
400 r.position()
401 };
402 pos += 4 + size_vint_len + header_size as usize;
403 }
404 }
405}