ZipPasswordTest.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. <?php
  2. declare(strict_types=1);
  3. /*
  4. * This file is part of the nelexa/zip package.
  5. * (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
  6. * For the full copyright and license information, please view the LICENSE
  7. * file that was distributed with this source code.
  8. */
  9. namespace PhpZip\Tests;
  10. use PhpZip\Constants\ZipCompressionMethod;
  11. use PhpZip\Constants\ZipEncryptionMethod;
  12. use PhpZip\Exception\InvalidArgumentException;
  13. use PhpZip\Exception\RuntimeException;
  14. use PhpZip\Exception\ZipAuthenticationException;
  15. use PhpZip\Exception\ZipEntryNotFoundException;
  16. use PhpZip\Exception\ZipException;
  17. use PhpZip\Model\ZipEntry;
  18. use PhpZip\ZipFile;
  19. /**
  20. * Tests with zip password.
  21. *
  22. * @internal
  23. *
  24. * @small
  25. */
  26. class ZipPasswordTest extends ZipFileSetTestCase
  27. {
  28. /**
  29. * Test archive password.
  30. *
  31. * @throws ZipException
  32. * @throws \Exception
  33. * @noinspection PhpRedundantCatchClauseInspection
  34. */
  35. public function testSetPassword(): void
  36. {
  37. if (\PHP_INT_SIZE === 4) { // php 32 bit
  38. $this->expectException(RuntimeException::class);
  39. $this->expectExceptionMessage('Traditional PKWARE Encryption is not supported in 32-bit PHP.');
  40. }
  41. $password = base64_encode(random_bytes(100));
  42. $badPassword = 'bad password';
  43. // create encryption password with Traditional PKWARE encryption
  44. $zipFile = new ZipFile();
  45. $zipFile->addDir(__DIR__);
  46. $zipFile->setPassword($password, ZipEncryptionMethod::PKWARE);
  47. $zipFile->saveAsFile($this->outputFilename);
  48. $zipFile->close();
  49. static::assertCorrectZipArchive($this->outputFilename, $password);
  50. $zipFile->openFile($this->outputFilename);
  51. // check bad password for Traditional PKWARE encryption
  52. $zipFile->setReadPassword($badPassword);
  53. foreach ($zipFile->getListFiles() as $entryName) {
  54. try {
  55. $zipFile[$entryName];
  56. static::fail('Expected Exception has not been raised.');
  57. } catch (ZipException $e) {
  58. }
  59. }
  60. // check correct password for Traditional PKWARE encryption
  61. $zipFile->setReadPassword($password);
  62. foreach ($zipFile->getEntries() as $zipEntry) {
  63. static::assertTrue($zipEntry->isEncrypted());
  64. static::assertSame(ZipEncryptionMethod::getEncryptionMethodName($zipEntry->getEncryptionMethod()), 'Traditional PKWARE encryption');
  65. $decryptContent = $zipFile[$zipEntry->getName()];
  66. static::assertNotEmpty($decryptContent);
  67. static::assertStringContainsString('<?php', $decryptContent);
  68. }
  69. // change encryption method to WinZip Aes and update file
  70. $zipFile->setPassword($password/*, ZipEncryptionMethod::WINZIP_AES_256*/);
  71. $zipFile->saveAsFile($this->outputFilename);
  72. $zipFile->close();
  73. /** @see https://sourceforge.net/p/p7zip/discussion/383044/thread/c859a2f0/ WinZip 99-character limit */
  74. static::assertCorrectZipArchive($this->outputFilename, substr($password, 0, 99));
  75. // check from WinZip AES encryption
  76. $zipFile->openFile($this->outputFilename);
  77. // set bad password WinZip AES
  78. $zipFile->setReadPassword($badPassword);
  79. foreach ($zipFile->getListFiles() as $entryName) {
  80. try {
  81. $zipFile[$entryName];
  82. static::fail('Expected Exception has not been raised.');
  83. } catch (ZipAuthenticationException $ae) {
  84. static::assertNotNull($ae);
  85. }
  86. }
  87. // set correct password WinZip AES
  88. $zipFile->setReadPassword($password);
  89. foreach ($zipFile->getEntries() as $zipEntry) {
  90. static::assertTrue($zipEntry->isEncrypted());
  91. static::assertSame($zipEntry->getCompressionMethod(), ZipCompressionMethod::DEFLATED);
  92. static::assertSame(ZipEncryptionMethod::getEncryptionMethodName($zipEntry->getEncryptionMethod()), 'WinZip AES-256');
  93. $decryptContent = $zipFile[$zipEntry->getName()];
  94. static::assertNotEmpty($decryptContent);
  95. static::assertStringContainsString('<?php', $decryptContent);
  96. }
  97. // clear password
  98. $zipFile->addFromString('file1', '');
  99. $zipFile->disableEncryption();
  100. $zipFile->addFromString('file2', '');
  101. $zipFile->saveAsFile($this->outputFilename);
  102. $zipFile->close();
  103. static::assertCorrectZipArchive($this->outputFilename);
  104. // check remove password
  105. $zipFile->openFile($this->outputFilename);
  106. foreach ($zipFile->getEntries() as $zipEntry) {
  107. static::assertFalse($zipEntry->isEncrypted());
  108. }
  109. $zipFile->close();
  110. }
  111. /**
  112. * @throws ZipException
  113. * @throws \Exception
  114. */
  115. public function testTraditionalEncryption(): void
  116. {
  117. if (\PHP_INT_SIZE === 4) { // php 32 bit
  118. $this->expectException(RuntimeException::class);
  119. $this->expectExceptionMessage('Traditional PKWARE Encryption is not supported in 32-bit PHP.');
  120. }
  121. $password = md5(random_bytes(50));
  122. $zip = new ZipFile();
  123. $zip->addDirRecursive($this->outputDirname);
  124. $zip->setPassword($password, ZipEncryptionMethod::PKWARE);
  125. $zip->saveAsFile($this->outputFilename);
  126. $zip->close();
  127. static::assertCorrectZipArchive($this->outputFilename, $password);
  128. $zip->openFile($this->outputFilename);
  129. $zip->setReadPassword($password);
  130. static::assertFilesResult($zip, array_keys(self::$files));
  131. foreach ($zip->getEntries() as $zipEntry) {
  132. if (!$zipEntry->isDirectory()) {
  133. static::assertTrue($zipEntry->isEncrypted());
  134. static::assertSame(ZipEncryptionMethod::getEncryptionMethodName($zipEntry->getEncryptionMethod()), 'Traditional PKWARE encryption');
  135. }
  136. }
  137. $zip->close();
  138. }
  139. /**
  140. * @dataProvider winZipKeyStrengthProvider
  141. *
  142. * @throws ZipException
  143. * @throws \Exception
  144. */
  145. public function testWinZipAesEncryption(int $encryptionMethod, int $bitSize): void
  146. {
  147. $password = base64_encode(random_bytes(50));
  148. $zip = new ZipFile();
  149. $zip->addDirRecursive($this->outputDirname);
  150. $zip->setPassword($password, $encryptionMethod);
  151. $zip->saveAsFile($this->outputFilename);
  152. $zip->close();
  153. static::assertCorrectZipArchive($this->outputFilename, $password);
  154. $zip->openFile($this->outputFilename);
  155. $zip->setReadPassword($password);
  156. static::assertFilesResult($zip, array_keys(self::$files));
  157. foreach ($zip->getEntries() as $info) {
  158. if (!$info->isDirectory()) {
  159. static::assertTrue($info->isEncrypted());
  160. static::assertSame($info->getEncryptionMethod(), $encryptionMethod);
  161. static::assertSame(ZipEncryptionMethod::getEncryptionMethodName($info->getEncryptionMethod()), 'WinZip AES-' . $bitSize);
  162. }
  163. }
  164. $zip->close();
  165. }
  166. public function winZipKeyStrengthProvider(): array
  167. {
  168. return [
  169. [ZipEncryptionMethod::WINZIP_AES_128, 128],
  170. [ZipEncryptionMethod::WINZIP_AES_192, 192],
  171. [ZipEncryptionMethod::WINZIP_AES_256, 256],
  172. ];
  173. }
  174. /**
  175. * @throws ZipException
  176. */
  177. public function testEncryptionEntries(): void
  178. {
  179. if (\PHP_INT_SIZE === 4) { // php 32 bit
  180. $this->expectException(RuntimeException::class);
  181. $this->expectExceptionMessage('Traditional PKWARE Encryption is not supported in 32-bit PHP.');
  182. }
  183. $password1 = '353442434235424234';
  184. $password2 = 'adgerhvrwjhqqehtqhkbqrgewg';
  185. $zip = new ZipFile();
  186. $zip->addDir($this->outputDirname);
  187. $zip->setPasswordEntry('.hidden', $password1, ZipEncryptionMethod::PKWARE);
  188. $zip->setPasswordEntry('text file.txt', $password2, ZipEncryptionMethod::WINZIP_AES_256);
  189. $zip->saveAsFile($this->outputFilename);
  190. $zip->close();
  191. $zip->openFile($this->outputFilename);
  192. $zip->setReadPasswordEntry('.hidden', $password1);
  193. $zip->setReadPasswordEntry('text file.txt', $password2);
  194. static::assertFilesResult(
  195. $zip,
  196. [
  197. '.hidden',
  198. 'text file.txt',
  199. 'Текстовый документ.txt',
  200. 'empty dir/',
  201. 'LoremIpsum.txt',
  202. ]
  203. );
  204. $info = $zip->getEntry('.hidden');
  205. static::assertTrue($info->isEncrypted());
  206. static::assertSame(ZipEncryptionMethod::getEncryptionMethodName($info->getEncryptionMethod()), 'Traditional PKWARE encryption');
  207. $info = $zip->getEntry('text file.txt');
  208. static::assertTrue($info->isEncrypted());
  209. static::assertStringContainsString(
  210. 'WinZip AES',
  211. ZipEncryptionMethod::getEncryptionMethodName($info->getEncryptionMethod())
  212. );
  213. static::assertFalse($zip->getEntry('Текстовый документ.txt')->isEncrypted());
  214. static::assertFalse($zip->getEntry('empty dir/')->isEncrypted());
  215. $zip->close();
  216. }
  217. /**
  218. * @throws ZipException
  219. */
  220. public function testEncryptionEntriesWithDefaultPassword(): void
  221. {
  222. if (\PHP_INT_SIZE === 4) { // php 32 bit
  223. $this->expectException(RuntimeException::class);
  224. $this->expectExceptionMessage('Traditional PKWARE Encryption is not supported in 32-bit PHP.');
  225. }
  226. $password1 = '353442434235424234';
  227. $password2 = 'adgerhvrwjhqqehtqhkbqrgewg';
  228. $defaultPassword = ' f f f f f ffff f5 ';
  229. $zip = new ZipFile();
  230. $zip->addDir($this->outputDirname);
  231. $zip->setPassword($defaultPassword);
  232. $zip->setPasswordEntry('.hidden', $password1, ZipEncryptionMethod::PKWARE);
  233. $zip->setPasswordEntry('text file.txt', $password2, ZipEncryptionMethod::WINZIP_AES_256);
  234. $zip->saveAsFile($this->outputFilename);
  235. $zip->close();
  236. $zip->openFile($this->outputFilename);
  237. $zip->setReadPassword($defaultPassword);
  238. $zip->setReadPasswordEntry('.hidden', $password1);
  239. $zip->setReadPasswordEntry('text file.txt', $password2);
  240. static::assertFilesResult(
  241. $zip,
  242. [
  243. '.hidden',
  244. 'text file.txt',
  245. 'Текстовый документ.txt',
  246. 'empty dir/',
  247. 'LoremIpsum.txt',
  248. ]
  249. );
  250. $zipEntry = $zip->getEntry('.hidden');
  251. static::assertTrue($zipEntry->isEncrypted());
  252. static::assertSame(ZipEncryptionMethod::getEncryptionMethodName($zipEntry->getEncryptionMethod()), 'Traditional PKWARE encryption');
  253. $zipEntry = $zip->getEntry('text file.txt');
  254. static::assertTrue($zipEntry->isEncrypted());
  255. static::assertStringContainsString(
  256. 'WinZip AES',
  257. ZipEncryptionMethod::getEncryptionMethodName($zipEntry->getEncryptionMethod())
  258. );
  259. $zipEntry = $zip->getEntry('Текстовый документ.txt');
  260. static::assertTrue($zipEntry->isEncrypted());
  261. static::assertStringContainsString(
  262. 'WinZip AES',
  263. ZipEncryptionMethod::getEncryptionMethodName($zipEntry->getEncryptionMethod())
  264. );
  265. static::assertFalse($zip->getEntry('empty dir/')->isEncrypted());
  266. $zip->close();
  267. }
  268. /**
  269. * @throws ZipException
  270. */
  271. public function testSetEncryptionMethodInvalid(): void
  272. {
  273. $this->expectException(InvalidArgumentException::class);
  274. $this->expectExceptionMessage('Encryption method 9999 is not supported.');
  275. $zipFile = new ZipFile();
  276. $encryptionMethod = 9999;
  277. $zipFile['entry'] = 'content';
  278. $zipFile->setPassword('pass', $encryptionMethod);
  279. $zipFile->outputAsString();
  280. }
  281. /**
  282. * @throws ZipException
  283. */
  284. public function testEntryPassword(): void
  285. {
  286. $zipFile = new ZipFile();
  287. $zipFile->setPassword('pass');
  288. $zipFile['file'] = 'content';
  289. static::assertFalse($zipFile->getEntry('file')->isEncrypted());
  290. for ($i = 1; $i <= 10; $i++) {
  291. $entryName = 'file' . $i;
  292. $zipFile[$entryName] = 'content';
  293. if ($i < 6) {
  294. $zipFile->setPasswordEntry($entryName, 'pass');
  295. static::assertTrue($zipFile->getEntry($entryName)->isEncrypted());
  296. } else {
  297. static::assertFalse($zipFile->getEntry($entryName)->isEncrypted());
  298. }
  299. }
  300. $zipFile->disableEncryptionEntry('file3');
  301. static::assertFalse($zipFile->getEntry('file3')->isEncrypted());
  302. static::assertTrue($zipFile->getEntry('file2')->isEncrypted());
  303. $zipFile->disableEncryption();
  304. $zipEntries = $zipFile->getEntries();
  305. array_walk(
  306. $zipEntries,
  307. function (ZipEntry $zipEntry): void {
  308. $this->assertFalse($zipEntry->isEncrypted());
  309. }
  310. );
  311. $zipFile->close();
  312. }
  313. /**
  314. * @throws ZipException
  315. */
  316. public function testInvalidEncryptionMethodEntry(): void
  317. {
  318. $this->expectException(InvalidArgumentException::class);
  319. $this->expectExceptionMessage('Encryption method 99 is not supported.');
  320. $zipFile = new ZipFile();
  321. $zipFile->addFromString('file', 'content', ZipCompressionMethod::STORED);
  322. $zipFile->setPasswordEntry('file', 'pass', ZipCompressionMethod::WINZIP_AES);
  323. }
  324. /**
  325. * @throws ZipException
  326. */
  327. public function testArchivePasswordUpdateWithoutSetReadPassword(): void
  328. {
  329. $zipFile = new ZipFile();
  330. $zipFile['file1'] = 'content';
  331. $zipFile['file2'] = 'content';
  332. $zipFile['file3'] = 'content';
  333. $zipFile->setPassword('password');
  334. $zipFile->saveAsFile($this->outputFilename);
  335. $zipFile->close();
  336. static::assertCorrectZipArchive($this->outputFilename, 'password');
  337. $zipFile->openFile($this->outputFilename);
  338. static::assertCount(3, $zipFile);
  339. foreach ($zipFile->getEntries() as $zipEntry) {
  340. static::assertTrue($zipEntry->isEncrypted());
  341. }
  342. unset($zipFile['file3']);
  343. $zipFile['file4'] = 'content';
  344. $zipFile->rewrite();
  345. static::assertCorrectZipArchive($this->outputFilename, 'password');
  346. static::assertCount(3, $zipFile);
  347. static::assertFalse(isset($zipFile['file3']));
  348. static::assertTrue(isset($zipFile['file4']));
  349. static::assertTrue($zipFile->getEntry('file1')->isEncrypted());
  350. static::assertTrue($zipFile->getEntry('file2')->isEncrypted());
  351. static::assertFalse($zipFile->getEntry('file4')->isEncrypted());
  352. static::assertSame($zipFile['file4'], 'content');
  353. $zipFile->extractTo($this->outputDirname, ['file4']);
  354. static::assertFileExists($this->outputDirname . \DIRECTORY_SEPARATOR . 'file4');
  355. static::assertStringEqualsFile($this->outputDirname . \DIRECTORY_SEPARATOR . 'file4', $zipFile['file4']);
  356. $zipFile->close();
  357. }
  358. /**
  359. * @see https://github.com/Ne-Lexa/php-zip/issues/9
  360. *
  361. * @throws ZipException
  362. * @throws \Exception
  363. */
  364. public function testIssues9(): void
  365. {
  366. $contents = str_pad('', 1000, 'test;test2;test3' . \PHP_EOL);
  367. $password = base64_encode(random_bytes(20));
  368. $zipFile = new ZipFile();
  369. $zipFile
  370. ->addFromString('codes.csv', $contents, ZipCompressionMethod::DEFLATED)
  371. ->setPassword($password)
  372. ->saveAsFile($this->outputFilename)
  373. ->close()
  374. ;
  375. static::assertCorrectZipArchive($this->outputFilename, $password);
  376. $zipFile->openFile($this->outputFilename);
  377. $zipFile->setReadPassword($password);
  378. static::assertSame($zipFile['codes.csv'], $contents);
  379. $zipFile->close();
  380. }
  381. /**
  382. * @throws ZipEntryNotFoundException
  383. * @throws ZipException
  384. */
  385. public function testReadAesEncryptedAndRewriteArchive(): void
  386. {
  387. $file = __DIR__ . '/resources/aes_password_archive.zip';
  388. $password = '1234567890';
  389. $zipFile = new ZipFile();
  390. $zipFile->openFile($file);
  391. $zipFile->setReadPassword($password);
  392. $zipFile->setPassword($password);
  393. $zipFile->setEntryComment('contents.txt', 'comment'); // change entry, but not changed contents
  394. $zipFile->saveAsFile($this->outputFilename);
  395. $zipFile2 = new ZipFile();
  396. $zipFile2->openFile($this->outputFilename);
  397. $zipFile2->setReadPassword($password);
  398. static::assertSame($zipFile2->getListFiles(), $zipFile->getListFiles());
  399. foreach ($zipFile as $name => $contents) {
  400. static::assertNotEmpty($name);
  401. static::assertNotEmpty($contents);
  402. static::assertStringContainsString('test contents', $contents);
  403. static::assertSame($zipFile2[$name], $contents);
  404. }
  405. $zipFile2->close();
  406. $zipFile->close();
  407. }
  408. }