1use rars_crc32::table_entry as crc32_table_entry;
2use zeroize::{ZeroizeOnDrop, Zeroizing};
3
4const INIT_SUBST_TABLE: [u8; 256] = [
5 215, 19, 149, 35, 73, 197, 192, 205, 249, 28, 16, 119, 48, 221, 2, 42, 232, 1, 177, 233, 14,
6 88, 219, 25, 223, 195, 244, 90, 87, 239, 153, 137, 255, 199, 147, 70, 92, 66, 246, 13, 216, 40,
7 62, 29, 217, 230, 86, 6, 71, 24, 171, 196, 101, 113, 218, 123, 93, 91, 163, 178, 202, 67, 44,
8 235, 107, 250, 75, 234, 49, 167, 125, 211, 83, 114, 157, 144, 32, 193, 143, 36, 158, 124, 247,
9 187, 89, 214, 141, 47, 121, 228, 61, 130, 213, 194, 174, 251, 97, 110, 54, 229, 115, 57, 152,
10 94, 105, 243, 212, 55, 209, 245, 63, 11, 164, 200, 31, 156, 81, 176, 227, 21, 76, 99, 139, 188,
11 127, 17, 248, 51, 207, 120, 189, 210, 8, 226, 41, 72, 183, 203, 135, 165, 166, 60, 98, 7, 122,
12 38, 155, 170, 69, 172, 252, 238, 39, 134, 59, 128, 236, 27, 240, 80, 131, 3, 85, 206, 145, 79,
13 154, 142, 159, 220, 201, 133, 74, 64, 20, 129, 224, 185, 138, 103, 173, 182, 43, 34, 254, 82,
14 198, 151, 231, 180, 58, 10, 118, 26, 102, 12, 50, 132, 22, 191, 136, 111, 162, 179, 45, 4, 148,
15 108, 161, 56, 78, 126, 242, 222, 15, 175, 146, 23, 33, 241, 181, 190, 77, 225, 0, 46, 169, 186,
16 68, 95, 237, 65, 53, 208, 253, 168, 9, 18, 100, 52, 116, 184, 160, 96, 109, 37, 30, 106, 140,
17 104, 150, 5, 204, 117, 112, 84,
18];
19
20const INIT_KEY: [u32; 4] = [0xd3a3_b879, 0x3f6d_12f7, 0x7515_a235, 0xa4e7_f123];
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23#[non_exhaustive]
24pub enum Error {
25 UnalignedInput,
26}
27
28impl std::fmt::Display for Error {
29 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 match self {
31 Self::UnalignedInput => f.write_str("RAR 2.0 cipher input is not block aligned"),
32 }
33 }
34}
35
36impl std::error::Error for Error {}
37
38pub type Result<T> = std::result::Result<T, Error>;
39
40#[derive(ZeroizeOnDrop)]
41pub struct Rar20Cipher {
42 key: [u32; 4],
43 subst: [u8; 256],
44}
45
46impl Rar20Cipher {
47 pub fn new(password: &[u8]) -> Self {
48 let mut cipher = Self {
49 key: INIT_KEY,
50 subst: INIT_SUBST_TABLE,
51 };
52 cipher.set_key(password);
53 cipher
54 }
55
56 pub fn decrypt_in_place(&mut self, data: &mut [u8]) -> Result<()> {
57 if !data.len().is_multiple_of(16) {
58 return Err(Error::UnalignedInput);
59 }
60 for block in data.chunks_exact_mut(16) {
61 self.decrypt_block(block);
62 }
63 Ok(())
64 }
65
66 pub fn encrypt_in_place(&mut self, data: &mut [u8]) -> Result<()> {
67 if !data.len().is_multiple_of(16) {
68 return Err(Error::UnalignedInput);
69 }
70 for block in data.chunks_exact_mut(16) {
71 self.encrypt_block(block);
72 }
73 Ok(())
74 }
75
76 fn set_key(&mut self, password: &[u8]) {
77 for j in 0..=255u32 {
78 for i in (0..password.len()).step_by(2) {
79 let n1_index = password[i].wrapping_sub(j as u8);
80 let next = password.get(i + 1).copied().unwrap_or(0);
81 let n2_index = next.wrapping_add(j as u8);
82 let mut n1 = crc32_table_entry(n1_index) as u8;
83 let n2 = crc32_table_entry(n2_index) as u8;
84 let mut k = 1usize;
85 while n1 != n2 {
86 self.subst.swap(n1 as usize, (n1 as usize + i + k) & 0xff);
87 n1 = n1.wrapping_add(1);
88 k += 1;
89 }
90 }
91 }
92
93 let mut padded = Zeroizing::new(password.to_vec());
94 let padding = (16 - padded.len() % 16) % 16;
95 let new_len = padded.len() + padding;
96 padded.resize(new_len, 0);
97 for block in padded.chunks_exact_mut(16) {
98 self.encrypt_block(block);
99 }
100 }
101
102 fn encrypt_block(&mut self, block: &mut [u8]) {
103 let (mut a, mut b, mut c, mut d) = self.load_block(block);
104 for i in 0..32 {
105 (a, b, c, d) = self.round(i, a, b, c, d);
106 }
107 write_block(
108 block,
109 c ^ self.key[0],
110 d ^ self.key[1],
111 a ^ self.key[2],
112 b ^ self.key[3],
113 );
114 self.update_keys(block);
115 }
116
117 fn decrypt_block(&mut self, block: &mut [u8]) {
118 let saved: [u8; 16] = block.try_into().expect("RAR 2 block size");
119 let (mut a, mut b, mut c, mut d) = self.load_block(block);
120 for i in (0..32).rev() {
121 (a, b, c, d) = self.round(i, a, b, c, d);
122 }
123 write_block(
124 block,
125 c ^ self.key[0],
126 d ^ self.key[1],
127 a ^ self.key[2],
128 b ^ self.key[3],
129 );
130 self.update_keys(&saved);
131 }
132
133 fn load_block(&self, block: &[u8]) -> (u32, u32, u32, u32) {
134 (
135 read_u32_le(&block[0..4]) ^ self.key[0],
136 read_u32_le(&block[4..8]) ^ self.key[1],
137 read_u32_le(&block[8..12]) ^ self.key[2],
138 read_u32_le(&block[12..16]) ^ self.key[3],
139 )
140 }
141
142 fn round(&self, i: usize, a: u32, b: u32, c: u32, d: u32) -> (u32, u32, u32, u32) {
143 let t = c.wrapping_add(d.rotate_left(11)) ^ self.key[i & 3];
144 let ta = a ^ self.subst_long(t);
145 let t = (d ^ c.rotate_left(17)).wrapping_add(self.key[i & 3]);
146 let tb = b ^ self.subst_long(t);
147 (c, d, ta, tb)
148 }
149
150 fn subst_long(&self, value: u32) -> u32 {
151 u32::from(self.subst[(value & 0xff) as usize])
152 | (u32::from(self.subst[((value >> 8) & 0xff) as usize]) << 8)
153 | (u32::from(self.subst[((value >> 16) & 0xff) as usize]) << 16)
154 | (u32::from(self.subst[((value >> 24) & 0xff) as usize]) << 24)
155 }
156
157 fn update_keys(&mut self, block: &[u8]) {
158 for chunk in block.chunks_exact(4) {
159 self.key[0] ^= crc32_table_entry(chunk[0]);
160 self.key[1] ^= crc32_table_entry(chunk[1]);
161 self.key[2] ^= crc32_table_entry(chunk[2]);
162 self.key[3] ^= crc32_table_entry(chunk[3]);
163 }
164 }
165}
166
167fn read_u32_le(bytes: &[u8]) -> u32 {
168 u32::from_le_bytes(bytes.try_into().expect("u32 input size"))
169}
170
171fn write_block(block: &mut [u8], a: u32, b: u32, c: u32, d: u32) {
172 block[0..4].copy_from_slice(&a.to_le_bytes());
173 block[4..8].copy_from_slice(&b.to_le_bytes());
174 block[8..12].copy_from_slice(&c.to_le_bytes());
175 block[12..16].copy_from_slice(&d.to_le_bytes());
176}
177
178#[cfg(test)]
179mod tests {
180 use super::{Error, Rar20Cipher};
181
182 #[test]
183 fn rar20_encrypt_decrypt_round_trips_blocks() {
184 let mut encrypted = *b"0123456789abcdefRAR2.0 block pad";
185 let original = encrypted;
186
187 Rar20Cipher::new(b"password")
188 .encrypt_in_place(&mut encrypted)
189 .unwrap();
190 assert_eq!(
191 encrypted,
192 [
193 0xb7, 0x14, 0x54, 0x5a, 0x55, 0x8b, 0xca, 0xf7, 0xbc, 0x18, 0x38, 0x17, 0x1d, 0x9e,
194 0x31, 0xab, 0x81, 0x40, 0x72, 0xfe, 0x02, 0x76, 0x76, 0x65, 0x4a, 0xa5, 0x3f, 0x4b,
195 0xb3, 0x0c, 0xad, 0x07,
196 ]
197 );
198
199 Rar20Cipher::new(b"password")
200 .decrypt_in_place(&mut encrypted)
201 .unwrap();
202 assert_eq!(encrypted, original);
203 }
204
205 #[test]
206 fn rar20_cipher_rejects_partial_tail() {
207 let mut data = *b"0123456789abcdef!";
208 let original = data;
209
210 assert_eq!(
211 Rar20Cipher::new(b"password").encrypt_in_place(&mut data),
212 Err(Error::UnalignedInput)
213 );
214 assert_eq!(data, original);
215 assert_eq!(
216 Rar20Cipher::new(b"password").decrypt_in_place(&mut data),
217 Err(Error::UnalignedInput)
218 );
219 assert_eq!(data, original);
220 }
221}