Skip to main content

rars_crypto/
rar20.rs

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}