File size: 4.09Kb
<?php
namespace PhpZip\IO\Filter\Cipher\WinZipAes;
use PhpZip\Exception\RuntimeException;
use PhpZip\Exception\ZipAuthenticationException;
use PhpZip\Util\CryptoUtil;
/**
* WinZip Aes Encryption.
*
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT APPENDIX E
* @see https://www.winzip.com/win/en/aes_info.html
*
* @author Ne-Lexa [email protected]
* @license MIT
*
* @internal
*/
class WinZipAesContext
{
/** @var int AES Block size */
const BLOCK_SIZE = self::IV_SIZE;
/** @var int Footer size */
const FOOTER_SIZE = 10;
/** @var int The iteration count for the derived keys of the cipher, KLAC and MAC. */
const ITERATION_COUNT = 1000;
/** @var int Password verifier size */
const PASSWORD_VERIFIER_SIZE = 2;
/** @var int IV size */
const IV_SIZE = 16;
/** @var string */
private $iv;
/** @var string */
private $key;
/** @var \HashContext|resource */
private $hmacContext;
/** @var string */
private $passwordVerifier;
/**
* WinZipAesContext constructor.
*
* @param int $encryptionStrengthBits
* @param string $password
* @param string $salt
*/
public function __construct($encryptionStrengthBits, $password, $salt)
{
$encryptionStrengthBits = (int) $encryptionStrengthBits;
if ($password === '') {
throw new RuntimeException('$password is empty');
}
if (empty($salt)) {
throw new RuntimeException('$salt is empty');
}
// WinZip 99-character limit https://sourceforge.net/p/p7zip/discussion/383044/thread/c859a2f0/
$password = substr($password, 0, 99);
$this->iv = str_repeat("\0", self::IV_SIZE);
$keyStrengthBytes = (int) ($encryptionStrengthBits / 8);
$hashLength = $keyStrengthBytes * 2 + self::PASSWORD_VERIFIER_SIZE * 8;
$hash = hash_pbkdf2(
'sha1',
$password,
$salt,
self::ITERATION_COUNT,
$hashLength,
true
);
$this->key = substr($hash, 0, $keyStrengthBytes);
$sha1Mac = substr($hash, $keyStrengthBytes, $keyStrengthBytes);
$this->hmacContext = hash_init('sha1', \HASH_HMAC, $sha1Mac);
$this->passwordVerifier = substr($hash, 2 * $keyStrengthBytes, self::PASSWORD_VERIFIER_SIZE);
}
/**
* @return string
*/
public function getPasswordVerifier()
{
return $this->passwordVerifier;
}
public function updateIv()
{
for ($ivCharIndex = 0; $ivCharIndex < self::IV_SIZE; $ivCharIndex++) {
$ivByte = \ord($this->iv[$ivCharIndex]);
if (++$ivByte === 256) {
// overflow, set this one to 0, increment next
$this->iv[$ivCharIndex] = "\0";
} else {
// no overflow, just write incremented number back and abort
$this->iv[$ivCharIndex] = \chr($ivByte);
break;
}
}
}
/**
* @param string $data
*
* @return string
*/
public function decryption($data)
{
hash_update($this->hmacContext, $data);
return CryptoUtil::decryptAesCtr($data, $this->key, $this->iv);
}
/**
* @param string $data
*
* @return string
*/
public function encrypt($data)
{
$encryptionData = CryptoUtil::encryptAesCtr($data, $this->key, $this->iv);
hash_update($this->hmacContext, $encryptionData);
return $encryptionData;
}
/**
* @param string $authCode
*
* @throws ZipAuthenticationException
*/
public function checkAuthCode($authCode)
{
$hmac = $this->getHmac();
// check authenticationCode
if (strcmp($hmac, $authCode) !== 0) {
throw new ZipAuthenticationException('Authenticated WinZip AES entry content has been tampered with.');
}
}
/**
* @return string
*/
public function getHmac()
{
return substr(
hash_final($this->hmacContext, true),
0,
self::FOOTER_SIZE
);
}
}