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}