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 {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result73 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`.
new(key: &[u8]) -> AesCtrZipKeyStream<C>93 pub fn new(key: &[u8]) -> AesCtrZipKeyStream<C> {
94 AesCtrZipKeyStream {
95 counter: 1,
96 cipher: C::Cipher::new(GenericArray::from_slice(key)),
97 buffer: [0u8; AES_BLOCK_SIZE],
98 pos: AES_BLOCK_SIZE,
99 }
100 }
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]
crypt_in_place(&mut self, mut target: &mut [u8])110 fn crypt_in_place(&mut self, mut target: &mut [u8]) {
111 while !target.is_empty() {
112 if self.pos == AES_BLOCK_SIZE {
113 // Note: AES block size is always 16 bytes, same as u128.
114 self.buffer
115 .as_mut()
116 .write_u128::<byteorder::LittleEndian>(self.counter)
117 .expect("did not expect u128 le conversion to fail");
118 self.cipher
119 .encrypt_block(GenericArray::from_mut_slice(&mut self.buffer));
120 self.counter += 1;
121 self.pos = 0;
122 }
123
124 let target_len = target.len().min(AES_BLOCK_SIZE - self.pos);
125
126 xor(
127 &mut target[0..target_len],
128 &self.buffer[self.pos..(self.pos + target_len)],
129 );
130 target = &mut target[target_len..];
131 self.pos += target_len;
132 }
133 }
134 }
135
136 /// This trait allows using generic AES ciphers with different key sizes.
137 pub trait AesCipher {
crypt_in_place(&mut self, target: &mut [u8])138 fn crypt_in_place(&mut self, target: &mut [u8]);
139 }
140
141 /// XORs a slice in place with another slice.
142 #[inline]
xor(dest: &mut [u8], src: &[u8])143 fn xor(dest: &mut [u8], src: &[u8]) {
144 assert_eq!(dest.len(), src.len());
145
146 for (lhs, rhs) in dest.iter_mut().zip(src.iter()) {
147 *lhs ^= *rhs;
148 }
149 }
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.
roundtrip<Aes>(key: &[u8], ciphertext: &mut [u8], expected_plaintext: &[u8]) where Aes: AesKind, Aes::Cipher: KeyInit + BlockEncrypt,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]
new_with_wrong_key_size()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]
crypt_aes_256_0_byte()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]
crypt_aes_128_5_byte()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]
crypt_aes_192_5_byte()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]
crypt_aes_256_5_byte()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]
crypt_aes_128_40_byte()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]
crypt_aes_192_40_byte()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]
crypt_aes_256_40_byte()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 }
283