Coverage Report

Created: 2026-03-31 06:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/zip/src/aes.rs
Line
Count
Source
1
//! Implementation of the AES decryption for zip files.
2
//!
3
//! This was implemented according to the [WinZip specification](https://www.winzip.com/win/en/aes_info.html).
4
//! Note that using CRC with AES depends on the used encryption specification, AE-1 or AE-2.
5
//! If the file is marked as encrypted with AE-2 the CRC field is ignored, even if it isn't set to 0.
6
7
use crate::aes_ctr;
8
use crate::types::AesMode;
9
use constant_time_eq::constant_time_eq;
10
use hmac::{Hmac, Mac};
11
use sha1::Sha1;
12
use std::io::{self, Read};
13
14
/// The length of the password verifcation value in bytes
15
const PWD_VERIFY_LENGTH: usize = 2;
16
/// The length of the authentication code in bytes
17
const AUTH_CODE_LENGTH: usize = 10;
18
/// The number of iterations used with PBKDF2
19
const ITERATION_COUNT: u32 = 1000;
20
21
/// Create a AesCipher depending on the used `AesMode` and the given `key`.
22
///
23
/// # Panics
24
///
25
/// This panics if `key` doesn't have the correct size for the chosen aes mode.
26
5.52k
fn cipher_from_mode(aes_mode: AesMode, key: &[u8]) -> Box<dyn aes_ctr::AesCipher> {
27
5.52k
    match aes_mode {
28
1.04k
        AesMode::Aes128 => Box::new(aes_ctr::AesCtrZipKeyStream::<aes_ctr::Aes128>::new(key))
29
1.04k
            as Box<dyn aes_ctr::AesCipher>,
30
2.50k
        AesMode::Aes192 => Box::new(aes_ctr::AesCtrZipKeyStream::<aes_ctr::Aes192>::new(key))
31
2.50k
            as Box<dyn aes_ctr::AesCipher>,
32
1.97k
        AesMode::Aes256 => Box::new(aes_ctr::AesCtrZipKeyStream::<aes_ctr::Aes256>::new(key))
33
1.97k
            as Box<dyn aes_ctr::AesCipher>,
34
    }
35
5.52k
}
36
37
// An aes encrypted file starts with a salt, whose length depends on the used aes mode
38
// followed by a 2 byte password verification value
39
// then the variable length encrypted data
40
// and lastly a 10 byte authentication code
41
pub struct AesReader<R> {
42
    reader: R,
43
    aes_mode: AesMode,
44
    data_length: u64,
45
}
46
47
impl<R: Read> AesReader<R> {
48
6.07k
    pub fn new(reader: R, aes_mode: AesMode, compressed_size: u64) -> AesReader<R> {
49
6.07k
        let data_length = compressed_size
50
6.07k
            - (PWD_VERIFY_LENGTH + AUTH_CODE_LENGTH + aes_mode.salt_length()) as u64;
51
52
6.07k
        Self {
53
6.07k
            reader,
54
6.07k
            aes_mode,
55
6.07k
            data_length,
56
6.07k
        }
57
6.07k
    }
58
59
    /// Read the AES header bytes and validate the password.
60
    ///
61
    /// Even if the validation succeeds, there is still a 1 in 65536 chance that an incorrect
62
    /// password was provided.
63
    /// It isn't possible to check the authentication code in this step. This will be done after
64
    /// reading and decrypting the file.
65
    ///
66
    /// # Returns
67
    ///
68
    /// If the password verification failed `Ok(None)` will be returned to match the validate
69
    /// method of ZipCryptoReader.
70
6.07k
    pub fn validate(mut self, password: &[u8]) -> io::Result<Option<AesReaderValid<R>>> {
71
6.07k
        let salt_length = self.aes_mode.salt_length();
72
6.07k
        let key_length = self.aes_mode.key_length();
73
74
6.07k
        let mut salt = vec![0; salt_length];
75
6.07k
        self.reader.read_exact(&mut salt)?;
76
77
        // next are 2 bytes used for password verification
78
6.07k
        let mut pwd_verification_value = vec![0; PWD_VERIFY_LENGTH];
79
6.07k
        self.reader.read_exact(&mut pwd_verification_value)?;
80
81
        // derive a key from the password and salt
82
        // the length depends on the aes key length
83
6.07k
        let derived_key_len = 2 * key_length + PWD_VERIFY_LENGTH;
84
6.07k
        let mut derived_key: Vec<u8> = vec![0; derived_key_len];
85
86
        // use PBKDF2 with HMAC-Sha1 to derive the key
87
6.07k
        pbkdf2::pbkdf2::<Hmac<Sha1>>(password, &salt, ITERATION_COUNT, &mut derived_key);
88
6.07k
        let decrypt_key = &derived_key[0..key_length];
89
6.07k
        let hmac_key = &derived_key[key_length..key_length * 2];
90
6.07k
        let pwd_verify = &derived_key[derived_key_len - 2..];
91
92
        // the last 2 bytes should equal the password verification value
93
6.07k
        if pwd_verification_value != pwd_verify {
94
            // wrong password
95
554
            return Ok(None);
96
5.52k
        }
97
98
5.52k
        let cipher = cipher_from_mode(self.aes_mode, decrypt_key);
99
5.52k
        let hmac = Hmac::<Sha1>::new_from_slice(hmac_key).unwrap();
100
101
5.52k
        Ok(Some(AesReaderValid {
102
5.52k
            reader: self.reader,
103
5.52k
            data_remaining: self.data_length,
104
5.52k
            cipher,
105
5.52k
            hmac,
106
5.52k
            finalized: false,
107
5.52k
        }))
108
6.07k
    }
109
}
110
111
/// A reader for aes encrypted files, which has already passed the first password check.
112
///
113
/// There is a 1 in 65536 chance that an invalid password passes that check.
114
/// After the data has been read and decrypted an HMAC will be checked and provide a final means
115
/// to check if either the password is invalid or if the data has been changed.
116
pub struct AesReaderValid<R: Read> {
117
    reader: R,
118
    data_remaining: u64,
119
    cipher: Box<dyn aes_ctr::AesCipher>,
120
    hmac: Hmac<Sha1>,
121
    finalized: bool,
122
}
123
124
impl<R: Read> Read for AesReaderValid<R> {
125
    /// This implementation does not fulfill all requirements set in the trait documentation.
126
    ///
127
    /// ```txt
128
    /// "If an error is returned then it must be guaranteed that no bytes were read."
129
    /// ```
130
    ///
131
    /// Whether this applies to errors that occur while reading the encrypted data depends on the
132
    /// underlying reader. If the error occurs while verifying the HMAC, the reader might become
133
    /// practically unusable, since its position after the error is not known.
134
39.2k
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
135
39.2k
        if self.data_remaining == 0 {
136
0
            return Ok(0);
137
39.2k
        }
138
139
        // get the number of bytes to read, compare as u64 to make sure we can read more than
140
        // 2^32 bytes even on 32 bit systems.
141
39.2k
        let bytes_to_read = self.data_remaining.min(buf.len() as u64) as usize;
142
39.2k
        let read = self.reader.read(&mut buf[0..bytes_to_read])?;
143
39.2k
        self.data_remaining -= read as u64;
144
145
        // Update the hmac with the encrypted data
146
39.2k
        self.hmac.update(&buf[0..read]);
147
148
        // decrypt the data
149
39.2k
        self.cipher.crypt_in_place(&mut buf[0..read]);
150
151
        // if there is no data left to read, check the integrity of the data
152
39.2k
        if self.data_remaining == 0 {
153
1.85k
            assert!(
154
1.85k
                !self.finalized,
155
0
                "Tried to use an already finalized HMAC. This is a bug!"
156
            );
157
1.85k
            self.finalized = true;
158
159
            // Zip uses HMAC-Sha1-80, which only uses the first half of the hash
160
            // see https://www.winzip.com/win/en/aes_info.html#auth-faq
161
1.85k
            let mut read_auth_code = [0; AUTH_CODE_LENGTH];
162
1.85k
            self.reader.read_exact(&mut read_auth_code)?;
163
1.64k
            let computed_auth_code = &self.hmac.finalize_reset().into_bytes()[0..AUTH_CODE_LENGTH];
164
165
            // use constant time comparison to mitigate timing attacks
166
1.64k
            if !constant_time_eq(computed_auth_code, &read_auth_code) {
167
1.64k
                return Err(
168
1.64k
                    io::Error::new(
169
1.64k
                        io::ErrorKind::InvalidData,
170
1.64k
                        "Invalid authentication code, this could be due to an invalid password or errors in the data"
171
1.64k
                    )
172
1.64k
                );
173
0
            }
174
37.3k
        }
175
176
37.3k
        Ok(read)
177
39.2k
    }
178
}
179
180
impl<R: Read> AesReaderValid<R> {
181
    /// Consumes this decoder, returning the underlying reader.
182
0
    pub fn into_inner(self) -> R {
183
0
        self.reader
184
0
    }
185
}