Coverage Report

Created: 2026-02-14 06:51

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
78
    pub fn new(key: &[u8]) -> AesCtrZipKeyStream<C> {
94
78
        AesCtrZipKeyStream {
95
78
            counter: 1,
96
78
            cipher: C::Cipher::new(GenericArray::from_slice(key)),
97
78
            buffer: [0u8; AES_BLOCK_SIZE],
98
78
            pos: AES_BLOCK_SIZE,
99
78
        }
100
78
    }
<zip::aes_ctr::AesCtrZipKeyStream<zip::aes_ctr::Aes128>>::new
Line
Count
Source
93
68
    pub fn new(key: &[u8]) -> AesCtrZipKeyStream<C> {
94
68
        AesCtrZipKeyStream {
95
68
            counter: 1,
96
68
            cipher: C::Cipher::new(GenericArray::from_slice(key)),
97
68
            buffer: [0u8; AES_BLOCK_SIZE],
98
68
            pos: AES_BLOCK_SIZE,
99
68
        }
100
68
    }
<zip::aes_ctr::AesCtrZipKeyStream<zip::aes_ctr::Aes192>>::new
Line
Count
Source
93
6
    pub fn new(key: &[u8]) -> AesCtrZipKeyStream<C> {
94
6
        AesCtrZipKeyStream {
95
6
            counter: 1,
96
6
            cipher: C::Cipher::new(GenericArray::from_slice(key)),
97
6
            buffer: [0u8; AES_BLOCK_SIZE],
98
6
            pos: AES_BLOCK_SIZE,
99
6
        }
100
6
    }
<zip::aes_ctr::AesCtrZipKeyStream<zip::aes_ctr::Aes256>>::new
Line
Count
Source
93
4
    pub fn new(key: &[u8]) -> AesCtrZipKeyStream<C> {
94
4
        AesCtrZipKeyStream {
95
4
            counter: 1,
96
4
            cipher: C::Cipher::new(GenericArray::from_slice(key)),
97
4
            buffer: [0u8; AES_BLOCK_SIZE],
98
4
            pos: AES_BLOCK_SIZE,
99
4
        }
100
4
    }
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
671
    fn crypt_in_place(&mut self, mut target: &mut [u8]) {
111
20.9k
        while !target.is_empty() {
112
20.3k
            if self.pos == AES_BLOCK_SIZE {
113
20.3k
                // Note: AES block size is always 16 bytes, same as u128.
114
20.3k
                self.buffer
115
20.3k
                    .as_mut()
116
20.3k
                    .write_u128::<byteorder::LittleEndian>(self.counter)
117
20.3k
                    .expect("did not expect u128 le conversion to fail");
118
20.3k
                self.cipher
119
20.3k
                    .encrypt_block(GenericArray::from_mut_slice(&mut self.buffer));
120
20.3k
                self.counter += 1;
121
20.3k
                self.pos = 0;
122
20.3k
            }
123
124
20.3k
            let target_len = target.len().min(AES_BLOCK_SIZE - self.pos);
125
126
20.3k
            xor(
127
20.3k
                &mut target[0..target_len],
128
20.3k
                &self.buffer[self.pos..(self.pos + target_len)],
129
            );
130
20.3k
            target = &mut target[target_len..];
131
20.3k
            self.pos += target_len;
132
        }
133
671
    }
<zip::aes_ctr::AesCtrZipKeyStream<zip::aes_ctr::Aes128> as zip::aes_ctr::AesCipher>::crypt_in_place
Line
Count
Source
110
572
    fn crypt_in_place(&mut self, mut target: &mut [u8]) {
111
10.5k
        while !target.is_empty() {
112
9.99k
            if self.pos == AES_BLOCK_SIZE {
113
9.99k
                // Note: AES block size is always 16 bytes, same as u128.
114
9.99k
                self.buffer
115
9.99k
                    .as_mut()
116
9.99k
                    .write_u128::<byteorder::LittleEndian>(self.counter)
117
9.99k
                    .expect("did not expect u128 le conversion to fail");
118
9.99k
                self.cipher
119
9.99k
                    .encrypt_block(GenericArray::from_mut_slice(&mut self.buffer));
120
9.99k
                self.counter += 1;
121
9.99k
                self.pos = 0;
122
9.99k
            }
123
124
9.99k
            let target_len = target.len().min(AES_BLOCK_SIZE - self.pos);
125
126
9.99k
            xor(
127
9.99k
                &mut target[0..target_len],
128
9.99k
                &self.buffer[self.pos..(self.pos + target_len)],
129
            );
130
9.99k
            target = &mut target[target_len..];
131
9.99k
            self.pos += target_len;
132
        }
133
572
    }
<zip::aes_ctr::AesCtrZipKeyStream<zip::aes_ctr::Aes192> as zip::aes_ctr::AesCipher>::crypt_in_place
Line
Count
Source
110
63
    fn crypt_in_place(&mut self, mut target: &mut [u8]) {
111
9.58k
        while !target.is_empty() {
112
9.51k
            if self.pos == AES_BLOCK_SIZE {
113
9.51k
                // Note: AES block size is always 16 bytes, same as u128.
114
9.51k
                self.buffer
115
9.51k
                    .as_mut()
116
9.51k
                    .write_u128::<byteorder::LittleEndian>(self.counter)
117
9.51k
                    .expect("did not expect u128 le conversion to fail");
118
9.51k
                self.cipher
119
9.51k
                    .encrypt_block(GenericArray::from_mut_slice(&mut self.buffer));
120
9.51k
                self.counter += 1;
121
9.51k
                self.pos = 0;
122
9.51k
            }
123
124
9.51k
            let target_len = target.len().min(AES_BLOCK_SIZE - self.pos);
125
126
9.51k
            xor(
127
9.51k
                &mut target[0..target_len],
128
9.51k
                &self.buffer[self.pos..(self.pos + target_len)],
129
            );
130
9.51k
            target = &mut target[target_len..];
131
9.51k
            self.pos += target_len;
132
        }
133
63
    }
<zip::aes_ctr::AesCtrZipKeyStream<zip::aes_ctr::Aes256> as zip::aes_ctr::AesCipher>::crypt_in_place
Line
Count
Source
110
36
    fn crypt_in_place(&mut self, mut target: &mut [u8]) {
111
836
        while !target.is_empty() {
112
800
            if self.pos == AES_BLOCK_SIZE {
113
800
                // Note: AES block size is always 16 bytes, same as u128.
114
800
                self.buffer
115
800
                    .as_mut()
116
800
                    .write_u128::<byteorder::LittleEndian>(self.counter)
117
800
                    .expect("did not expect u128 le conversion to fail");
118
800
                self.cipher
119
800
                    .encrypt_block(GenericArray::from_mut_slice(&mut self.buffer));
120
800
                self.counter += 1;
121
800
                self.pos = 0;
122
800
            }
123
124
800
            let target_len = target.len().min(AES_BLOCK_SIZE - self.pos);
125
126
800
            xor(
127
800
                &mut target[0..target_len],
128
800
                &self.buffer[self.pos..(self.pos + target_len)],
129
            );
130
800
            target = &mut target[target_len..];
131
800
            self.pos += target_len;
132
        }
133
36
    }
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
20.3k
fn xor(dest: &mut [u8], src: &[u8]) {
144
20.3k
    assert_eq!(dest.len(), src.len());
145
146
324k
    for (lhs, rhs) in dest.iter_mut().zip(src.iter()) {
147
324k
        *lhs ^= *rhs;
148
324k
    }
149
20.3k
}
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
}