Skip to main content

rars_crypto/
rar50.rs

1use aes::cipher::{BlockCipherDecrypt, BlockCipherEncrypt, KeyInit};
2use aes::Aes256;
3use hmac::{Hmac, Mac};
4use sha2::{Digest, Sha256};
5use zeroize::{Zeroize, ZeroizeOnDrop};
6
7const MAX_KDF_COUNT_LOG: u8 = 24;
8type HmacSha256 = Hmac<Sha256>;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
11#[non_exhaustive]
12pub enum Error {
13    KdfCountTooLarge,
14    BadPassword,
15    UnalignedInput,
16}
17
18impl std::fmt::Display for Error {
19    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20        match self {
21            Self::KdfCountTooLarge => f.write_str("RAR 5 KDF count is too large"),
22            Self::BadPassword => f.write_str("wrong password or corrupt encrypted data"),
23            Self::UnalignedInput => f.write_str("RAR 5 AES input is not block aligned"),
24        }
25    }
26}
27
28impl std::error::Error for Error {}
29
30pub type Result<T> = std::result::Result<T, Error>;
31
32#[derive(Clone, ZeroizeOnDrop)]
33#[non_exhaustive]
34pub struct Rar50Keys {
35    pub key: [u8; 32],
36    pub hash_key: [u8; 32],
37    pub password_check: [u8; 8],
38}
39
40impl std::fmt::Debug for Rar50Keys {
41    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42        f.debug_struct("Rar50Keys").finish_non_exhaustive()
43    }
44}
45
46impl PartialEq for Rar50Keys {
47    fn eq(&self, other: &Self) -> bool {
48        let key_eq = constant_time_eq(&self.key, &other.key);
49        let hash_eq = constant_time_eq(&self.hash_key, &other.hash_key);
50        let check_eq = constant_time_eq(&self.password_check, &other.password_check);
51        key_eq & hash_eq & check_eq
52    }
53}
54
55impl Eq for Rar50Keys {}
56
57impl Rar50Keys {
58    pub fn derive(password: &[u8], salt: [u8; 16], kdf_count_log: u8) -> Result<Self> {
59        if kdf_count_log > MAX_KDF_COUNT_LOG {
60            return Err(Error::KdfCountTooLarge);
61        }
62
63        let mut first_input = Vec::with_capacity(salt.len() + 4);
64        first_input.extend_from_slice(&salt);
65        first_input.extend_from_slice(&1u32.to_be_bytes());
66
67        let mut u = hmac_sha256(password, &first_input);
68        let mut accumulator = u;
69        let mut taps = [[0u8; 32]; 3];
70        let mut iterations = (1u32 << kdf_count_log) - 1;
71
72        for tap in &mut taps {
73            for _ in 0..iterations {
74                u = hmac_sha256(password, &u);
75                for (acc, byte) in accumulator.iter_mut().zip(u) {
76                    *acc ^= byte;
77                }
78            }
79            *tap = accumulator;
80            iterations = 16;
81        }
82
83        let mut password_check = [0u8; 8];
84        for (i, byte) in password_check.iter_mut().enumerate() {
85            *byte = taps[2][i] ^ taps[2][i + 8] ^ taps[2][i + 16] ^ taps[2][i + 24];
86        }
87
88        let result = Self {
89            key: taps[0],
90            hash_key: taps[1],
91            password_check,
92        };
93        u.zeroize();
94        accumulator.zeroize();
95        taps.zeroize();
96        Ok(result)
97    }
98
99    pub fn check_password(&self, stored: &[u8; 12]) -> Result<()> {
100        let checksum = sha256(&stored[..8]);
101        let checksum_matches = constant_time_eq(&checksum[..4], &stored[8..12]);
102        let password_matches = constant_time_eq(&self.password_check, &stored[..8]);
103        if !(checksum_matches & password_matches) {
104            return Err(Error::BadPassword);
105        }
106        Ok(())
107    }
108
109    pub fn password_check_record(&self) -> [u8; 12] {
110        let mut record = [0u8; 12];
111        record[..8].copy_from_slice(&self.password_check);
112        record[8..].copy_from_slice(&sha256(&self.password_check)[..4]);
113        record
114    }
115
116    pub fn mac_crc32(&self, crc: u32) -> u32 {
117        let digest = hmac_sha256(&self.hash_key, &crc.to_le_bytes());
118        digest.chunks_exact(4).fold(0, |acc, chunk| {
119            acc ^ u32::from_le_bytes(chunk.try_into().unwrap())
120        })
121    }
122
123    pub fn mac_hash32(&self, hash: [u8; 32]) -> [u8; 32] {
124        hmac_sha256(&self.hash_key, &hash)
125    }
126}
127
128fn constant_time_eq(left: &[u8], right: &[u8]) -> bool {
129    if left.len() != right.len() {
130        return false;
131    }
132    let mut diff = 0u8;
133    for (&left, &right) in left.iter().zip(right) {
134        diff |= left ^ right;
135    }
136    diff == 0
137}
138
139#[derive(ZeroizeOnDrop)]
140pub struct Rar50Cipher {
141    cipher: Aes256,
142    iv: [u8; 16],
143}
144
145impl Rar50Cipher {
146    pub fn new(key: [u8; 32], iv: [u8; 16]) -> Self {
147        Self {
148            cipher: Aes256::new(&key.into()),
149            iv,
150        }
151    }
152
153    pub fn decrypt_in_place(&mut self, data: &mut [u8]) -> Result<()> {
154        if !data.len().is_multiple_of(16) {
155            return Err(Error::UnalignedInput);
156        }
157        for block in data.chunks_exact_mut(16) {
158            self.decrypt_block(block);
159        }
160        Ok(())
161    }
162
163    pub fn encrypt_in_place(&mut self, data: &mut [u8]) -> Result<()> {
164        if !data.len().is_multiple_of(16) {
165            return Err(Error::UnalignedInput);
166        }
167        for block in data.chunks_exact_mut(16) {
168            self.encrypt_block(block);
169        }
170        Ok(())
171    }
172
173    fn encrypt_block(&mut self, block: &mut [u8]) {
174        for (byte, iv_byte) in block.iter_mut().zip(self.iv) {
175            *byte ^= iv_byte;
176        }
177        let block: &mut [u8; 16] = block.try_into().expect("AES block size");
178        self.cipher.encrypt_block(block.into());
179        self.iv.copy_from_slice(block);
180    }
181
182    fn decrypt_block(&mut self, block: &mut [u8]) {
183        let ciphertext: [u8; 16] = block.try_into().expect("AES block size");
184        let block: &mut [u8; 16] = block.try_into().expect("AES block size");
185        self.cipher.decrypt_block(block.into());
186        for (byte, iv_byte) in block.iter_mut().zip(self.iv) {
187            *byte ^= iv_byte;
188        }
189        self.iv = ciphertext;
190    }
191}
192
193fn hmac_sha256(key: &[u8], data: &[u8]) -> [u8; 32] {
194    let mut hmac =
195        <HmacSha256 as KeyInit>::new_from_slice(key).expect("HMAC accepts keys of any size");
196    hmac.update(data);
197    hmac.finalize().into_bytes().into()
198}
199
200fn sha256(data: &[u8]) -> [u8; 32] {
201    Sha256::digest(data).into()
202}
203
204#[cfg(test)]
205mod tests {
206    use super::*;
207
208    #[test]
209    fn sha256_matches_standard_vectors() {
210        assert_eq!(
211            hex(&sha256(b"")),
212            "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
213        );
214        assert_eq!(
215            hex(&sha256(b"abc")),
216            "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
217        );
218    }
219
220    #[test]
221    fn hmac_sha256_matches_standard_vector() {
222        assert_eq!(
223            hex(&hmac_sha256(&[0x0b; 20], b"Hi There")),
224            "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"
225        );
226    }
227
228    #[test]
229    fn password_check_uses_the_check_value_and_its_checksum() {
230        let keys = Rar50Keys::derive(b"secret", [7; 16], 4).unwrap();
231        let mut record = keys.password_check_record();
232
233        assert_eq!(keys.check_password(&record), Ok(()));
234
235        record[0] ^= 0x01;
236        assert_eq!(keys.check_password(&record), Err(Error::BadPassword));
237
238        let mut record = keys.password_check_record();
239        record[11] ^= 0x01;
240        assert_eq!(keys.check_password(&record), Err(Error::BadPassword));
241    }
242
243    #[test]
244    fn rar50_aes_encrypt_decrypt_round_trips_blocks() {
245        let key = [9u8; 32];
246        let iv = [5u8; 16];
247        let mut data = *b"0123456789abcdefRAR5 block two!!";
248        let plain = data;
249
250        Rar50Cipher::new(key, iv)
251            .encrypt_in_place(&mut data)
252            .unwrap();
253        assert_ne!(data, plain);
254
255        Rar50Cipher::new(key, iv)
256            .decrypt_in_place(&mut data)
257            .unwrap();
258        assert_eq!(data, plain);
259    }
260
261    #[test]
262    fn rar50_aes_rejects_partial_tail() {
263        let key = [9u8; 32];
264        let iv = [5u8; 16];
265        let mut data = *b"partial block!!";
266
267        assert_eq!(
268            Rar50Cipher::new(key, iv).encrypt_in_place(&mut data),
269            Err(Error::UnalignedInput)
270        );
271        assert_eq!(
272            Rar50Cipher::new(key, iv).decrypt_in_place(&mut data),
273            Err(Error::UnalignedInput)
274        );
275    }
276
277    #[test]
278    fn rar50_kdf_matches_pinned_vector() {
279        let keys = Rar50Keys::derive(
280            b"password",
281            [
282                0x00, 0x01, 0x02, 0x03, 0x10, 0x11, 0x12, 0x13, 0x20, 0x21, 0x22, 0x23, 0x30, 0x31,
283                0x32, 0x33,
284            ],
285            4,
286        )
287        .unwrap();
288
289        assert_eq!(
290            hex(&keys.key),
291            "cae43ebc57fcbdfc97ddc6f4a2d09687fd06010b51f651bec8f911f20caf008f"
292        );
293        assert_eq!(
294            hex(&keys.hash_key),
295            "e65c566ff17139eaabdf60986e64058aac7e8dd82d6c5b027dd2e6d761a44d3c"
296        );
297        assert_eq!(hex(&keys.password_check), "118929fdcad8a74f");
298        assert_eq!(
299            hex(&keys.password_check_record()),
300            "118929fdcad8a74f5379ff2d"
301        );
302        assert_eq!(keys.mac_crc32(0x1234_5678), 0xd742_398d);
303    }
304
305    fn hex(bytes: &[u8]) -> String {
306        bytes.iter().map(|byte| format!("{byte:02x}")).collect()
307    }
308}