Skip to main content

rar_stream/crypto/
rar4.rs

1//! RAR 3.x/4.x encryption support.
2//!
3//! RAR 3.x/4.x uses:
4//! - AES-128-CBC encryption
5//! - Custom SHA-1 based key derivation with 2^18 (262,144) iterations
6//! - 8-byte salt
7//! - No password verification (wrong password produces garbage)
8
9use aes::cipher::{block_padding::NoPadding, BlockDecryptMut, KeyIvInit};
10use sha1::{Digest, Sha1};
11
12use super::CryptoError;
13
14type Aes128CbcDec = cbc::Decryptor<aes::Aes128>;
15
16/// RAR 3.x/4.x encryption info from file header.
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct Rar4EncryptionInfo {
19    /// 8-byte salt from file header
20    pub salt: [u8; 8],
21}
22
23/// RAR 3.x/4.x crypto handler.
24#[derive(Clone, Debug)]
25pub struct Rar4Crypto {
26    /// Derived AES-256 key (32 bytes)
27    key: [u8; 32],
28    /// Derived IV (16 bytes)
29    iv: [u8; 16],
30}
31
32impl Rar4Crypto {
33    /// Derive key and IV from password and salt.
34    ///
35    /// The RAR4 KDF uses 2^18 iterations of SHA-1:
36    /// - Password is encoded as UTF-16LE, concatenated with salt as seed
37    /// - Each iteration: `SHA1.update(seed + counter[0..3])`
38    /// - Counter is a 3-byte little-endian integer
39    /// - Every 16384 (0x4000) iterations at j=0, extract byte 19 of digest as IV byte
40    /// - Final SHA-1 digest (first 16 bytes) is the AES-128 key (with endian swap)
41    pub fn derive_key(password: &str, salt: &[u8; 8]) -> Self {
42        // Convert password to UTF-16LE and concatenate with salt
43        let seed: Vec<u8> = password
44            .encode_utf16()
45            .flat_map(|c| c.to_le_bytes())
46            .chain(salt.iter().copied())
47            .collect();
48
49        let mut hasher = Sha1::new();
50        let mut iv = [0u8; 16];
51
52        // 16 outer iterations (for IV bytes), each with 0x4000 inner iterations
53        for i in 0..16 {
54            for j in 0..0x4000u32 {
55                let cnt = i * 0x4000 + j;
56                let cnt_bytes = [cnt as u8, (cnt >> 8) as u8, (cnt >> 16) as u8];
57
58                hasher.update(&seed);
59                hasher.update(cnt_bytes);
60
61                // At the start of each outer iteration, extract IV byte
62                if j == 0 {
63                    let temp_digest = hasher.clone().finalize();
64                    iv[i as usize] = temp_digest[19];
65                }
66            }
67        }
68
69        // Final digest - first 16 bytes become the key (with endian swap)
70        let digest = hasher.finalize();
71        let key_be: [u8; 16] = digest[..16].try_into().unwrap();
72
73        // Swap endianness: convert from big-endian to little-endian
74        // pack("<LLLL", *unpack(">LLLL", key_be))
75        let key16 = Self::swap_key_endianness(&key_be);
76
77        // Store as 32-byte key for AES-256 compatibility (but only use first 16 for AES-128)
78        let mut key = [0u8; 32];
79        key[..16].copy_from_slice(&key16);
80        key[16..32].copy_from_slice(&key16);
81
82        Self { key, iv }
83    }
84
85    /// Swap key bytes from big-endian to little-endian (4 x 32-bit words).
86    fn swap_key_endianness(key_be: &[u8; 16]) -> [u8; 16] {
87        let mut key_le = [0u8; 16];
88        for i in 0..4 {
89            // Each 4-byte word is swapped
90            key_le[i * 4] = key_be[i * 4 + 3];
91            key_le[i * 4 + 1] = key_be[i * 4 + 2];
92            key_le[i * 4 + 2] = key_be[i * 4 + 1];
93            key_le[i * 4 + 3] = key_be[i * 4];
94        }
95        key_le
96    }
97
98    /// Decrypt data in place using AES-128-CBC (using first 16 bytes of key).
99    pub fn decrypt(&self, data: &mut [u8]) -> Result<(), CryptoError> {
100        if data.is_empty() {
101            return Ok(());
102        }
103
104        // Data must be multiple of 16 bytes
105        if data.len() % 16 != 0 {
106            return Err(CryptoError::DecryptionFailed);
107        }
108
109        // Use only first 16 bytes of key for AES-128
110        let decryptor = Aes128CbcDec::new_from_slices(&self.key[..16], &self.iv)
111            .map_err(|_| CryptoError::DecryptionFailed)?;
112
113        decryptor
114            .decrypt_padded_mut::<NoPadding>(data)
115            .map_err(|_| CryptoError::DecryptionFailed)?;
116
117        Ok(())
118    }
119
120    /// Decrypt data returning a new Vec.
121    pub fn decrypt_to_vec(&self, data: &[u8]) -> Result<Vec<u8>, CryptoError> {
122        let mut decrypted = data.to_vec();
123        self.decrypt(&mut decrypted)?;
124        Ok(decrypted)
125    }
126
127    /// Get the derived IV.
128    pub fn iv(&self) -> &[u8; 16] {
129        &self.iv
130    }
131
132    /// Get the derived key.
133    pub fn key(&self) -> &[u8; 32] {
134        &self.key
135    }
136}
137
138impl Drop for Rar4Crypto {
139    fn drop(&mut self) {
140        // Zero sensitive key material to reduce exposure window.
141        for byte in &mut self.key {
142            unsafe { std::ptr::write_volatile(byte, 0) };
143        }
144        for byte in &mut self.iv {
145            unsafe { std::ptr::write_volatile(byte, 0) };
146        }
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153
154    #[test]
155    fn test_derive_key() {
156        // Test key derivation with known password and salt
157        let salt = [0x72, 0x8b, 0xe5, 0x8c, 0x22, 0x7f, 0x8d, 0xb4];
158        let crypto = Rar4Crypto::derive_key("hello", &salt);
159
160        // The key and IV should be deterministic
161        assert_eq!(crypto.iv.len(), 16);
162        assert_eq!(crypto.key.len(), 32);
163
164        // Verify same password/salt produces same result
165        let crypto2 = Rar4Crypto::derive_key("hello", &salt);
166        assert_eq!(crypto.key, crypto2.key);
167        assert_eq!(crypto.iv, crypto2.iv);
168    }
169
170    #[test]
171    fn test_different_passwords_different_keys() {
172        let salt = [0x72, 0x8b, 0xe5, 0x8c, 0x22, 0x7f, 0x8d, 0xb4];
173        let crypto1 = Rar4Crypto::derive_key("hello", &salt);
174        let crypto2 = Rar4Crypto::derive_key("world", &salt);
175
176        assert_ne!(crypto1.key, crypto2.key);
177        assert_ne!(crypto1.iv, crypto2.iv);
178    }
179
180    #[test]
181    fn test_different_salts_different_keys() {
182        let salt1 = [0x72, 0x8b, 0xe5, 0x8c, 0x22, 0x7f, 0x8d, 0xb4];
183        let salt2 = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07];
184        let crypto1 = Rar4Crypto::derive_key("hello", &salt1);
185        let crypto2 = Rar4Crypto::derive_key("hello", &salt2);
186
187        assert_ne!(crypto1.key, crypto2.key);
188        assert_ne!(crypto1.iv, crypto2.iv);
189    }
190}