Coverage Report

Created: 2026-06-10 06:36

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/zip/src/aes_ctr.rs
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
3.76k
    pub fn new(key: &[u8]) -> AesCtrZipKeyStream<C> {
94
3.76k
        AesCtrZipKeyStream {
95
3.76k
            counter: 1,
96
3.76k
            cipher: C::Cipher::new(GenericArray::from_slice(key)),
97
3.76k
            buffer: [0u8; AES_BLOCK_SIZE],
98
3.76k
            pos: AES_BLOCK_SIZE,
99
3.76k
        }
100
3.76k
    }
<zip::aes_ctr::AesCtrZipKeyStream<zip::aes_ctr::Aes128>>::new
Line
Count
Source
93
1.00k
    pub fn new(key: &[u8]) -> AesCtrZipKeyStream<C> {
94
1.00k
        AesCtrZipKeyStream {
95
1.00k
            counter: 1,
96
1.00k
            cipher: C::Cipher::new(GenericArray::from_slice(key)),
97
1.00k
            buffer: [0u8; AES_BLOCK_SIZE],
98
1.00k
            pos: AES_BLOCK_SIZE,
99
1.00k
        }
100
1.00k
    }
<zip::aes_ctr::AesCtrZipKeyStream<zip::aes_ctr::Aes192>>::new
Line
Count
Source
93
1.20k
    pub fn new(key: &[u8]) -> AesCtrZipKeyStream<C> {
94
1.20k
        AesCtrZipKeyStream {
95
1.20k
            counter: 1,
96
1.20k
            cipher: C::Cipher::new(GenericArray::from_slice(key)),
97
1.20k
            buffer: [0u8; AES_BLOCK_SIZE],
98
1.20k
            pos: AES_BLOCK_SIZE,
99
1.20k
        }
100
1.20k
    }
<zip::aes_ctr::AesCtrZipKeyStream<zip::aes_ctr::Aes256>>::new
Line
Count
Source
93
1.55k
    pub fn new(key: &[u8]) -> AesCtrZipKeyStream<C> {
94
1.55k
        AesCtrZipKeyStream {
95
1.55k
            counter: 1,
96
1.55k
            cipher: C::Cipher::new(GenericArray::from_slice(key)),
97
1.55k
            buffer: [0u8; AES_BLOCK_SIZE],
98
1.55k
            pos: AES_BLOCK_SIZE,
99
1.55k
        }
100
1.55k
    }
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
25.7k
    fn crypt_in_place(&mut self, mut target: &mut [u8]) {
111
1.25M
        while !target.is_empty() {
112
1.22M
            if self.pos == AES_BLOCK_SIZE {
113
1.22M
                // Note: AES block size is always 16 bytes, same as u128.
114
1.22M
                self.buffer
115
1.22M
                    .as_mut()
116
1.22M
                    .write_u128::<byteorder::LittleEndian>(self.counter)
117
1.22M
                    .expect("did not expect u128 le conversion to fail");
118
1.22M
                self.cipher
119
1.22M
                    .encrypt_block(GenericArray::from_mut_slice(&mut self.buffer));
120
1.22M
                self.counter += 1;
121
1.22M
                self.pos = 0;
122
1.22M
            }
123
124
1.22M
            let target_len = target.len().min(AES_BLOCK_SIZE - self.pos);
125
126
1.22M
            xor(
127
1.22M
                &mut target[0..target_len],
128
1.22M
                &self.buffer[self.pos..(self.pos + target_len)],
129
            );
130
1.22M
            target = &mut target[target_len..];
131
1.22M
            self.pos += target_len;
132
        }
133
25.7k
    }
<zip::aes_ctr::AesCtrZipKeyStream<zip::aes_ctr::Aes128> as zip::aes_ctr::AesCipher>::crypt_in_place
Line
Count
Source
110
10.8k
    fn crypt_in_place(&mut self, mut target: &mut [u8]) {
111
1.10M
        while !target.is_empty() {
112
1.09M
            if self.pos == AES_BLOCK_SIZE {
113
1.09M
                // Note: AES block size is always 16 bytes, same as u128.
114
1.09M
                self.buffer
115
1.09M
                    .as_mut()
116
1.09M
                    .write_u128::<byteorder::LittleEndian>(self.counter)
117
1.09M
                    .expect("did not expect u128 le conversion to fail");
118
1.09M
                self.cipher
119
1.09M
                    .encrypt_block(GenericArray::from_mut_slice(&mut self.buffer));
120
1.09M
                self.counter += 1;
121
1.09M
                self.pos = 0;
122
1.09M
            }
123
124
1.09M
            let target_len = target.len().min(AES_BLOCK_SIZE - self.pos);
125
126
1.09M
            xor(
127
1.09M
                &mut target[0..target_len],
128
1.09M
                &self.buffer[self.pos..(self.pos + target_len)],
129
            );
130
1.09M
            target = &mut target[target_len..];
131
1.09M
            self.pos += target_len;
132
        }
133
10.8k
    }
<zip::aes_ctr::AesCtrZipKeyStream<zip::aes_ctr::Aes192> as zip::aes_ctr::AesCipher>::crypt_in_place
Line
Count
Source
110
4.68k
    fn crypt_in_place(&mut self, mut target: &mut [u8]) {
111
68.0k
        while !target.is_empty() {
112
63.3k
            if self.pos == AES_BLOCK_SIZE {
113
63.3k
                // Note: AES block size is always 16 bytes, same as u128.
114
63.3k
                self.buffer
115
63.3k
                    .as_mut()
116
63.3k
                    .write_u128::<byteorder::LittleEndian>(self.counter)
117
63.3k
                    .expect("did not expect u128 le conversion to fail");
118
63.3k
                self.cipher
119
63.3k
                    .encrypt_block(GenericArray::from_mut_slice(&mut self.buffer));
120
63.3k
                self.counter += 1;
121
63.3k
                self.pos = 0;
122
63.3k
            }
123
124
63.3k
            let target_len = target.len().min(AES_BLOCK_SIZE - self.pos);
125
126
63.3k
            xor(
127
63.3k
                &mut target[0..target_len],
128
63.3k
                &self.buffer[self.pos..(self.pos + target_len)],
129
            );
130
63.3k
            target = &mut target[target_len..];
131
63.3k
            self.pos += target_len;
132
        }
133
4.68k
    }
<zip::aes_ctr::AesCtrZipKeyStream<zip::aes_ctr::Aes256> as zip::aes_ctr::AesCipher>::crypt_in_place
Line
Count
Source
110
10.2k
    fn crypt_in_place(&mut self, mut target: &mut [u8]) {
111
83.5k
        while !target.is_empty() {
112
73.3k
            if self.pos == AES_BLOCK_SIZE {
113
73.3k
                // Note: AES block size is always 16 bytes, same as u128.
114
73.3k
                self.buffer
115
73.3k
                    .as_mut()
116
73.3k
                    .write_u128::<byteorder::LittleEndian>(self.counter)
117
73.3k
                    .expect("did not expect u128 le conversion to fail");
118
73.3k
                self.cipher
119
73.3k
                    .encrypt_block(GenericArray::from_mut_slice(&mut self.buffer));
120
73.3k
                self.counter += 1;
121
73.3k
                self.pos = 0;
122
73.3k
            }
123
124
73.3k
            let target_len = target.len().min(AES_BLOCK_SIZE - self.pos);
125
126
73.3k
            xor(
127
73.3k
                &mut target[0..target_len],
128
73.3k
                &self.buffer[self.pos..(self.pos + target_len)],
129
            );
130
73.3k
            target = &mut target[target_len..];
131
73.3k
            self.pos += target_len;
132
        }
133
10.2k
    }
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
1.22M
fn xor(dest: &mut [u8], src: &[u8]) {
144
1.22M
    assert_eq!(dest.len(), src.len());
145
146
19.6M
    for (lhs, rhs) in dest.iter_mut().zip(src.iter()) {
147
19.6M
        *lhs ^= *rhs;
148
19.6M
    }
149
1.22M
}
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
}