zip/
zipcrypto.rs

1//! Implementation of the ZipCrypto algorithm
2//!
3//! The following paper was used to implement the ZipCrypto algorithm:
4//! [https://courses.cs.ut.ee/MTAT.07.022/2015_fall/uploads/Main/dmitri-report-f15-16.pdf](https://courses.cs.ut.ee/MTAT.07.022/2015_fall/uploads/Main/dmitri-report-f15-16.pdf)
5
6use std::fmt::{Debug, Formatter};
7use std::hash::Hash;
8use std::num::Wrapping;
9
10use crate::result::ZipError;
11
12/// A container to hold the current key state
13#[cfg_attr(fuzzing, derive(arbitrary::Arbitrary))]
14#[derive(Clone, Copy, Hash, Ord, PartialOrd, Eq, PartialEq)]
15pub(crate) struct ZipCryptoKeys {
16    key_0: Wrapping<u32>,
17    key_1: Wrapping<u32>,
18    key_2: Wrapping<u32>,
19}
20
21impl Debug for ZipCryptoKeys {
22    #[allow(unreachable_code)]
23    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
24        #[cfg(not(any(test, fuzzing)))]
25        {
26            use std::collections::hash_map::DefaultHasher;
27            use std::hash::Hasher;
28            let mut t = DefaultHasher::new();
29            self.hash(&mut t);
30            f.write_fmt(format_args!("ZipCryptoKeys(hash {})", t.finish()))
31        }
32        #[cfg(any(test, fuzzing))]
33        f.write_fmt(format_args!(
34            "ZipCryptoKeys::of({:#10x},{:#10x},{:#10x})",
35            self.key_0, self.key_1, self.key_2
36        ))
37    }
38}
39
40impl ZipCryptoKeys {
41    const fn new() -> ZipCryptoKeys {
42        ZipCryptoKeys {
43            key_0: Wrapping(0x12345678),
44            key_1: Wrapping(0x23456789),
45            key_2: Wrapping(0x34567890),
46        }
47    }
48
49    #[allow(unused)]
50    pub const fn of(key_0: u32, key_1: u32, key_2: u32) -> ZipCryptoKeys {
51        ZipCryptoKeys {
52            key_0: Wrapping(key_0),
53            key_1: Wrapping(key_1),
54            key_2: Wrapping(key_2),
55        }
56    }
57
58    fn update(&mut self, input: u8) {
59        self.key_0 = ZipCryptoKeys::crc32(self.key_0, input);
60        self.key_1 =
61            (self.key_1 + (self.key_0 & Wrapping(0xff))) * Wrapping(0x08088405) + Wrapping(1);
62        self.key_2 = ZipCryptoKeys::crc32(self.key_2, (self.key_1 >> 24).0 as u8);
63    }
64
65    fn stream_byte(&mut self) -> u8 {
66        let temp: Wrapping<u16> = Wrapping(self.key_2.0 as u16) | Wrapping(3);
67        ((temp * (temp ^ Wrapping(1))) >> 8).0 as u8
68    }
69
70    fn decrypt_byte(&mut self, cipher_byte: u8) -> u8 {
71        let plain_byte: u8 = self.stream_byte() ^ cipher_byte;
72        self.update(plain_byte);
73        plain_byte
74    }
75
76    #[allow(dead_code)]
77    fn encrypt_byte(&mut self, plain_byte: u8) -> u8 {
78        let cipher_byte: u8 = self.stream_byte() ^ plain_byte;
79        self.update(plain_byte);
80        cipher_byte
81    }
82
83    fn crc32(crc: Wrapping<u32>, input: u8) -> Wrapping<u32> {
84        (crc >> 8) ^ Wrapping(CRCTABLE[((crc & Wrapping(0xff)).0 as u8 ^ input) as usize])
85    }
86    pub(crate) fn derive(password: &[u8]) -> ZipCryptoKeys {
87        let mut keys = ZipCryptoKeys::new();
88        for byte in password.iter() {
89            keys.update(*byte);
90        }
91        keys
92    }
93}
94
95/// A ZipCrypto reader with unverified password
96pub struct ZipCryptoReader<R> {
97    file: R,
98    keys: ZipCryptoKeys,
99}
100
101pub enum ZipCryptoValidator {
102    PkzipCrc32(u32),
103    InfoZipMsdosTime(u16),
104}
105
106impl<R: std::io::Read> ZipCryptoReader<R> {
107    /// Note: The password is `&[u8]` and not `&str` because the
108    /// [zip specification](https://pkware.cachefly.net/webdocs/APPNOTE/APPNOTE-6.3.3.TXT)
109    /// does not specify password encoding (see function `update_keys` in the specification).
110    /// Therefore, if `&str` was used, the password would be UTF-8 and it
111    /// would be impossible to decrypt files that were encrypted with a
112    /// password byte sequence that is unrepresentable in UTF-8.
113    pub fn new(file: R, password: &[u8]) -> ZipCryptoReader<R> {
114        ZipCryptoReader {
115            file,
116            keys: ZipCryptoKeys::derive(password),
117        }
118    }
119
120    /// Read the ZipCrypto header bytes and validate the password.
121    pub fn validate(
122        mut self,
123        validator: ZipCryptoValidator,
124    ) -> Result<ZipCryptoReaderValid<R>, ZipError> {
125        // ZipCrypto prefixes a file with a 12 byte header
126        let mut header_buf = [0u8; 12];
127        self.file.read_exact(&mut header_buf)?;
128        for byte in header_buf.iter_mut() {
129            *byte = self.keys.decrypt_byte(*byte);
130        }
131
132        match validator {
133            ZipCryptoValidator::PkzipCrc32(crc32_plaintext) => {
134                // PKZIP before 2.0 used 2 byte CRC check.
135                // PKZIP 2.0+ used 1 byte CRC check. It's more secure.
136                // We also use 1 byte CRC.
137
138                if (crc32_plaintext >> 24) as u8 != header_buf[11] {
139                    return Err(ZipError::InvalidPassword);
140                }
141            }
142            ZipCryptoValidator::InfoZipMsdosTime(last_mod_time) => {
143                // Info-ZIP modification to ZipCrypto format:
144                // If bit 3 of the general purpose bit flag is set
145                // (indicates that the file uses a data-descriptor section),
146                // it uses high byte of 16-bit File Time.
147                // Info-ZIP code probably writes 2 bytes of File Time.
148                // We check only 1 byte.
149
150                if (last_mod_time >> 8) as u8 != header_buf[11] {
151                    return Err(ZipError::InvalidPassword);
152                }
153            }
154        }
155
156        Ok(ZipCryptoReaderValid { reader: self })
157    }
158}
159#[allow(unused)]
160pub(crate) struct ZipCryptoWriter<W> {
161    pub(crate) writer: W,
162    pub(crate) buffer: Vec<u8>,
163    pub(crate) keys: ZipCryptoKeys,
164}
165impl<W: std::io::Write> ZipCryptoWriter<W> {
166    #[allow(unused)]
167    pub(crate) fn finish(mut self, crc32: u32) -> std::io::Result<W> {
168        self.buffer[11] = (crc32 >> 24) as u8;
169        for byte in self.buffer.iter_mut() {
170            *byte = self.keys.encrypt_byte(*byte);
171        }
172        self.writer.write_all(&self.buffer)?;
173        self.writer.flush()?;
174        Ok(self.writer)
175    }
176}
177impl<W: std::io::Write> std::io::Write for ZipCryptoWriter<W> {
178    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
179        self.buffer.extend_from_slice(buf);
180        Ok(buf.len())
181    }
182    fn flush(&mut self) -> std::io::Result<()> {
183        Ok(())
184    }
185}
186
187/// A ZipCrypto reader with verified password
188pub struct ZipCryptoReaderValid<R> {
189    reader: ZipCryptoReader<R>,
190}
191
192impl<R: std::io::Read> std::io::Read for ZipCryptoReaderValid<R> {
193    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
194        // Note: There might be potential for optimization. Inspiration can be found at:
195        // https://github.com/kornelski/7z/blob/master/CPP/7zip/Crypto/ZipCrypto.cpp
196
197        let n = self.reader.file.read(buf)?;
198        for byte in buf.iter_mut().take(n) {
199            *byte = self.reader.keys.decrypt_byte(*byte);
200        }
201        Ok(n)
202    }
203}
204
205impl<R: std::io::Read> ZipCryptoReaderValid<R> {
206    /// Consumes this decoder, returning the underlying reader.
207    pub fn into_inner(self) -> R {
208        self.reader.file
209    }
210}
211
212static CRCTABLE: [u32; 256] = [
213    0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
214    0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
215    0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
216    0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
217    0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
218    0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
219    0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
220    0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
221    0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
222    0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
223    0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
224    0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
225    0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
226    0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
227    0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
228    0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
229    0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
230    0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
231    0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
232    0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
233    0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
234    0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
235    0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
236    0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
237    0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
238    0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
239    0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
240    0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
241    0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
242    0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
243    0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
244    0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
245];