Coverage Report

Created: 2025-10-28 06:10

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
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
}