ZipPasswordTest.php 17 KB

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