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 | 0 | fn cipher_from_mode(aes_mode: AesMode, key: &[u8]) -> Box<dyn aes_ctr::AesCipher> { |
27 | 0 | match aes_mode { |
28 | 0 | AesMode::Aes128 => Box::new(aes_ctr::AesCtrZipKeyStream::<aes_ctr::Aes128>::new(key)) |
29 | 0 | as Box<dyn aes_ctr::AesCipher>, |
30 | 0 | AesMode::Aes192 => Box::new(aes_ctr::AesCtrZipKeyStream::<aes_ctr::Aes192>::new(key)) |
31 | 0 | as Box<dyn aes_ctr::AesCipher>, |
32 | 0 | AesMode::Aes256 => Box::new(aes_ctr::AesCtrZipKeyStream::<aes_ctr::Aes256>::new(key)) |
33 | 0 | as Box<dyn aes_ctr::AesCipher>, |
34 | | } |
35 | 0 | } |
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 | 0 | pub fn new(reader: R, aes_mode: AesMode, compressed_size: u64) -> AesReader<R> { |
49 | 0 | let data_length = compressed_size |
50 | 0 | - (PWD_VERIFY_LENGTH + AUTH_CODE_LENGTH + aes_mode.salt_length()) as u64; |
51 | | |
52 | 0 | Self { |
53 | 0 | reader, |
54 | 0 | aes_mode, |
55 | 0 | data_length, |
56 | 0 | } |
57 | 0 | } |
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 | 0 | pub fn validate(mut self, password: &[u8]) -> io::Result<Option<AesReaderValid<R>>> { |
71 | 0 | let salt_length = self.aes_mode.salt_length(); |
72 | 0 | let key_length = self.aes_mode.key_length(); |
73 | | |
74 | 0 | let mut salt = vec![0; salt_length]; |
75 | 0 | self.reader.read_exact(&mut salt)?; |
76 | | |
77 | | // next are 2 bytes used for password verification |
78 | 0 | let mut pwd_verification_value = vec![0; PWD_VERIFY_LENGTH]; |
79 | 0 | 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 | 0 | let derived_key_len = 2 * key_length + PWD_VERIFY_LENGTH; |
84 | 0 | let mut derived_key: Vec<u8> = vec![0; derived_key_len]; |
85 | | |
86 | | // use PBKDF2 with HMAC-Sha1 to derive the key |
87 | 0 | pbkdf2::pbkdf2::<Hmac<Sha1>>(password, &salt, ITERATION_COUNT, &mut derived_key); |
88 | 0 | let decrypt_key = &derived_key[0..key_length]; |
89 | 0 | let hmac_key = &derived_key[key_length..key_length * 2]; |
90 | 0 | let pwd_verify = &derived_key[derived_key_len - 2..]; |
91 | | |
92 | | // the last 2 bytes should equal the password verification value |
93 | 0 | if pwd_verification_value != pwd_verify { |
94 | | // wrong password |
95 | 0 | return Ok(None); |
96 | 0 | } |
97 | | |
98 | 0 | let cipher = cipher_from_mode(self.aes_mode, decrypt_key); |
99 | 0 | let hmac = Hmac::<Sha1>::new_from_slice(hmac_key).unwrap(); |
100 | | |
101 | 0 | Ok(Some(AesReaderValid { |
102 | 0 | reader: self.reader, |
103 | 0 | data_remaining: self.data_length, |
104 | 0 | cipher, |
105 | 0 | hmac, |
106 | 0 | finalized: false, |
107 | 0 | })) |
108 | 0 | } |
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 | 0 | fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { |
135 | 0 | if self.data_remaining == 0 { |
136 | 0 | return Ok(0); |
137 | 0 | } |
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 | 0 | let bytes_to_read = self.data_remaining.min(buf.len() as u64) as usize; |
142 | 0 | let read = self.reader.read(&mut buf[0..bytes_to_read])?; |
143 | 0 | self.data_remaining -= read as u64; |
144 | | |
145 | | // Update the hmac with the encrypted data |
146 | 0 | self.hmac.update(&buf[0..read]); |
147 | | |
148 | | // decrypt the data |
149 | 0 | 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 | 0 | if self.data_remaining == 0 { |
153 | 0 | assert!( |
154 | 0 | !self.finalized, |
155 | 0 | "Tried to use an already finalized HMAC. This is a bug!" |
156 | | ); |
157 | 0 | 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 | 0 | let mut read_auth_code = [0; AUTH_CODE_LENGTH]; |
162 | 0 | self.reader.read_exact(&mut read_auth_code)?; |
163 | 0 | 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 | 0 | if !constant_time_eq(computed_auth_code, &read_auth_code) { |
167 | 0 | return Err( |
168 | 0 | io::Error::new( |
169 | 0 | io::ErrorKind::InvalidData, |
170 | 0 | "Invalid authentication code, this could be due to an invalid password or errors in the data" |
171 | 0 | ) |
172 | 0 | ); |
173 | 0 | } |
174 | 0 | } |
175 | | |
176 | 0 | Ok(read) |
177 | 0 | } |
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 | | } |