From cc18a545a87ca616f05114d174690e5cc9614669 Mon Sep 17 00:00:00 2001 From: Igor Tolmachev Date: Tue, 16 Jul 2024 17:24:33 +0900 Subject: Optimize encryption - Add archive for testing encryption of compressed files - Implement incorrect password check - Use custom crc32 function --- src/zip/archive.rs | 19 +++++++++++ src/zip/driver.rs | 25 +++++++++------ src/zip/encryption.rs | 49 +++++++++++++++++++++------- src/zip/error.rs | 2 ++ src/zip/file/info.rs | 16 ++++++---- src/zip/file/read.rs | 88 +++++++++++++++++++++++---------------------------- src/zip/file/write.rs | 6 +--- src/zip/structs.rs | 6 ++-- 8 files changed, 128 insertions(+), 83 deletions(-) (limited to 'src/zip') diff --git a/src/zip/archive.rs b/src/zip/archive.rs index 79e8ca1..569ad87 100644 --- a/src/zip/archive.rs +++ b/src/zip/archive.rs @@ -1,3 +1,4 @@ +use crate::zip::{ZipFileReader, ZipResult}; use crate::{Archive, Zip}; use std::io::{Read, Seek, Write}; @@ -5,6 +6,24 @@ impl Archive> { pub fn comment(&self) -> &String { self.driver.comment() } + + pub fn get_file_reader_by_index_with_password<'d>( + &'d mut self, + index: usize, + password: &[u8], + ) -> ZipResult> { + self.driver + .get_file_reader_with_optional_password(index, Some(password)) + } + + #[inline] + pub fn get_file_reader_by_name_with_password<'d>( + &'d mut self, + name: &str, + password: &[u8], + ) -> ZipResult> { + self.get_file_reader_by_index_with_password(self.get_file_index(name)?, password) + } } impl Archive> {} diff --git a/src/zip/driver.rs b/src/zip/driver.rs index 87f9c1a..62da39f 100644 --- a/src/zip/driver.rs +++ b/src/zip/driver.rs @@ -221,7 +221,7 @@ impl ArchiveRead for Zip { indexes.insert(name.clone(), i); files.push(ZipFileInfo::new( CompressionMethod::from_struct_id(cdr.compression_method)?, - EncryptionMethod::from_bit_flag(bit_flag), + EncryptionMethod::from_bif_flag(bit_flag, cdr.crc, cdr.dos_time), bit_flag, mtime, atime, @@ -258,11 +258,22 @@ impl ArchiveRead for Zip { self.files.get(index).ok_or(ZipError::FileNotFound.into()) } - fn get_file_reader<'d>( + #[inline] + fn get_file_reader<'d>(&'d mut self, index: usize) -> ZipResult> { + self.get_file_reader_with_optional_password(index, None) + } +} + +impl Zip { + pub fn comment(&self) -> &String { + &self.comment + } + + pub fn get_file_reader_with_optional_password<'d>( &'d mut self, index: usize, - password: Option<&str>, - ) -> ZipResult> { + password: Option<&[u8]>, + ) -> ZipResult<::FileReader<'d>> { Ok(ZipFileReader::new( &mut self.io, self.files.get(index).ok_or(ZipError::FileNotFound)?, @@ -271,12 +282,6 @@ impl ArchiveRead for Zip { } } -impl Zip { - pub fn comment(&self) -> &String { - &self.comment - } -} - impl ArchiveWrite for Zip { type FileWriter<'d> = ZipFileWriter<'d, Io> where Io: 'd; } diff --git a/src/zip/encryption.rs b/src/zip/encryption.rs index 84e30d5..28a6bdb 100644 --- a/src/zip/encryption.rs +++ b/src/zip/encryption.rs @@ -1,12 +1,35 @@ use crate::utils::ReadUtils; -use crate::zip::ZipResult; -use crc32fast::Hasher; +use crate::zip::{ZipError, ZipResult}; use std::io::{Read, Result as IoResult}; -fn crc32(byte: u8, crc32: u32) -> u32 { - let mut hasher = Hasher::new_with_initial(crc32 ^ 0xFFFFFFFF); - hasher.update(&[byte]); - hasher.finalize() ^ 0xFFFFFFFF +const TABLE: [u32; 256] = generate_table(); + +const fn generate_table() -> [u32; 256] { + let mut table = [0; 256]; + + let mut b = 0; + while b <= 255 { + let mut crc = b as u32; + + let mut i = 0; + while i < 8 { + if (crc & 1) > 0 { + crc = (crc >> 1) ^ 0xEDB88320 + } else { + crc >>= 1 + } + i += 1; + } + + table[b] = crc; + b += 1 + } + + table +} + +fn crc32(byte: u8, crc: u32) -> u32 { + (crc >> 8) ^ TABLE[((crc & 0xFF) as u8 ^ byte) as usize] } pub struct WeakDecoder { @@ -17,7 +40,7 @@ pub struct WeakDecoder { } impl WeakDecoder { - pub fn new(io: Io, password: &str) -> ZipResult { + pub fn new(io: Io, check: u8, password: &[u8]) -> ZipResult { let mut decoder = Self { key0: 305419896, key1: 591751049, @@ -25,11 +48,14 @@ impl WeakDecoder { io, }; - for c in password.chars() { - decoder.update_keys(c as u8) + for c in password { + decoder.update_keys(*c) } let buf = decoder.read_arr::<12>()?; + if check != buf[11] { + return Err(ZipError::IncorrectPassword.into()); + } Ok(decoder) } @@ -46,7 +72,9 @@ impl WeakDecoder { fn decode_byte(&mut self, byte: u8) -> u8 { let key = self.key2 | 2; - byte ^ (((key * (key ^ 1)) >> 8) as u8) + let byte = byte ^ ((key * (key ^ 1)) >> 8) as u8; + self.update_keys(byte); + byte } } @@ -55,7 +83,6 @@ impl Read for WeakDecoder { let bytes = self.io.read(buf)?; for i in 0..bytes { buf[i] = self.decode_byte(buf[i]); - self.update_keys(buf[i]) } Ok(bytes) } diff --git a/src/zip/error.rs b/src/zip/error.rs index 9ec0956..a4b8c2b 100644 --- a/src/zip/error.rs +++ b/src/zip/error.rs @@ -20,6 +20,7 @@ pub enum ZipError { InvalidFileComment, FileNotFound, + IncorrectPassword, PasswordIsNotSpecified, CompressedDataIsUnseekable, EncryptedDataIsUnseekable, @@ -66,6 +67,7 @@ impl Display for ZipError { Self::InvalidFileComment => write!(f, "Invalid file comment"), Self::FileNotFound => write!(f, "File not found"), + Self::IncorrectPassword => write!(f, "Incorrect password"), Self::PasswordIsNotSpecified => write!(f, "Password is not specified"), Self::CompressedDataIsUnseekable => write!(f, "Compressed data is unseekable"), Self::EncryptedDataIsUnseekable => write!(f, "Encrypted data is unseekable"), diff --git a/src/zip/file/info.rs b/src/zip/file/info.rs index f5d4d8a..599dcc3 100644 --- a/src/zip/file/info.rs +++ b/src/zip/file/info.rs @@ -2,7 +2,7 @@ use crate::driver::ArchiveFileInfo; use crate::zip::{ZipError, ZipResult}; use chrono::{DateTime, Local}; -#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[derive(Debug, PartialEq, Eq, Clone)] pub enum CompressionMethod { Store, Deflate, @@ -28,18 +28,22 @@ impl CompressionMethod { } } -#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[derive(Debug, PartialEq, Eq, Clone)] pub enum EncryptionMethod { None, - Weak, + Weak(u8), Unsupported, } impl EncryptionMethod { - pub(crate) fn from_bit_flag(bit_flag: BitFlag) -> EncryptionMethod { + pub(crate) fn from_bif_flag(bit_flag: BitFlag, crc: u32, dos_time: u16) -> EncryptionMethod { match (bit_flag.is_encrypted(), bit_flag.is_strong_encryption()) { (false, false) => EncryptionMethod::None, - (true, false) => EncryptionMethod::Weak, + (true, false) => EncryptionMethod::Weak(if bit_flag.is_has_data_descriptor() { + (dos_time >> 8) as u8 // Info-ZIP modification + } else { + (crc >> 24) as u8 + }), (true, true) => EncryptionMethod::Unsupported, _ => panic!("impossible"), } @@ -52,7 +56,7 @@ pub struct BitFlag { } pub mod bit { - #[derive(Debug, PartialEq, Eq, Clone, Copy)] + #[derive(Debug, PartialEq, Eq, Clone)] pub enum DeflateMode { Normal, Maximum, diff --git a/src/zip/file/read.rs b/src/zip/file/read.rs index aa665c3..c26b304 100644 --- a/src/zip/file/read.rs +++ b/src/zip/file/read.rs @@ -17,11 +17,12 @@ enum Encryption { } impl Encryption { - pub fn new(io: Io, method: EncryptionMethod, password: Option<&str>) -> ZipResult { - Ok(match method { + pub fn new(io: Io, info: &ZipFileInfo, password: Option<&[u8]>) -> ZipResult { + Ok(match info.encryption_method { EncryptionMethod::None => Self::None(io), - EncryptionMethod::Weak => Self::Weak(WeakDecoder::new( + EncryptionMethod::Weak(check) => Self::Weak(WeakDecoder::new( io, + check, password.ok_or(ZipError::PasswordIsNotSpecified)?, )?), EncryptionMethod::Unsupported => { @@ -61,12 +62,29 @@ enum Compression { } impl Compression { - pub fn new(io: Io, method: CompressionMethod) -> ZipResult { - Ok(match method { + pub fn new(mut io: Io, info: &ZipFileInfo) -> ZipResult { + Ok(match info.compression_method { CompressionMethod::Store => Self::Store(io), CompressionMethod::Deflate => Self::Deflate(DeflateDecoder::new(io)), CompressionMethod::BZip2 => Self::BZip2(BzDecoder::new(io)), - CompressionMethod::Lzma => panic!(), + CompressionMethod::Lzma => { + let buf = io.read_arr::<9>()?; + Compression::Xz(XzDecoder::new_stream( + io, + Stream::new_raw_decoder( + Filters::new().lzma1( + LzmaOptions::new() + .literal_context_bits((buf[4] % 9) as u32) + .literal_position_bits((buf[4] / 9 % 5) as u32) + .position_bits((buf[4] / 45) as u32) + .dict_size( + u32::from_le_bytes(buf[5..9].try_into().unwrap()).max(4096), + ), + ), + ) + .unwrap(), + )) + } CompressionMethod::Zstd => Self::Zstd(ZstdDecoder::new(io)?), CompressionMethod::Xz => Self::Xz(XzDecoder::new(io)), CompressionMethod::Unsupported(id) => { @@ -108,14 +126,14 @@ pub struct ZipFileReader<'d, Io: Read> { impl<'d, Io: Read> FileDriver for ZipFileReader<'d, Io> { type Io = Io; type FileInfo = ZipFileInfo; - - fn info(&self) -> &Self::FileInfo { - self.info - } } impl<'d, Io: Read + Seek> ZipFileReader<'d, Io> { - pub fn new(io: &'d mut Io, info: &'d ZipFileInfo, password: Option<&str>) -> ZipResult { + pub(crate) fn new( + io: &'d mut Io, + info: &'d ZipFileInfo, + password: Option<&[u8]>, + ) -> ZipResult { io.seek(SeekFrom::Start(info.header_pointer))?; let buf = io.read_arr::<30>()?; @@ -127,49 +145,23 @@ impl<'d, Io: Read + Seek> ZipFileReader<'d, Io> { + u16::from_le_bytes(buf[26..28].try_into().unwrap()) as u64 + u16::from_le_bytes(buf[28..30].try_into().unwrap()) as u64; - io.seek(SeekFrom::Start(data_pointer))?; - Ok(Self { - io: match info.compression_method { - CompressionMethod::Lzma => { - let buf = io.read_arr::<9>()?; - Compression::Xz(XzDecoder::new_stream( - Encryption::new( - IoCursor::new( - io, - data_pointer + 9, - data_pointer + info.compressed_size, - )?, - info.encryption_method, - password, - )?, - Stream::new_raw_decoder( - Filters::new().lzma1( - LzmaOptions::new() - .literal_context_bits((buf[4] % 9) as u32) - .literal_position_bits((buf[4] / 9 % 5) as u32) - .position_bits((buf[4] / 45) as u32) - .dict_size( - u32::from_le_bytes(buf[5..9].try_into().unwrap()).max(4096), - ), - ), - ) - .unwrap(), - )) - } - _ => Compression::new( - Encryption::new( - IoCursor::new(io, data_pointer, data_pointer + info.compressed_size)?, - info.encryption_method, - password, - )?, - info.compression_method, + io: Compression::new( + Encryption::new( + IoCursor::new(io, data_pointer, data_pointer + info.compressed_size)?, + info, + password, )?, - }, + info, + )?, info, }) } + pub fn info(&self) -> &ZipFileInfo { + self.info + } + pub fn is_seekable(&self) -> bool { match self.io { Compression::Store(Encryption::None(..)) => true, diff --git a/src/zip/file/write.rs b/src/zip/file/write.rs index d5b686c..20e53b3 100644 --- a/src/zip/file/write.rs +++ b/src/zip/file/write.rs @@ -2,8 +2,8 @@ use crate::driver::FileDriver; use crate::zip::ZipFileInfo; use std::io::Write; +#[allow(dead_code)] pub struct ZipFileWriter<'d, Io: Write> { - #[allow(dead_code)] io: &'d mut Io, info: &'d ZipFileInfo, } @@ -11,8 +11,4 @@ pub struct ZipFileWriter<'d, Io: Write> { impl<'d, Io: Write> FileDriver for ZipFileWriter<'d, Io> { type Io = Io; type FileInfo = ZipFileInfo; - - fn info(&self) -> &Self::FileInfo { - self.info - } } diff --git a/src/zip/structs.rs b/src/zip/structs.rs index a44659b..9fd1aeb 100644 --- a/src/zip/structs.rs +++ b/src/zip/structs.rs @@ -1,4 +1,4 @@ -use crate::structs::{Settings, StructResult}; +use crate::structs::{ByteOrder, Settings, StructResult, VariantIndexType}; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] @@ -60,9 +60,9 @@ pub struct ExtraHeader { #[allow(dead_code)] pub fn serialize(object: &mut T) -> StructResult> { - Settings::default().serialize(object) + Settings::new(ByteOrder::Le, VariantIndexType::U8).serialize(object) } pub fn deserialize<'de, T: Deserialize<'de>>(object: &'de [u8]) -> StructResult { - Settings::default().deserialize(object) + Settings::new(ByteOrder::Le, VariantIndexType::U8).deserialize(object) } -- cgit v1.2.3