From 7154fc7f4790d66965e2d551f7047c680009c83c Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 22 Nov 2025 11:52:29 +0200 Subject: [PATCH 01/10] Detect AES via compression=99 --- .../io/archive/zip/AbstractZipReaderImpl.class.php | 12 +++++++++--- .../zip/unittest/vendors/SevenZipFileTest.class.php | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/php/io/archive/zip/AbstractZipReaderImpl.class.php b/src/main/php/io/archive/zip/AbstractZipReaderImpl.class.php index 62dfa4d3..0b30a668 100755 --- a/src/main/php/io/archive/zip/AbstractZipReaderImpl.class.php +++ b/src/main/php/io/archive/zip/AbstractZipReaderImpl.class.php @@ -1,7 +1,7 @@ skip= $header['compressed']+ 16; } - // Bit 1: The file is encrypted - if ($header['flags'] & 1) { + // AES vs. traditional PKZIP cipher + if (99 === $header['compression']) { + $aes= unpack('vheader/vsize/vversion/a2vendor/cstrength/vcompression', $extra); + + // TODO: Implement + + throw new MethodNotImplementedException('Not yet implemented', 'AES'); + } else if ($header['flags'] & 1) { $cipher= new ZipCipher($this->password); $preamble= $cipher->decipher($this->streamRead(12)); diff --git a/src/test/php/io/archive/zip/unittest/vendors/SevenZipFileTest.class.php b/src/test/php/io/archive/zip/unittest/vendors/SevenZipFileTest.class.php index ea27f4fb..e7e2964a 100755 --- a/src/test/php/io/archive/zip/unittest/vendors/SevenZipFileTest.class.php +++ b/src/test/php/io/archive/zip/unittest/vendors/SevenZipFileTest.class.php @@ -82,7 +82,7 @@ public function zipCryptoPasswordProtected() { $this->assertSecuredEntriesIn($this->archiveReaderFor($this->vendor(), 'zip-crypto')); } - #[Test, Ignore('Not yet supported')] + #[Test] public function aes256PasswordProtected() { $this->assertSecuredEntriesIn($this->archiveReaderFor($this->vendor(), 'aes-256')); } From b891e828974c156403f0abac58f6d5c70d29a54e Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 22 Nov 2025 16:27:38 +0200 Subject: [PATCH 02/10] Implement AES encryption --- .../io/archive/zip/AESInputStream.class.php | 106 ++++++++++++++++++ .../zip/AbstractZipReaderImpl.class.php | 39 +++++-- .../archive/zip/ZipFileInputStream.class.php | 3 +- 3 files changed, 138 insertions(+), 10 deletions(-) create mode 100755 src/main/php/io/archive/zip/AESInputStream.class.php diff --git a/src/main/php/io/archive/zip/AESInputStream.class.php b/src/main/php/io/archive/zip/AESInputStream.class.php new file mode 100755 index 00000000..c71eb464 --- /dev/null +++ b/src/main/php/io/archive/zip/AESInputStream.class.php @@ -0,0 +1,106 @@ +in= $in; + $this->key= $key; + + $this->cipher= 'aes-'.(strlen($key) * 8).'-ecb'; + $this->counter= "\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; + $this->hmac= hash_init('sha1', HASH_HMAC, $auth); + } + + /** + * Decrypt, updating the HMAC and the counter while doing so + * + * @param string $input + * @return string + */ + private function decrypt($input) { + hash_update($this->hmac, $input); + + $return= ''; + for ($offset= 0, $l= strlen($input); $offset < $l; $offset+= self::BLOCK) { + + // Encrypt counter block using AES-ECB + $keystream= openssl_encrypt( + $this->counter, + $this->cipher, + $this->key, + OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING + ); + + // Take relevant part + $return.= substr($input, $offset, self::BLOCK) ^ $keystream; + + // Increment little-endian counter + for ($j= 0, $carry= 1; $j < 16 && $carry; $j++) { + $s= ord($this->counter[$j]) + $carry; + $this->counter[$j]= chr($s & 0xff); + $carry= $s >> 8; + } + } + return $return; + } + + /** + * Read a string + * + * @param int $limit default 8192 + * @return string + * @throws lang.IllegalStateException when HMAC verification fails + */ + public function read($limit= 8192) { + $chunk= $this->in->read($limit); + if ($this->in->available()) return $this->decrypt($chunk); + + // Verify HMAC checksum for last block + $plain= $this->decrypt(substr($chunk, 0, -10)); + + $mac= hash_final($this->hmac, true); + if (0 !== substr_compare($mac, substr($chunk, -10), 0, 10)) { + throw new IllegalStateException('HMAC verification failed — corrupted data'); + } + + return $plain; + } + + /** + * Returns the number of bytes that can be read from this stream + * without blocking. + * + * @return int + */ + public function available() { + return $this->in->available(); + } + + /** + * Close this buffer + * + * @return void + */ + public function close() { + $this->in->close(); + } +} \ No newline at end of file diff --git a/src/main/php/io/archive/zip/AbstractZipReaderImpl.class.php b/src/main/php/io/archive/zip/AbstractZipReaderImpl.class.php index 0b30a668..5313a81a 100755 --- a/src/main/php/io/archive/zip/AbstractZipReaderImpl.class.php +++ b/src/main/php/io/archive/zip/AbstractZipReaderImpl.class.php @@ -2,7 +2,7 @@ use io\streams\InputStream; use lang\{FormatException, IllegalArgumentException, MethodNotImplementedException}; -use util\Date; +use util\{Date, Secret}; /** * Abstract base class for zip reader implementations @@ -34,14 +34,13 @@ public function __construct(InputStream $stream) { /** * Set password to use when extracting * - * @param string password + * @param ?string|util.Secret $password */ public function setPassword($password) { if (null === $password) { $this->password= null; } else { - $this->password= new ZipCipher(); - $this->password->initialize(iconv(\xp::ENCODING, 'cp437', $password)); + $this->password= $password instanceof Secret ? $password : new Secret($password); } } @@ -229,12 +228,31 @@ public function currentEntry() { // AES vs. traditional PKZIP cipher if (99 === $header['compression']) { $aes= unpack('vheader/vsize/vversion/a2vendor/cstrength/vcompression', $extra); + switch ($aes['strength']) { + case 1: $sl= 8; $dl= 16; break; + case 2: $sl= 12; $dl= 24; break; + case 3: $sl= 16; $dl= 32; break; + default: throw new IllegalArgumentException('Invalid AES strength '.$aes['strength']); + } - // TODO: Implement + // Verify password + $salt= $this->streamRead($sl); + $pvv= $this->streamRead(2); + $dk= hash_pbkdf2('sha1', $this->password->reveal(), $salt, 1000, 2 * $dl + 2, true); + if (0 !== substr_compare($dk, $pvv, 64, 2)) { + throw new IllegalArgumentException('The password did not match'); + } - throw new MethodNotImplementedException('Not yet implemented', 'AES'); + $this->skip-= $sl + 2; + $header['compression']= $aes['compression']; + $is= new AESInputStream( + new ZipFileInputStream($this, $this->position, $header['compressed'] - $sl - 2), + substr($dk, 0, 32), + substr($dk, 32, 32) + ); } else if ($header['flags'] & 1) { - $cipher= new ZipCipher($this->password); + $cipher= new ZipCipher(); + $cipher->initialize(iconv(\xp::ENCODING, 'cp437', $this->password->reveal())); $preamble= $cipher->decipher($this->streamRead(12)); // Verify @@ -243,9 +261,12 @@ public function currentEntry() { } // Password matches. - $this->skip-= 12; + $this->skip-= 12; $header['compressed']-= 12; - $is= new DecipheringInputStream(new ZipFileInputStream($this, $this->position, $header['compressed']), $cipher); + $is= new DecipheringInputStream( + new ZipFileInputStream($this, $this->position, $header['compressed']), + $cipher + ); } else { $is= new ZipFileInputStream($this, $this->position, $header['compressed']); } diff --git a/src/main/php/io/archive/zip/ZipFileInputStream.class.php b/src/main/php/io/archive/zip/ZipFileInputStream.class.php index a4ce361c..722fa1af 100755 --- a/src/main/php/io/archive/zip/ZipFileInputStream.class.php +++ b/src/main/php/io/archive/zip/ZipFileInputStream.class.php @@ -1,12 +1,13 @@ Date: Sat, 22 Nov 2025 17:58:38 +0200 Subject: [PATCH 03/10] Consistently throw IllegalAccessExceptions for missing or mismatched passwords --- .../zip/AbstractZipReaderImpl.class.php | 19 +++++++---- .../vendors/SevenZipFileTest.class.php | 33 +++++++++---------- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/src/main/php/io/archive/zip/AbstractZipReaderImpl.class.php b/src/main/php/io/archive/zip/AbstractZipReaderImpl.class.php index 5313a81a..cb5a6838 100755 --- a/src/main/php/io/archive/zip/AbstractZipReaderImpl.class.php +++ b/src/main/php/io/archive/zip/AbstractZipReaderImpl.class.php @@ -1,7 +1,7 @@ password) { + throw new IllegalAccessException('No password set'); + } + $aes= unpack('vheader/vsize/vversion/a2vendor/cstrength/vcompression', $extra); switch ($aes['strength']) { case 1: $sl= 8; $dl= 16; break; @@ -240,7 +244,7 @@ public function currentEntry() { $pvv= $this->streamRead(2); $dk= hash_pbkdf2('sha1', $this->password->reveal(), $salt, 1000, 2 * $dl + 2, true); if (0 !== substr_compare($dk, $pvv, 64, 2)) { - throw new IllegalArgumentException('The password did not match'); + throw new IllegalAccessException('The password did not match'); } $this->skip-= $sl + 2; @@ -251,13 +255,16 @@ public function currentEntry() { substr($dk, 32, 32) ); } else if ($header['flags'] & 1) { + if (null === $this->password) { + throw new IllegalAccessException('No password set'); + } + + // Verify password $cipher= new ZipCipher(); $cipher->initialize(iconv(\xp::ENCODING, 'cp437', $this->password->reveal())); $preamble= $cipher->decipher($this->streamRead(12)); - - // Verify - if (ord($preamble[11]) !== (($header['crc'] >> 24) & 0xFF)) { - throw new IllegalArgumentException('The password did not match ('.ord($preamble[11]).' vs. '.(($header['crc'] >> 24) & 0xFF).')'); + if (ord($preamble[11]) !== (($header['crc'] >> 24) & 0xff)) { + throw new IllegalAccessException('The password did not match'); } // Password matches. diff --git a/src/test/php/io/archive/zip/unittest/vendors/SevenZipFileTest.class.php b/src/test/php/io/archive/zip/unittest/vendors/SevenZipFileTest.class.php index e7e2964a..7f70c982 100755 --- a/src/test/php/io/archive/zip/unittest/vendors/SevenZipFileTest.class.php +++ b/src/test/php/io/archive/zip/unittest/vendors/SevenZipFileTest.class.php @@ -1,8 +1,9 @@ assertCompressedEntryIn($this->archiveReaderFor($this->vendor(), 'ppmd')); } - /** - * Assertion helper - * - * @param io.archive.zip.ZipArchiveReader reader - * @throws unittest.AssertionFailedError - */ - protected function assertSecuredEntriesIn($reader) { + #[Test, Expect(IllegalAccessException::class), Values(['zip-crypto', 'aes-256'])] + public function missing_password($fixture) { + $this->archiveReaderFor($this->vendor(), $fixture)->iterator()->next(); + } + + #[Test, Expect(IllegalAccessException::class), Values(['zip-crypto', 'aes-256'])] + public function incorrect_password($fixture) { + $this->archiveReaderFor($this->vendor(), $fixture)->usingPassword('wrong')->iterator()->next(); + } + + #[Test, Values(['zip-crypto', 'aes-256'])] + public function password_protected($fixture) { + $reader= $this->archiveReaderFor($this->vendor(), $fixture); with ($it= $reader->usingPassword('secret')->iterator()); { $entry= $it->next(); Assert::equals('password.txt', $entry->getName()); @@ -76,14 +83,4 @@ protected function assertSecuredEntriesIn($reader) { Assert::equals('Very secret contents', (string)Streams::readAll($entry->in())); } } - - #[Test] - public function zipCryptoPasswordProtected() { - $this->assertSecuredEntriesIn($this->archiveReaderFor($this->vendor(), 'zip-crypto')); - } - - #[Test] - public function aes256PasswordProtected() { - $this->assertSecuredEntriesIn($this->archiveReaderFor($this->vendor(), 'aes-256')); - } } \ No newline at end of file From b861dd296795d6963757e3ea58450736ff7a622f Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 22 Nov 2025 19:30:20 +0200 Subject: [PATCH 04/10] Make ZipArchiveWriter::usingPassword() accept util.Secret instances --- src/main/php/io/archive/zip/ZipArchiveWriter.class.php | 10 +++++++--- .../zip/unittest/ZipArchiveWriterTest.class.php | 9 +++++---- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/main/php/io/archive/zip/ZipArchiveWriter.class.php b/src/main/php/io/archive/zip/ZipArchiveWriter.class.php index f40c9c0d..c54462e5 100755 --- a/src/main/php/io/archive/zip/ZipArchiveWriter.class.php +++ b/src/main/php/io/archive/zip/ZipArchiveWriter.class.php @@ -2,7 +2,7 @@ use io\streams\OutputStream; use lang\{Closeable, IllegalArgumentException}; -use util\Date; +use util\{Date, Secret}; /** * Writes to a ZIP archive @@ -52,7 +52,7 @@ public function usingUnicodeNames($unicode= true) { /** * Set password to use when adding entries * - * @param string password + * @param string|util.Secret $password * @return io.archive.zip.ZipArchiveWriter this */ public function usingPassword($password) { @@ -60,7 +60,11 @@ public function usingPassword($password) { $this->password= null; } else { $this->password= new ZipCipher(); - $this->password->initialize(iconv(\xp::ENCODING, 'cp437', $password)); + $this->password->initialize(iconv( + \xp::ENCODING, + 'cp437', + $password instanceof Secret ? $password->reveal() : $password) + ); } return $this; } diff --git a/src/test/php/io/archive/zip/unittest/ZipArchiveWriterTest.class.php b/src/test/php/io/archive/zip/unittest/ZipArchiveWriterTest.class.php index a9425a0a..6f9b1bb6 100755 --- a/src/test/php/io/archive/zip/unittest/ZipArchiveWriterTest.class.php +++ b/src/test/php/io/archive/zip/unittest/ZipArchiveWriterTest.class.php @@ -3,7 +3,8 @@ use io\archive\zip\{ZipArchiveWriter, ZipDirEntry, ZipFile, ZipFileEntry}; use io\streams\{MemoryInputStream, MemoryOutputStream, StreamTransfer}; use lang\IllegalArgumentException; -use test\{Assert, Expect, Test}; +use test\{Assert, Expect, Test, Values}; +use util\Secret; class ZipArchiveWriterTest extends AbstractZipFileTest { @@ -83,11 +84,11 @@ public function adding_files_and_dir() { ); } - #[Test] - public function using_password_protection() { + #[Test, Values(['secret', new Secret('secret')])] + public function using_password_protection($password) { $out= new MemoryOutputStream(); - $fixture= ZipFile::create($out)->usingPassword('secret'); + $fixture= ZipFile::create($out)->usingPassword($password); $fixture->addFile(new ZipFileEntry('test.txt'))->out()->write('File contents'); $fixture->close(); From 47b863f39e9e26e6e9aef293427d0063d208691b Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 23 Nov 2025 10:48:47 +0200 Subject: [PATCH 05/10] Ensure we always decrypt complete blocks while streaming --- .../php/io/archive/zip/AESInputStream.class.php | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/main/php/io/archive/zip/AESInputStream.class.php b/src/main/php/io/archive/zip/AESInputStream.class.php index c71eb464..515e5574 100755 --- a/src/main/php/io/archive/zip/AESInputStream.class.php +++ b/src/main/php/io/archive/zip/AESInputStream.class.php @@ -13,6 +13,7 @@ class AESInputStream implements InputStream { private $in, $key; private $cipher, $counter, $hmac; + private $buffer= ''; /** * Constructor @@ -71,10 +72,22 @@ private function decrypt($input) { * @throws lang.IllegalStateException when HMAC verification fails */ public function read($limit= 8192) { - $chunk= $this->in->read($limit); - if ($this->in->available()) return $this->decrypt($chunk); + $chunk= $this->buffer.$this->in->read($limit); + + // Ensure we always decrypt complete blocks while streaming + if ($this->in->available()) { + $rest= -strlen($chunk) % self::BLOCK; + if ($rest) { + $this->buffer= substr($chunk, $rest); + return $this->decrypt(substr($chunk, 0, $rest)); + } else { + $this->buffer= ''; + return $this->decrypt($chunk); + } + } // Verify HMAC checksum for last block + $this->buffer= ''; $plain= $this->decrypt(substr($chunk, 0, -10)); $mac= hash_final($this->hmac, true); From 97ae20f7ae1576d65c1d92c7fa422b1c3583d074 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 23 Nov 2025 10:54:53 +0200 Subject: [PATCH 06/10] Fix "Constant expression contains invalid operations" (PHP 8.0) --- .../io/archive/zip/unittest/AbstractZipFileTest.class.php | 7 +++++++ .../io/archive/zip/unittest/ZipArchiveWriterTest.class.php | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/test/php/io/archive/zip/unittest/AbstractZipFileTest.class.php b/src/test/php/io/archive/zip/unittest/AbstractZipFileTest.class.php index 7312b89f..6320fc15 100755 --- a/src/test/php/io/archive/zip/unittest/AbstractZipFileTest.class.php +++ b/src/test/php/io/archive/zip/unittest/AbstractZipFileTest.class.php @@ -3,6 +3,7 @@ use io\archive\zip\{ZipArchiveReader, ZipEntry, ZipFile}; use io\streams\Streams; use test\verify\Runtime; +use util\Secret; /** * Base class for testing zip files @@ -13,6 +14,12 @@ #[Runtime(extensions: ['zlib'])] abstract class AbstractZipFileTest { + /** @return iterable */ + private function passwords() { + yield ['secret']; + yield [new Secret('secret')]; + } + /** * Returns entry content; or NULL for directories * diff --git a/src/test/php/io/archive/zip/unittest/ZipArchiveWriterTest.class.php b/src/test/php/io/archive/zip/unittest/ZipArchiveWriterTest.class.php index 6f9b1bb6..1c2aeb70 100755 --- a/src/test/php/io/archive/zip/unittest/ZipArchiveWriterTest.class.php +++ b/src/test/php/io/archive/zip/unittest/ZipArchiveWriterTest.class.php @@ -84,7 +84,7 @@ public function adding_files_and_dir() { ); } - #[Test, Values(['secret', new Secret('secret')])] + #[Test, Values(from: 'passwords')] public function using_password_protection($password) { $out= new MemoryOutputStream(); From f345060840595e0736000ddfb7b5e1afeaf31489 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 23 Nov 2025 10:55:26 +0200 Subject: [PATCH 07/10] QA: Remove unused import --- .../php/io/archive/zip/unittest/ZipArchiveWriterTest.class.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/php/io/archive/zip/unittest/ZipArchiveWriterTest.class.php b/src/test/php/io/archive/zip/unittest/ZipArchiveWriterTest.class.php index 1c2aeb70..8f9c24bc 100755 --- a/src/test/php/io/archive/zip/unittest/ZipArchiveWriterTest.class.php +++ b/src/test/php/io/archive/zip/unittest/ZipArchiveWriterTest.class.php @@ -4,7 +4,6 @@ use io\streams\{MemoryInputStream, MemoryOutputStream, StreamTransfer}; use lang\IllegalArgumentException; use test\{Assert, Expect, Test, Values}; -use util\Secret; class ZipArchiveWriterTest extends AbstractZipFileTest { From 446d2ad89f5109978fe727987326840f076ec129 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 23 Nov 2025 11:08:15 +0200 Subject: [PATCH 08/10] Add support for AES-128 and AES-192 --- .../archive/zip/AbstractZipReaderImpl.class.php | 6 +++--- .../unittest/vendors/SevenZipFileTest.class.php | 6 +++--- .../archive/zip/unittest/vendors/7zip/aes-128.zip | Bin 0 -> 405 bytes .../archive/zip/unittest/vendors/7zip/aes-192.zip | Bin 0 -> 413 bytes 4 files changed, 6 insertions(+), 6 deletions(-) create mode 100755 src/test/resources/io/archive/zip/unittest/vendors/7zip/aes-128.zip create mode 100755 src/test/resources/io/archive/zip/unittest/vendors/7zip/aes-192.zip diff --git a/src/main/php/io/archive/zip/AbstractZipReaderImpl.class.php b/src/main/php/io/archive/zip/AbstractZipReaderImpl.class.php index cb5a6838..642a3519 100755 --- a/src/main/php/io/archive/zip/AbstractZipReaderImpl.class.php +++ b/src/main/php/io/archive/zip/AbstractZipReaderImpl.class.php @@ -243,7 +243,7 @@ public function currentEntry() { $salt= $this->streamRead($sl); $pvv= $this->streamRead(2); $dk= hash_pbkdf2('sha1', $this->password->reveal(), $salt, 1000, 2 * $dl + 2, true); - if (0 !== substr_compare($dk, $pvv, 64, 2)) { + if (0 !== substr_compare($dk, $pvv, 2 * $dl, 2)) { throw new IllegalAccessException('The password did not match'); } @@ -251,8 +251,8 @@ public function currentEntry() { $header['compression']= $aes['compression']; $is= new AESInputStream( new ZipFileInputStream($this, $this->position, $header['compressed'] - $sl - 2), - substr($dk, 0, 32), - substr($dk, 32, 32) + substr($dk, 0, $dl), + substr($dk, $dl, $dl) ); } else if ($header['flags'] & 1) { if (null === $this->password) { diff --git a/src/test/php/io/archive/zip/unittest/vendors/SevenZipFileTest.class.php b/src/test/php/io/archive/zip/unittest/vendors/SevenZipFileTest.class.php index 7f70c982..e7b3b461 100755 --- a/src/test/php/io/archive/zip/unittest/vendors/SevenZipFileTest.class.php +++ b/src/test/php/io/archive/zip/unittest/vendors/SevenZipFileTest.class.php @@ -58,17 +58,17 @@ public function ppmd() { $this->assertCompressedEntryIn($this->archiveReaderFor($this->vendor(), 'ppmd')); } - #[Test, Expect(IllegalAccessException::class), Values(['zip-crypto', 'aes-256'])] + #[Test, Expect(IllegalAccessException::class), Values(['zip-crypto', 'aes-128', 'aes-192', 'aes-256'])] public function missing_password($fixture) { $this->archiveReaderFor($this->vendor(), $fixture)->iterator()->next(); } - #[Test, Expect(IllegalAccessException::class), Values(['zip-crypto', 'aes-256'])] + #[Test, Expect(IllegalAccessException::class), Values(['zip-crypto', 'aes-128', 'aes-192', 'aes-256'])] public function incorrect_password($fixture) { $this->archiveReaderFor($this->vendor(), $fixture)->usingPassword('wrong')->iterator()->next(); } - #[Test, Values(['zip-crypto', 'aes-256'])] + #[Test, Values(['zip-crypto', 'aes-128', 'aes-192', 'aes-256'])] public function password_protected($fixture) { $reader= $this->archiveReaderFor($this->vendor(), $fixture); with ($it= $reader->usingPassword('secret')->iterator()); { diff --git a/src/test/resources/io/archive/zip/unittest/vendors/7zip/aes-128.zip b/src/test/resources/io/archive/zip/unittest/vendors/7zip/aes-128.zip new file mode 100755 index 0000000000000000000000000000000000000000..0446243e2ccdd507f680de2379cf8944bae9c0a7 GIT binary patch literal 405 zcmWIWW@a&FU}Q*Um^0D9jsXaiftVkNc^J4E3KENp%kzs;^hzp97-zCGFfll~GBPl5 zsm(sgmQ@$-z_{hY=i2H2R%mHOwm#nUe#ye?MRF{+7pgE%3xJ!OzZzn$2GC3qAm#v? zTb5c>iDu@)-xhmQ_2t*S-K4(a#YgE+v9Gr%P5E(>$Mth`p^E0sWpxIYCZek|iUYhE zne3TyIawcQ0SG8G{BH!&$R6NgPyh)5JuAV$AoFYPf~}{4enACDo%3;@=7S9SmZ literal 0 HcmV?d00001 diff --git a/src/test/resources/io/archive/zip/unittest/vendors/7zip/aes-192.zip b/src/test/resources/io/archive/zip/unittest/vendors/7zip/aes-192.zip new file mode 100755 index 0000000000000000000000000000000000000000..95a75f810766c5dbdf501355ebc12941babea923 GIT binary patch literal 413 zcmWIWW@a&FU}Q*Um^0D9jsXbNftVkNc^J4E3KENp%kzs;^hzp97-zCGFfll~GBGd+ zhAMtrz3{)klj=Iv6?3bK{W@PZPl(zW{NbXEmA&E`KjBFiYF4qv2f)qFUkx!^2WYMc z5OV;{E=w({L^JoM$}o9~xn%in~P$TcRHRyQ#I8h2fSu zSDm=ki2!d#CVOUFPS*!o0s;yR{~JLxvPZZW6hJ~iPfIW`$o!hSVC!i{umBQ) literal 0 HcmV?d00001 From 437af4d27ffe251f00cac47ae6ca9d283a315f42 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 23 Nov 2025 11:11:44 +0200 Subject: [PATCH 09/10] QA: Implementation consistency --- src/main/php/io/archive/zip/AbstractZipReaderImpl.class.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/php/io/archive/zip/AbstractZipReaderImpl.class.php b/src/main/php/io/archive/zip/AbstractZipReaderImpl.class.php index 642a3519..c03872cb 100755 --- a/src/main/php/io/archive/zip/AbstractZipReaderImpl.class.php +++ b/src/main/php/io/archive/zip/AbstractZipReaderImpl.class.php @@ -267,11 +267,9 @@ public function currentEntry() { throw new IllegalAccessException('The password did not match'); } - // Password matches. $this->skip-= 12; - $header['compressed']-= 12; $is= new DecipheringInputStream( - new ZipFileInputStream($this, $this->position, $header['compressed']), + new ZipFileInputStream($this, $this->position, $header['compressed'] - 12), $cipher ); } else { From 6da96d358d87decea8c21979e59742213e225f1a Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 23 Nov 2025 11:45:47 +0200 Subject: [PATCH 10/10] QA: WS --- .../io/archive/zip/AbstractZipReaderImpl.class.php | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/main/php/io/archive/zip/AbstractZipReaderImpl.class.php b/src/main/php/io/archive/zip/AbstractZipReaderImpl.class.php index c03872cb..3b9ee944 100755 --- a/src/main/php/io/archive/zip/AbstractZipReaderImpl.class.php +++ b/src/main/php/io/archive/zip/AbstractZipReaderImpl.class.php @@ -209,20 +209,16 @@ public function currentEntry() { if (!isset($this->index[$name])) throw new FormatException('.zip archive broken: cannot find "'.$name.'" in central directory.'); $header= $this->index[$name]; - // In case we're here, we can be sure to have a - // RandomAccessStream - otherwise the central directory - // could not have been read in the first place. So, - // we may seek. - // If we had strict type checking this would not be - // possible, though. + // In case we're here, we can be sure to have a RandomAccessStream - otherwise the + // central directory could not have been read in the first place. So, we may seek. // The offset is relative to the file begin - but also skip over the usual parts: // * file header signature (4 bytes) // * file header (26 bytes) // * file extra + file name (variable size) - $this->streamPosition($header['offset']+ 30 + $header['extralen'] + $header['namelen']); + $this->streamPosition($header['offset'] + 30 + $header['extralen'] + $header['namelen']); // Set skip accordingly: 4 bytes data descriptor signature + 12 bytes data descriptor - $this->skip= $header['compressed']+ 16; + $this->skip= $header['compressed'] + 16; } // AES vs. traditional PKZIP cipher