ZipPasswordTest.php 16 KB

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