diff --git a/src/aes.rs b/src/aes.rs index d78bbcfa..d4df982b 100644 --- a/src/aes.rs +++ b/src/aes.rs @@ -1,86 +1,134 @@ +use crate::aes_ctr; use crate::types::AesMode; -use std::io; - -use byteorder::{LittleEndian, ReadBytesExt}; -use hmac::Hmac; +use hmac::{Hmac, Mac, NewMac}; use sha1::Sha1; +use std::io::{Error, Read}; /// The length of the password verifcation value in bytes -const PWD_VERIFY_LENGTH: u64 = 2; +const PWD_VERIFY_LENGTH: usize = 2; /// The length of the authentication code in bytes -const AUTH_CODE_LENGTH: u64 = 10; +const AUTH_CODE_LENGTH: usize = 10; /// The number of iterations used with PBKDF2 const ITERATION_COUNT: u32 = 1000; -/// AES block size in bytes -const BLOCK_SIZE: usize = 16; + +fn cipher_from_mode(aes_mode: AesMode, key: &[u8]) -> Box { + match aes_mode { + AesMode::Aes128 => Box::new(aes_ctr::AesCtrZipKeyStream::::new(key)) + as Box, + AesMode::Aes192 => Box::new(aes_ctr::AesCtrZipKeyStream::::new(key)) + as Box, + AesMode::Aes256 => Box::new(aes_ctr::AesCtrZipKeyStream::::new(key)) + as Box, + } +} // an aes encrypted file starts with a salt, whose length depends on the used aes mode // followed by a 2 byte password verification value // then the variable length encrypted data // and lastly a 10 byte authentication code -pub(crate) struct AesReader { +pub struct AesReader { reader: R, aes_mode: AesMode, - salt_length: usize, data_length: u64, } -impl AesReader { +impl AesReader { pub fn new(reader: R, aes_mode: AesMode, compressed_size: u64) -> AesReader { - let salt_length = aes_mode.salt_length(); - let data_length = compressed_size - (PWD_VERIFY_LENGTH + AUTH_CODE_LENGTH + salt_length); + let data_length = compressed_size + - (PWD_VERIFY_LENGTH + AUTH_CODE_LENGTH + aes_mode.salt_length()) as u64; Self { reader, aes_mode, - salt_length: salt_length as usize, data_length, } } - pub fn validate(mut self, password: &[u8]) -> Result>, io::Error> { - // the length of the salt depends on the used key size - let mut salt = vec![0; self.salt_length as usize]; - self.reader.read_exact(&mut salt).unwrap(); + /// Read the AES header bytes and validate the password. + /// + /// Even if the validation succeeds, there is still a 1 in 65536 chance that an incorrect + /// password was provided. + /// It isn't possible to check the authentication code in this step. This will be done after + /// reading and decrypting the file. + pub fn validate(mut self, password: &[u8]) -> Result>, Error> { + + let salt_length = self.aes_mode.salt_length(); + let key_length = self.aes_mode.key_length(); + + let mut salt = vec![0; salt_length]; + self.reader.read_exact(&mut salt)?; // next are 2 bytes used for password verification - let mut pwd_verification_value = vec![0; PWD_VERIFY_LENGTH as usize]; - self.reader.read_exact(&mut pwd_verification_value).unwrap(); + let mut pwd_verification_value = vec![0; PWD_VERIFY_LENGTH]; + self.reader.read_exact(&mut pwd_verification_value)?; // derive a key from the password and salt // the length depends on the aes key length - let derived_key_len = (2 * self.aes_mode.key_length() + PWD_VERIFY_LENGTH) as usize; + let derived_key_len = 2 * key_length + PWD_VERIFY_LENGTH; let mut derived_key: Vec = vec![0; derived_key_len]; // use PBKDF2 with HMAC-Sha1 to derive the key pbkdf2::pbkdf2::>(password, &salt, ITERATION_COUNT, &mut derived_key); + let decrypt_key = &derived_key[0..key_length]; + let hmac_key = &derived_key[key_length..key_length * 2]; + let pwd_verify = &derived_key[derived_key_len - 2..]; // the last 2 bytes should equal the password verification value - if pwd_verification_value != &derived_key[derived_key_len - 2..] { + if pwd_verification_value != pwd_verify { // wrong password return Ok(None); } - // the first key_length bytes are used as decryption key - let decrypt_key = &derived_key[0..self.aes_mode.key_length() as usize]; + let cipher = cipher_from_mode(self.aes_mode, &decrypt_key); + let hmac = Hmac::::new_varkey(hmac_key).unwrap(); - panic!("Validating AesReader"); + Ok(Some(AesReaderValid { + reader: self.reader, + data_remaining: self.data_length, + cipher, + hmac, + })) } } -pub(crate) struct AesReaderValid { - reader: AesReader, +pub struct AesReaderValid { + reader: R, + data_remaining: u64, + cipher: Box, + hmac: Hmac, } -impl io::Read for AesReaderValid { - fn read(&mut self, mut buf: &mut [u8]) -> std::io::Result { - panic!("Reading from AesReaderValid") +impl Read for AesReaderValid { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + // get the number of bytes to read, compare as u64 to make sure we can read more than + // 2^32 bytes even on 32 bit systems. + let bytes_to_read = self.data_remaining.min(buf.len() as u64) as usize; + let read = self.reader.read(&mut buf[0..bytes_to_read])?; + + self.cipher.crypt_in_place(&mut buf[0..read]); + self.data_remaining -= read as u64; + + // Update the hmac with the decrypted data + self.hmac.update(&buf[0..read]); + if self.data_remaining <= 0 { + // if there is no data left to read, check the integrity of the data + let mut read_auth = [0; 10]; + self.reader.read_exact(&mut read_auth)?; + let computed_auth = self.hmac.finalize_reset(); + // FIXME: The mac uses the whole sha1 hash each step + // Zip uses HMAC-Sha1-80, which throws away the second half each time + // see https://www.winzip.com/win/en/aes_info.html#auth-faq + // if computed_auth.into_bytes().as_slice() != &read_auth { + // } + } + + Ok(read) } } -impl AesReaderValid { +impl AesReaderValid { /// Consumes this decoder, returning the underlying reader. pub fn into_inner(self) -> R { - self.reader.reader + self.reader } } diff --git a/src/aes_ctr.rs b/src/aes_ctr.rs index a7b498f9..efda6b77 100644 --- a/src/aes_ctr.rs +++ b/src/aes_ctr.rs @@ -79,24 +79,24 @@ where C::Cipher: NewBlockCipher, { /// Creates a new zip variant AES-CTR key stream. - pub fn new(key: &C::Key) -> AesCtrZipKeyStream { + pub fn new(key: &[u8]) -> AesCtrZipKeyStream { AesCtrZipKeyStream { counter: 1, - cipher: C::Cipher::new(GenericArray::from_slice(key.as_ref())), + cipher: C::Cipher::new(GenericArray::from_slice(key)), buffer: [0u8; AES_BLOCK_SIZE], pos: AES_BLOCK_SIZE, } } } -impl AesCtrZipKeyStream +impl AesCipher for AesCtrZipKeyStream where C: AesKind, C::Cipher: BlockCipher, { /// Decrypt or encrypt given data. #[inline] - fn crypt(&mut self, mut target: &mut [u8]) { + fn crypt_in_place(&mut self, mut target: &mut [u8]) { while target.len() > 0 { if self.pos == AES_BLOCK_SIZE { // Note: AES block size is always 16 bytes, same as u128. @@ -122,9 +122,13 @@ where } } +pub trait AesCipher { + fn crypt_in_place(&mut self, target: &mut [u8]); +} + /// XORs a slice in place with another slice. #[inline] -pub fn xor(dest: &mut [u8], src: &[u8]) { +fn xor(dest: &mut [u8], src: &[u8]) { debug_assert_eq!(dest.len(), src.len()); for (lhs, rhs) in dest.iter_mut().zip(src.iter()) { @@ -134,7 +138,7 @@ pub fn xor(dest: &mut [u8], src: &[u8]) { #[cfg(test)] mod tests { - use super::{Aes128, Aes192, Aes256, AesCtrZipKeyStream}; + use super::{Aes128, Aes192, Aes256, AesCipher, AesCtrZipKeyStream}; // The data used in these tests was generated with p7zip without any compression. // It's not possible to recreate the exact same data, since a random salt is used for encryption. @@ -153,12 +157,12 @@ mod tests { let mut key_stream = AesCtrZipKeyStream::::new(&key); let mut plaintext = ciphertext; - key_stream.crypt(&mut plaintext); + key_stream.crypt_in_place(&mut plaintext); assert_eq!(plaintext.to_vec(), expected_plaintext.to_vec()); // Round-tripping should yield the ciphertext again. let mut key_stream = AesCtrZipKeyStream::::new(&key); - key_stream.crypt(&mut plaintext); + key_stream.crypt_in_place(&mut plaintext); assert_eq!(plaintext.to_vec(), ciphertext.to_vec()); } @@ -175,12 +179,12 @@ mod tests { let mut key_stream = AesCtrZipKeyStream::::new(&key); let mut plaintext = ciphertext; - key_stream.crypt(&mut plaintext); + key_stream.crypt_in_place(&mut plaintext); assert_eq!(plaintext.to_vec(), expected_plaintext.to_vec()); // Round-tripping should yield the ciphertext again. let mut key_stream = AesCtrZipKeyStream::::new(&key); - key_stream.crypt(&mut plaintext); + key_stream.crypt_in_place(&mut plaintext); assert_eq!(plaintext.to_vec(), ciphertext.to_vec()); } @@ -197,12 +201,12 @@ mod tests { let mut key_stream = AesCtrZipKeyStream::::new(&key); let mut plaintext = ciphertext; - key_stream.crypt(&mut plaintext); + key_stream.crypt_in_place(&mut plaintext); assert_eq!(plaintext.to_vec(), expected_plaintext.to_vec()); // Round-tripping should yield the ciphertext again. let mut key_stream = AesCtrZipKeyStream::::new(&key); - key_stream.crypt(&mut plaintext); + key_stream.crypt_in_place(&mut plaintext); assert_eq!(plaintext.to_vec(), ciphertext.to_vec()); } @@ -220,12 +224,12 @@ mod tests { let mut key_stream = AesCtrZipKeyStream::::new(&key); let mut plaintext = ciphertext; - key_stream.crypt(&mut plaintext); + key_stream.crypt_in_place(&mut plaintext); assert_eq!(plaintext.to_vec(), expected_plaintext.to_vec()); // Round-tripping should yield the ciphertext again. let mut key_stream = AesCtrZipKeyStream::::new(&key); - key_stream.crypt(&mut plaintext); + key_stream.crypt_in_place(&mut plaintext); assert_eq!(plaintext.to_vec(), ciphertext.to_vec()); } @@ -246,12 +250,12 @@ mod tests { let mut key_stream = AesCtrZipKeyStream::::new(&key); let mut plaintext = ciphertext; - key_stream.crypt(&mut plaintext); + key_stream.crypt_in_place(&mut plaintext); assert_eq!(plaintext.to_vec(), expected_plaintext.to_vec()); // Round-tripping should yield the ciphertext again. let mut key_stream = AesCtrZipKeyStream::::new(&key); - key_stream.crypt(&mut plaintext); + key_stream.crypt_in_place(&mut plaintext); assert_eq!(plaintext.to_vec(), ciphertext.to_vec()); } @@ -272,12 +276,12 @@ mod tests { let mut key_stream = AesCtrZipKeyStream::::new(&key); let mut plaintext = ciphertext; - key_stream.crypt(&mut plaintext); + key_stream.crypt_in_place(&mut plaintext); assert_eq!(plaintext.to_vec(), expected_plaintext.to_vec()); // Round-tripping should yield the ciphertext again. let mut key_stream = AesCtrZipKeyStream::::new(&key); - key_stream.crypt(&mut plaintext); + key_stream.crypt_in_place(&mut plaintext); assert_eq!(plaintext.to_vec(), ciphertext.to_vec()); } @@ -299,12 +303,12 @@ mod tests { let mut key_stream = AesCtrZipKeyStream::::new(&key); let mut plaintext = ciphertext; - key_stream.crypt(&mut plaintext); + key_stream.crypt_in_place(&mut plaintext); assert_eq!(plaintext.to_vec(), expected_plaintext.to_vec()); // Round-tripping should yield the ciphertext again. let mut key_stream = AesCtrZipKeyStream::::new(&key); - key_stream.crypt(&mut plaintext); + key_stream.crypt_in_place(&mut plaintext); assert_eq!(plaintext.to_vec(), ciphertext.to_vec()); } } diff --git a/src/types.rs b/src/types.rs index c2f40f4a..d2a7645d 100644 --- a/src/types.rs +++ b/src/types.rs @@ -309,11 +309,11 @@ pub enum AesMode { } impl AesMode { - pub fn salt_length(&self) -> u64 { + pub fn salt_length(&self) -> usize { self.key_length() / 2 } - pub fn key_length(&self) -> u64 { + pub fn key_length(&self) -> usize { match self { Self::Aes128 => 16, Self::Aes192 => 24,