Line | Count | Source |
1 | | //! A counter mode (CTR) for AES to work with the encryption used in zip files. |
2 | | //! |
3 | | //! This was implemented since the zip specification requires the mode to not use a nonce and uses a |
4 | | //! different byte order (little endian) than NIST (big endian). |
5 | | //! See [AesCtrZipKeyStream] for more information. |
6 | | |
7 | | use aes::cipher::generic_array::GenericArray; |
8 | | // use aes::{BlockEncrypt, NewBlockCipher}; |
9 | | use aes::cipher::{BlockEncrypt, KeyInit}; |
10 | | use byteorder::WriteBytesExt; |
11 | | use std::{any, fmt}; |
12 | | |
13 | | /// Internal block size of an AES cipher. |
14 | | const AES_BLOCK_SIZE: usize = 16; |
15 | | |
16 | | /// AES-128. |
17 | | #[derive(Debug)] |
18 | | pub struct Aes128; |
19 | | /// AES-192 |
20 | | #[derive(Debug)] |
21 | | pub struct Aes192; |
22 | | /// AES-256. |
23 | | #[derive(Debug)] |
24 | | pub struct Aes256; |
25 | | |
26 | | /// An AES cipher kind. |
27 | | pub trait AesKind { |
28 | | /// Key type. |
29 | | type Key: AsRef<[u8]>; |
30 | | /// Cipher used to decrypt. |
31 | | type Cipher; |
32 | | } |
33 | | |
34 | | impl AesKind for Aes128 { |
35 | | type Key = [u8; 16]; |
36 | | type Cipher = aes::Aes128; |
37 | | } |
38 | | |
39 | | impl AesKind for Aes192 { |
40 | | type Key = [u8; 24]; |
41 | | type Cipher = aes::Aes192; |
42 | | } |
43 | | |
44 | | impl AesKind for Aes256 { |
45 | | type Key = [u8; 32]; |
46 | | type Cipher = aes::Aes256; |
47 | | } |
48 | | |
49 | | /// An AES-CTR key stream generator. |
50 | | /// |
51 | | /// Implements the slightly non-standard AES-CTR variant used by WinZip AES encryption. |
52 | | /// |
53 | | /// Typical AES-CTR implementations combine a nonce with a 64 bit counter. WinZIP AES instead uses |
54 | | /// no nonce and also uses a different byte order (little endian) than NIST (big endian). |
55 | | /// |
56 | | /// The stream implements the `Read` trait; encryption or decryption is performed by XOR-ing the |
57 | | /// bytes from the key stream with the ciphertext/plaintext. |
58 | | pub struct AesCtrZipKeyStream<C: AesKind> { |
59 | | /// Current AES counter. |
60 | | counter: u128, |
61 | | /// AES cipher instance. |
62 | | cipher: C::Cipher, |
63 | | /// Stores the currently available keystream bytes. |
64 | | buffer: [u8; AES_BLOCK_SIZE], |
65 | | /// Number of bytes already used up from `buffer`. |
66 | | pos: usize, |
67 | | } |
68 | | |
69 | | impl<C> fmt::Debug for AesCtrZipKeyStream<C> |
70 | | where |
71 | | C: AesKind, |
72 | | { |
73 | | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
74 | | write!( |
75 | | f, |
76 | | "AesCtrZipKeyStream<{}>(counter: {})", |
77 | | any::type_name::<C>(), |
78 | | self.counter |
79 | | ) |
80 | | } |
81 | | } |
82 | | |
83 | | impl<C> AesCtrZipKeyStream<C> |
84 | | where |
85 | | C: AesKind, |
86 | | C::Cipher: KeyInit, |
87 | | { |
88 | | /// Creates a new zip variant AES-CTR key stream. |
89 | | /// |
90 | | /// # Panics |
91 | | /// |
92 | | /// This panics if `key` doesn't have the correct size for cipher `C`. |
93 | 0 | pub fn new(key: &[u8]) -> AesCtrZipKeyStream<C> { |
94 | 0 | AesCtrZipKeyStream { |
95 | 0 | counter: 1, |
96 | 0 | cipher: C::Cipher::new(GenericArray::from_slice(key)), |
97 | 0 | buffer: [0u8; AES_BLOCK_SIZE], |
98 | 0 | pos: AES_BLOCK_SIZE, |
99 | 0 | } |
100 | 0 | } Unexecuted instantiation: <zip::aes_ctr::AesCtrZipKeyStream<zip::aes_ctr::Aes128>>::new Unexecuted instantiation: <zip::aes_ctr::AesCtrZipKeyStream<zip::aes_ctr::Aes192>>::new Unexecuted instantiation: <zip::aes_ctr::AesCtrZipKeyStream<zip::aes_ctr::Aes256>>::new |
101 | | } |
102 | | |
103 | | impl<C> AesCipher for AesCtrZipKeyStream<C> |
104 | | where |
105 | | C: AesKind, |
106 | | C::Cipher: BlockEncrypt, |
107 | | { |
108 | | /// Decrypt or encrypt `target`. |
109 | | #[inline] |
110 | 0 | fn crypt_in_place(&mut self, mut target: &mut [u8]) { |
111 | 0 | while !target.is_empty() { |
112 | 0 | if self.pos == AES_BLOCK_SIZE { |
113 | 0 | // Note: AES block size is always 16 bytes, same as u128. |
114 | 0 | self.buffer |
115 | 0 | .as_mut() |
116 | 0 | .write_u128::<byteorder::LittleEndian>(self.counter) |
117 | 0 | .expect("did not expect u128 le conversion to fail"); |
118 | 0 | self.cipher |
119 | 0 | .encrypt_block(GenericArray::from_mut_slice(&mut self.buffer)); |
120 | 0 | self.counter += 1; |
121 | 0 | self.pos = 0; |
122 | 0 | } |
123 | | |
124 | 0 | let target_len = target.len().min(AES_BLOCK_SIZE - self.pos); |
125 | | |
126 | 0 | xor( |
127 | 0 | &mut target[0..target_len], |
128 | 0 | &self.buffer[self.pos..(self.pos + target_len)], |
129 | | ); |
130 | 0 | target = &mut target[target_len..]; |
131 | 0 | self.pos += target_len; |
132 | | } |
133 | 0 | } Unexecuted instantiation: <zip::aes_ctr::AesCtrZipKeyStream<zip::aes_ctr::Aes128> as zip::aes_ctr::AesCipher>::crypt_in_place Unexecuted instantiation: <zip::aes_ctr::AesCtrZipKeyStream<zip::aes_ctr::Aes192> as zip::aes_ctr::AesCipher>::crypt_in_place Unexecuted instantiation: <zip::aes_ctr::AesCtrZipKeyStream<zip::aes_ctr::Aes256> as zip::aes_ctr::AesCipher>::crypt_in_place |
134 | | } |
135 | | |
136 | | /// This trait allows using generic AES ciphers with different key sizes. |
137 | | pub trait AesCipher { |
138 | | fn crypt_in_place(&mut self, target: &mut [u8]); |
139 | | } |
140 | | |
141 | | /// XORs a slice in place with another slice. |
142 | | #[inline] |
143 | 0 | fn xor(dest: &mut [u8], src: &[u8]) { |
144 | 0 | assert_eq!(dest.len(), src.len()); |
145 | | |
146 | 0 | for (lhs, rhs) in dest.iter_mut().zip(src.iter()) { |
147 | 0 | *lhs ^= *rhs; |
148 | 0 | } |
149 | 0 | } |
150 | | |
151 | | #[cfg(test)] |
152 | | mod tests { |
153 | | use super::{Aes128, Aes192, Aes256, AesCipher, AesCtrZipKeyStream, AesKind}; |
154 | | use aes::cipher::{BlockEncrypt, KeyInit}; |
155 | | |
156 | | /// Checks whether `crypt_in_place` produces the correct plaintext after one use and yields the |
157 | | /// cipertext again after applying it again. |
158 | | fn roundtrip<Aes>(key: &[u8], ciphertext: &mut [u8], expected_plaintext: &[u8]) |
159 | | where |
160 | | Aes: AesKind, |
161 | | Aes::Cipher: KeyInit + BlockEncrypt, |
162 | | { |
163 | | let mut key_stream = AesCtrZipKeyStream::<Aes>::new(key); |
164 | | |
165 | | let mut plaintext: Vec<u8> = ciphertext.to_vec(); |
166 | | key_stream.crypt_in_place(plaintext.as_mut_slice()); |
167 | | assert_eq!(plaintext, expected_plaintext.to_vec()); |
168 | | |
169 | | // Round-tripping should yield the ciphertext again. |
170 | | let mut key_stream = AesCtrZipKeyStream::<Aes>::new(key); |
171 | | key_stream.crypt_in_place(&mut plaintext); |
172 | | assert_eq!(plaintext, ciphertext.to_vec()); |
173 | | } |
174 | | |
175 | | #[test] |
176 | | #[should_panic] |
177 | | fn new_with_wrong_key_size() { |
178 | | AesCtrZipKeyStream::<Aes128>::new(&[1, 2, 3, 4, 5]); |
179 | | } |
180 | | |
181 | | // The data used in these tests was generated with p7zip without any compression. |
182 | | // It's not possible to recreate the exact same data, since a random salt is used for encryption. |
183 | | // `7z a -phelloworld -mem=AES256 -mx=0 aes256_40byte.zip 40byte_data.txt` |
184 | | #[test] |
185 | | fn crypt_aes_256_0_byte() { |
186 | | let mut ciphertext = []; |
187 | | let expected_plaintext = &[]; |
188 | | let key = [ |
189 | | 0x0b, 0xec, 0x2e, 0xf2, 0x46, 0xf0, 0x7e, 0x35, 0x16, 0x54, 0xe0, 0x98, 0x10, 0xb3, |
190 | | 0x18, 0x55, 0x24, 0xa3, 0x9e, 0x0e, 0x40, 0xe7, 0x92, 0xad, 0xb2, 0x8a, 0x48, 0xf4, |
191 | | 0x5c, 0xd0, 0xc0, 0x54, |
192 | | ]; |
193 | | |
194 | | roundtrip::<Aes256>(&key, &mut ciphertext, expected_plaintext); |
195 | | } |
196 | | |
197 | | #[test] |
198 | | fn crypt_aes_128_5_byte() { |
199 | | let mut ciphertext = [0x98, 0xa9, 0x8c, 0x26, 0x0e]; |
200 | | let expected_plaintext = b"asdf\n"; |
201 | | let key = [ |
202 | | 0xe0, 0x25, 0x7b, 0x57, 0x97, 0x6a, 0xa4, 0x23, 0xab, 0x94, 0xaa, 0x44, 0xfd, 0x47, |
203 | | 0x4f, 0xa5, |
204 | | ]; |
205 | | |
206 | | roundtrip::<Aes128>(&key, &mut ciphertext, expected_plaintext); |
207 | | } |
208 | | |
209 | | #[test] |
210 | | fn crypt_aes_192_5_byte() { |
211 | | let mut ciphertext = [0x36, 0x55, 0x5c, 0x61, 0x3c]; |
212 | | let expected_plaintext = b"asdf\n"; |
213 | | let key = [ |
214 | | 0xe4, 0x4a, 0x88, 0x52, 0x8f, 0xf7, 0x0b, 0x81, 0x7b, 0x75, 0xf1, 0x74, 0x21, 0x37, |
215 | | 0x8c, 0x90, 0xad, 0xbe, 0x4a, 0x65, 0xa8, 0x96, 0x0e, 0xcc, |
216 | | ]; |
217 | | |
218 | | roundtrip::<Aes192>(&key, &mut ciphertext, expected_plaintext); |
219 | | } |
220 | | |
221 | | #[test] |
222 | | fn crypt_aes_256_5_byte() { |
223 | | let mut ciphertext = [0xc2, 0x47, 0xc0, 0xdc, 0x56]; |
224 | | let expected_plaintext = b"asdf\n"; |
225 | | let key = [ |
226 | | 0x79, 0x5e, 0x17, 0xf2, 0xc6, 0x3d, 0x28, 0x9b, 0x4b, 0x4b, 0xbb, 0xa9, 0xba, 0xc9, |
227 | | 0xa5, 0xee, 0x3a, 0x4f, 0x0f, 0x4b, 0x29, 0xbd, 0xe9, 0xb8, 0x41, 0x9c, 0x41, 0xa5, |
228 | | 0x15, 0xb2, 0x86, 0xab, |
229 | | ]; |
230 | | |
231 | | roundtrip::<Aes256>(&key, &mut ciphertext, expected_plaintext); |
232 | | } |
233 | | |
234 | | #[test] |
235 | | fn crypt_aes_128_40_byte() { |
236 | | let mut ciphertext = [ |
237 | | 0xcf, 0x72, 0x6b, 0xa1, 0xb2, 0x0f, 0xdf, 0xaa, 0x10, 0xad, 0x9c, 0x7f, 0x6d, 0x1c, |
238 | | 0x8d, 0xb5, 0x16, 0x7e, 0xbb, 0x11, 0x69, 0x52, 0x8c, 0x89, 0x80, 0x32, 0xaa, 0x76, |
239 | | 0xa6, 0x18, 0x31, 0x98, 0xee, 0xdd, 0x22, 0x68, 0xb7, 0xe6, 0x77, 0xd2, |
240 | | ]; |
241 | | let expected_plaintext = b"Lorem ipsum dolor sit amet, consectetur\n"; |
242 | | let key = [ |
243 | | 0x43, 0x2b, 0x6d, 0xbe, 0x05, 0x76, 0x6c, 0x9e, 0xde, 0xca, 0x3b, 0xf8, 0xaf, 0x5d, |
244 | | 0x81, 0xb6, |
245 | | ]; |
246 | | |
247 | | roundtrip::<Aes128>(&key, &mut ciphertext, expected_plaintext); |
248 | | } |
249 | | |
250 | | #[test] |
251 | | fn crypt_aes_192_40_byte() { |
252 | | let mut ciphertext = [ |
253 | | 0xa6, 0xfc, 0x52, 0x79, 0x2c, 0x6c, 0xfe, 0x68, 0xb1, 0xa8, 0xb3, 0x07, 0x52, 0x8b, |
254 | | 0x82, 0xa6, 0x87, 0x9c, 0x72, 0x42, 0x3a, 0xf8, 0xc6, 0xa9, 0xc9, 0xfb, 0x61, 0x19, |
255 | | 0x37, 0xb9, 0x56, 0x62, 0xf4, 0xfc, 0x5e, 0x7a, 0xdd, 0x55, 0x0a, 0x48, |
256 | | ]; |
257 | | let expected_plaintext = b"Lorem ipsum dolor sit amet, consectetur\n"; |
258 | | let key = [ |
259 | | 0xac, 0x92, 0x41, 0xba, 0xde, 0xd9, 0x02, 0xfe, 0x40, 0x92, 0x20, 0xf6, 0x56, 0x03, |
260 | | 0xfe, 0xae, 0x1b, 0xba, 0x01, 0x97, 0x97, 0x79, 0xbb, 0xa6, |
261 | | ]; |
262 | | |
263 | | roundtrip::<Aes192>(&key, &mut ciphertext, expected_plaintext); |
264 | | } |
265 | | |
266 | | #[test] |
267 | | fn crypt_aes_256_40_byte() { |
268 | | let mut ciphertext = [ |
269 | | 0xa9, 0x99, 0xbd, 0xea, 0x82, 0x9b, 0x8f, 0x2f, 0xb7, 0x52, 0x2f, 0x6b, 0xd8, 0xf6, |
270 | | 0xab, 0x0e, 0x24, 0x51, 0x9e, 0x18, 0x0f, 0xc0, 0x8f, 0x54, 0x15, 0x80, 0xae, 0xbc, |
271 | | 0xa0, 0x5c, 0x8a, 0x11, 0x8d, 0x14, 0x7e, 0xc5, 0xb4, 0xae, 0xd3, 0x37, |
272 | | ]; |
273 | | let expected_plaintext = b"Lorem ipsum dolor sit amet, consectetur\n"; |
274 | | let key = [ |
275 | | 0x64, 0x7c, 0x7a, 0xde, 0xf0, 0xf2, 0x61, 0x49, 0x1c, 0xf1, 0xf1, 0xe3, 0x37, 0xfc, |
276 | | 0xe1, 0x4d, 0x4a, 0x77, 0xd4, 0xeb, 0x9e, 0x3d, 0x75, 0xce, 0x9a, 0x3e, 0x10, 0x50, |
277 | | 0xc2, 0x07, 0x36, 0xb6, |
278 | | ]; |
279 | | |
280 | | roundtrip::<Aes256>(&key, &mut ciphertext, expected_plaintext); |
281 | | } |
282 | | } |