From 11a7c16f1bf7ba20b05f71d7e41d6f70e972f9ed Mon Sep 17 00:00:00 2001 From: wapplay-home-linux <alexey@nelexa.ru> Date: Wed, 14 Dec 2016 12:34:59 +0300 Subject: [PATCH] Fixed bug from issues 1 - Fixed encryption and decryption Traditional PKWARE Encryption - add casting to 32-bit integer. - The restriction on the length of the password to 99 characters for the WinZIP AES Encryption method. --- .../TraditionalPkwareEncryptionEngine.php | 23 ++++++++-- src/PhpZip/Crypto/WinZipAesEngine.php | 16 +++++-- tests/PhpZip/ZipTest.php | 6 ++- tests/PhpZip/ZipTestCase.php | 44 +++++++++++++++---- 4 files changed, 73 insertions(+), 16 deletions(-) diff --git a/src/PhpZip/Crypto/TraditionalPkwareEncryptionEngine.php b/src/PhpZip/Crypto/TraditionalPkwareEncryptionEngine.php index bd887fa..31336c1 100644 --- a/src/PhpZip/Crypto/TraditionalPkwareEncryptionEngine.php +++ b/src/PhpZip/Crypto/TraditionalPkwareEncryptionEngine.php @@ -105,9 +105,26 @@ class TraditionalPkwareEncryptionEngine private function updateKeys($charAt) { $this->keys[0] = self::crc32($this->keys[0], $charAt); - $this->keys[1] = ($this->keys[1] + ($this->keys[0] & 0xff)) & 4294967295; - $this->keys[1] = ($this->keys[1] * 134775813 + 1) & 4294967295; - $this->keys[2] = self::crc32($this->keys[2], ($this->keys[1] >> 24) & 0xff); + $this->keys[1] = $this->keys[1] + ($this->keys[0] & 0xff); + $this->keys[1] = self::toInt($this->keys[1] * 134775813 + 1); + $this->keys[2] = self::toInt(self::crc32($this->keys[2], ($this->keys[1] >> 24) & 0xff)); + } + + /** + * Cast to int + * + * @param $i + * @return int + */ + private static function toInt($i) + { + $i = (int)($i & 0xffffffff); + if ($i > 2147483647) { + return -(-$i & 0xffffffff); + } elseif ($i < -2147483648) { + return $i & -2147483648; + } + return $i; } /** diff --git a/src/PhpZip/Crypto/WinZipAesEngine.php b/src/PhpZip/Crypto/WinZipAesEngine.php index 0283dfb..f88c312 100644 --- a/src/PhpZip/Crypto/WinZipAesEngine.php +++ b/src/PhpZip/Crypto/WinZipAesEngine.php @@ -88,16 +88,20 @@ class WinZipAesEngine throw new ZipCryptoException("Expected end of file after WinZip AES authentication code!"); } - do { - assert($this->entry->getPassword() !== null); - assert(self::AES_BLOCK_SIZE_BITS <= $keyStrengthBits); + $password = $this->entry->getPassword(); + assert($password !== null); + assert(self::AES_BLOCK_SIZE_BITS <= $keyStrengthBits); + // WinZip 99-character limit + // @see https://sourceforge.net/p/p7zip/discussion/383044/thread/c859a2f0/ + $password = substr($password, 0, 99); + do { // Here comes the strange part about WinZip AES encryption: // Its unorthodox use of the Password-Based Key Derivation // Function 2 (PBKDF2) of PKCS #5 V2.0 alias RFC 2898. // Yes, the password verifier is only a 16 bit value. // So we must use the MAC for password verification, too. - $keyParam = hash_pbkdf2("sha1", $this->entry->getPassword(), $salt, self::ITERATION_COUNT, (2 * $keyStrengthBits + self::PWD_VERIFIER_BITS) / 8, true); + $keyParam = hash_pbkdf2("sha1", $password, $salt, self::ITERATION_COUNT, (2 * $keyStrengthBits + self::PWD_VERIFIER_BITS) / 8, true); $ctrIvSize = self::AES_BLOCK_SIZE_BITS / 8; $iv = str_repeat(chr(0), $ctrIvSize); @@ -202,6 +206,10 @@ class WinZipAesEngine $password = $this->entry->getPassword(); assert($password !== null); + // WinZip 99-character limit + // @see https://sourceforge.net/p/p7zip/discussion/383044/thread/c859a2f0/ + $password = substr($password, 0, 99); + $keyStrengthBytes = 32; $keyStrengthBits = $keyStrengthBytes * 8; diff --git a/tests/PhpZip/ZipTest.php b/tests/PhpZip/ZipTest.php index 6f5327b..eb2603b 100644 --- a/tests/PhpZip/ZipTest.php +++ b/tests/PhpZip/ZipTest.php @@ -752,7 +752,7 @@ class ZipTest extends ZipTestCase */ public function testSetPassword() { - $password = CryptoUtil::randomBytes(100); + $password = base64_encode(CryptoUtil::randomBytes(100)); $badPassword = "sdgt43r23wefe"; $outputZip = ZipOutputFile::create(); @@ -761,6 +761,8 @@ class ZipTest extends ZipTestCase $outputZip->saveAsFile($this->outputFilename); $outputZip->close(); + self::assertCorrectZipArchive($this->outputFilename, $password); + $zipFile = ZipFile::openFromFile($this->outputFilename); // set bad password Traditional Encryption @@ -791,6 +793,8 @@ class ZipTest extends ZipTestCase $outputZip->close(); $zipFile->close(); + self::assertCorrectZipArchive($this->outputFilename, $password); + // check from WinZip AES encryption $zipFile = ZipFile::openFromFile($this->outputFilename); diff --git a/tests/PhpZip/ZipTestCase.php b/tests/PhpZip/ZipTestCase.php index 2497db2..95a6474 100644 --- a/tests/PhpZip/ZipTestCase.php +++ b/tests/PhpZip/ZipTestCase.php @@ -9,18 +9,47 @@ class ZipTestCase extends \PHPUnit_Framework_TestCase /** * Assert correct zip archive. * - * @param $filename + * @param string $filename + * @param string|null $password */ - public static function assertCorrectZipArchive($filename) + public static function assertCorrectZipArchive($filename, $password = null) { - if (DIRECTORY_SEPARATOR !== '\\' && `which zip`) { - exec("zip -T " . escapeshellarg($filename), $output, $returnCode); + if (DIRECTORY_SEPARATOR !== '\\' && `which unzip`) { + $command = "unzip"; + if ($password !== null) { + $command .= " -P " . escapeshellarg($password); + } + $command .= " -t " . escapeshellarg($filename); + exec($command, $output, $returnCode); $output = implode(PHP_EOL, $output); - self::assertEquals($returnCode, 0); - self::assertNotContains('zip error', $output); - self::assertContains(' OK', $output); + if ($password !== null && $returnCode === 81) { + if(`which 7z`){ + // WinZip 99-character limit + // @see https://sourceforge.net/p/p7zip/discussion/383044/thread/c859a2f0/ + $password = substr($password, 0, 99); + + $command = "7z t -p" . escapeshellarg($password). " " . escapeshellarg($filename); + exec($command, $output, $returnCode); + + $output = implode(PHP_EOL, $output); + + self::assertEquals($returnCode, 0); + self::assertNotContains(' Errors', $output); + self::assertContains(' Ok', $output); + } + else{ + fwrite(STDERR, 'Program unzip cannot support this function.'.PHP_EOL); + fwrite(STDERR, 'Please install 7z. For Ubuntu-like: sudo apt-get install p7zip-full'.PHP_EOL); + } + } + else { + self::assertEquals($returnCode, 0); + self::assertNotContains('incorrect password', $output); + self::assertContains(' OK', $output); + self::assertContains('No errors', $output); + } } } @@ -52,7 +81,6 @@ class ZipTestCase extends \PHPUnit_Framework_TestCase exec("zipalign -c -v 4 " . escapeshellarg($filename), $output, $returnCode); return $returnCode === 0; } else { - echo 'Can not find program "zipalign" for test' . PHP_EOL; fwrite(STDERR, 'Can not find program "zipalign" for test'); return null; }