Skip to content

Commit 9e39aa6

Browse files
authored
Merge pull request #7 from xp-framework/feature/encrypt
Refactor encryption, adding AES-128, -192 and -256 write support
2 parents 2bfd36e + 8b095ad commit 9e39aa6

21 files changed

+686
-374
lines changed

src/main/php/io/archive/zip/AbstractZipReaderImpl.class.php

Lines changed: 7 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -221,69 +221,25 @@ public function currentEntry() {
221221
$this->skip= $header['compressed'] + 16;
222222
}
223223

224-
$stream= new ZipFileInputStream($this, $this->position, $header['compressed']);
225-
226224
// AES vs. traditional PKZIP cipher
227225
if (99 === $header['compression']) {
228226
$aes= unpack('vheader/vsize/vversion/a2vendor/cstrength/vcompression', $extra);
229227
$header['compression']= $aes['compression'];
230-
$is= function() use($header, $stream, $aes) {
231-
if (null === $this->password) {
232-
throw new IllegalAccessException('No password set');
233-
}
234-
235-
switch ($aes['strength']) {
236-
case 1: $sl= 8; $dl= 16; break;
237-
case 2: $sl= 12; $dl= 24; break;
238-
case 3: $sl= 16; $dl= 32; break;
239-
default: throw new IllegalArgumentException('Invalid AES strength '.$aes['strength']);
240-
}
241-
242-
// Verify password
243-
$stream->seek(0);
244-
$salt= $stream->read($sl);
245-
$pvv= $stream->read(2);
246-
$dk= hash_pbkdf2('sha1', $this->password->reveal(), $salt, 1000, 2 * $dl + 2, true);
247-
if (0 !== substr_compare($dk, $pvv, 2 * $dl, 2)) {
248-
throw new IllegalAccessException('The password did not match');
249-
}
250-
251-
return new AESInputStream(
252-
$stream,
253-
substr($dk, 0, $dl),
254-
substr($dk, $dl, $dl)
255-
);
256-
};
228+
$encryption= Encryption::aes($this->password, $aes['strength']);
257229
} else if ($header['flags'] & 1) {
258-
$is= function() use($header, $stream) {
259-
if (null === $this->password) {
260-
throw new IllegalAccessException('No password set');
261-
}
262-
263-
// Verify password
264-
$stream->seek(0);
265-
$cipher= new ZipCipher();
266-
$cipher->initialize(iconv(\xp::ENCODING, 'cp437', $this->password->reveal()));
267-
$preamble= $cipher->decipher($stream->read(12));
268-
if (ord($preamble[11]) !== (($header['crc'] >> 24) & 0xff)) {
269-
throw new IllegalAccessException('The password did not match');
270-
}
271-
272-
return new DecipheringInputStream($stream, $cipher);
273-
};
230+
$encryption= Encryption::cipher($this->password);
274231
} else {
275-
$is= function() use($stream) {
276-
$stream->seek(0);
277-
return $stream;
278-
};
232+
$encryption= null;
279233
}
280234

281235
// Create ZipEntry object and return it
282236
$e= new ZipFileEntry($decoded);
283237
$e->setLastModified($date);
284238
$e->setSize($header['uncompressed']);
285-
$e->setCompression(Compression::getInstance($header['compression']));
286-
$e->is= $is;
239+
$e->withCrc32($header['crc']);
240+
$e->useCompression(Compression::getInstance($header['compression']));
241+
$e->useEncryption($encryption);
242+
$e->is= new ZipFileInputStream($this, $this->position, $header['compressed']);
287243
return $e;
288244
}
289245
case self::DHDR: { // Zip directory
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php namespace io\archive\zip;
2+
3+
use io\streams\{InputStream, OutputStream};
4+
5+
/** @test io.archive.zip.unittest.BufferTest */
6+
class Buffer implements InputStream, OutputStream {
7+
public $length= 0;
8+
private $position= 0;
9+
private $bytes= '';
10+
11+
/** @return int */
12+
public function available() {
13+
return $this->length - $this->position;
14+
}
15+
16+
/**
17+
* Read a number of given bytes
18+
*
19+
* @param int $size
20+
* @return string
21+
*/
22+
public function read($size= 8192) {
23+
$chunk= substr($this->bytes, $this->position, $size);
24+
$this->position+= strlen($chunk);
25+
return $chunk;
26+
}
27+
28+
/**
29+
* Write given bytes
30+
*
31+
* @param string $bytes
32+
* @return void
33+
*/
34+
public function write($bytes) {
35+
$this->length+= strlen($bytes);
36+
37+
// TODO: Buffer to disk!
38+
$this->bytes.= $bytes;
39+
}
40+
41+
/** @return void */
42+
public function flush() {
43+
// NOOP
44+
}
45+
46+
/** @return void */
47+
public function close() {
48+
// NOOP
49+
}
50+
}

src/main/php/io/archive/zip/CipheringZipFileOutputStream.class.php

Lines changed: 0 additions & 103 deletions
This file was deleted.

src/main/php/io/archive/zip/DecipheringInputStream.class.php

Lines changed: 0 additions & 51 deletions
This file was deleted.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php namespace io\archive\zip;
2+
3+
use io\archive\zip\encrypt\{AESEncryption, Cipher};
4+
5+
/** Encryption methods */
6+
abstract class Encryption {
7+
8+
/**
9+
* Returns AES encryption with either 128, 192 or 256 bits
10+
*
11+
* @ext openssl
12+
* @param string|util.Secret $password
13+
* @param int $bits
14+
* @return self
15+
*/
16+
public static function aes($password, $bits): self {
17+
return new AESEncryption($password, $bits);
18+
}
19+
20+
/**
21+
* Returns traditional PKZIP cipher
22+
*
23+
* @param string|util.Secret $password
24+
* @return self
25+
*/
26+
public static function cipher($password): self {
27+
return new Cipher($password);
28+
}
29+
}

src/main/php/io/archive/zip/ZipArchiveReader.class.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,21 @@ public function __construct(InputStream $stream) {
4747
}
4848
}
4949

50+
/**
51+
* Sets password for decryption.
52+
*
53+
* @param string|util.Secret $password
54+
* @return self
55+
*/
56+
public function decryptWith($password) {
57+
$this->impl->setPassword($password);
58+
return $this;
59+
}
60+
5061
/**
5162
* Set password to use when extracting
5263
*
64+
* @deprecated Use decryptWith() instead!
5365
* @param string $password
5466
* @return self
5567
*/

0 commit comments

Comments
 (0)