add support for Galois/Counter Mode (GCM)

This commit is contained in:
terrafrost 2018-12-27 08:31:35 -06:00
parent a30cfff79c
commit 01c92a59f8
7 changed files with 1020 additions and 121 deletions

View File

@ -315,4 +315,64 @@ abstract class Strings
return ltrim($bits, '0');
}
/**
* Switch Endianness Bit Order
*
* @access public
* @param string $x
* @return string
*/
public static function switchEndianness($x)
{
$r = '';
// from http://graphics.stanford.edu/~seander/bithacks.html#ReverseByteWith32Bits
for ($i = strlen($x) - 1; $i >= 0; $i--) {
$b = ord($x[$i]);
$p1 = ($b * 0x0802) & 0x22110;
$p2 = ($b * 0x8020) & 0x88440;
$r.= chr(
(($p1 | $p2) * 0x10101) >> 16
);
}
return $r;
}
/**
* Increment the current string
*
* @param string $var
* @return string
* @access public
*/
public static function increment_str(&$var)
{
for ($i = 4; $i <= strlen($var); $i+= 4) {
$temp = substr($var, -$i, 4);
switch ($temp) {
case "\xFF\xFF\xFF\xFF":
$var = substr_replace($var, "\x00\x00\x00\x00", -$i, 4);
break;
case "\x7F\xFF\xFF\xFF":
$var = substr_replace($var, "\x80\x00\x00\x00", -$i, 4);
return $var;
default:
$temp = unpack('Nnum', $temp);
$var = substr_replace($var, pack('N', $temp['num'] + 1), -$i, 4);
return $var;
}
}
$remainder = strlen($var) % 4;
if ($remainder == 0) {
return $var;
}
$temp = unpack('Nnum', str_pad(substr($var, 0, $remainder), 4, "\0", STR_PAD_LEFT));
$temp = substr(pack('N', $temp['num'] + 1), -$remainder);
$var = substr_replace($var, $temp, 0, $remainder);
return $var;
}
}

View File

@ -49,6 +49,8 @@
namespace phpseclib\Crypt;
use phpseclib\Common\Functions\Strings;
/**
* Pure-PHP implementation of AES.
*
@ -58,6 +60,38 @@ namespace phpseclib\Crypt;
*/
class AES extends Rijndael
{
/**
* Test for engine validity
*
* This is mainly just a wrapper to set things up for \phpseclib\Crypt\Common\SymmetricKey::isValidEngine()
*
* @see \phpseclib\Crypt\Common\SymmetricKey::__construct()
* @param int $engine
* @access protected
* @return bool
*/
protected function isValidEngineHelper($engine)
{
switch ($engine) {
case self::ENGINE_LIBSODIUM:
return function_exists('sodium_crypto_aead_aes256gcm_is_available') &&
sodium_crypto_aead_aes256gcm_is_available() &&
$this->mode == self::MODE_GCM &&
$this->key_length == 32 &&
$this->nonce && strlen($this->nonce) == 12;
case self::ENGINE_OPENSSL_GCM:
if (!extension_loaded('openssl')) {
return false;
}
$methods = openssl_get_cipher_methods();
return $this->mode == self::MODE_GCM &&
version_compare(PHP_VERSION, '7.1.0', '>=') &&
in_array('aes-' . $this->getKeyLength() . '-gcm', $methods);
}
return parent::isValidEngineHelper($engine);
}
/**
* Dummy function
*
@ -120,4 +154,101 @@ class AES extends Rijndael
parent::setKey($key);
}
/**
* Encrypts a message.
*
* @see self::decrypt()
* @see parent::encrypt()
* @access public
* @param string $plaintext
* @return string
*/
public function encrypt($plaintext)
{
switch ($this->engine) {
case self::ENGINE_LIBSODIUM:
$this->checkForChanges();
$this->newtag = sodium_crypto_aead_aes256gcm_encrypt($plaintext, $this->aad, $this->nonce, $this->key);
return Strings::shift($this->newtag, strlen($plaintext));
case self::ENGINE_OPENSSL_GCM:
$this->checkForChanges();
return openssl_encrypt(
$plaintext,
'aes-' . $this->getKeyLength() . '-gcm',
$this->key,
OPENSSL_RAW_DATA,
$this->nonce,
$this->newtag,
$this->aad
);
}
return parent::encrypt($plaintext);
}
/**
* Decrypts a message.
*
* @see self::encrypt()
* @see parent::decrypt()
* @access public
* @param string $ciphertext
* @return string
*/
public function decrypt($ciphertext)
{
switch ($this->engine) {
case self::ENGINE_LIBSODIUM:
$this->checkForChanges();
if ($this->oldtag === false) {
throw new \UnexpectedValueException('Authentication Tag has not been set');
}
if (strlen($this->oldtag) != 16) {
break;
}
$plaintext = sodium_crypto_aead_aes256gcm_decrypt($ciphertext . $this->oldtag, $this->aad, $this->nonce, $this->key);
if ($plaintext === false) {
$this->oldtag = false;
throw new \UnexpectedValueException('Error decrypting ciphertext with libsodium');
}
return $plaintext;
case self::ENGINE_OPENSSL_GCM:
$this->checkForChanges();
if ($this->oldtag === false) {
throw new \UnexpectedValueException('Authentication Tag has not been set');
}
$plaintext = openssl_decrypt(
$ciphertext,
'aes-' . $this->getKeyLength() . '-gcm',
$this->key,
OPENSSL_RAW_DATA,
$this->nonce,
$this->oldtag,
$this->aad
);
if ($plaintext === false) {
$this->oldtag = false;
throw new \UnexpectedValueException('Error decrypting ciphertext with OpenSSL');
}
return $plaintext;
}
return parent::decrypt($ciphertext);
}
/**
* Check For Changes
*
* @see self::encrypt()
* @see self::decrypt()
* @access private
*/
private function checkForChanges()
{
if ($this->changed) {
$this->clearBuffers();
$this->changed = false;
}
}
}

View File

@ -39,6 +39,7 @@ namespace phpseclib\Crypt\Common;
use phpseclib\Crypt\Hash;
use phpseclib\Common\Functions\Strings;
use phpseclib\Math\BigInteger;
use phpseclib\Math\BinaryField;
/**
* Base Class for all \phpseclib\Crypt\* cipher classes
@ -90,10 +91,16 @@ abstract class SymmetricKey
* @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Output_feedback_.28OFB.29
*/
const MODE_OFB = 4;
/**
* Encrypt / decrypt using Galois/Counter mode.
*
* @link https://en.wikipedia.org/wiki/Galois/Counter_Mode
*/
const MODE_GCM = 5;
/**
* Encrypt / decrypt using streaming mode.
*/
const MODE_STREAM = 5;
const MODE_STREAM = 6;
/**#@-*/
/**
@ -109,6 +116,7 @@ abstract class SymmetricKey
'cfb' => self::MODE_CFB,
'cfb8' => self::MODE_CFB8,
'ofb' => self::MODE_OFB,
'gcm' => self::MODE_GCM,
'stream' => self::MODE_STREAM
];
@ -129,9 +137,17 @@ abstract class SymmetricKey
*/
const ENGINE_MCRYPT = 3;
/**
* Base value for the mcrypt implementation $engine switch
* Base value for the openssl implementation $engine switch
*/
const ENGINE_OPENSSL = 4;
/**
* Base value for the libsodium implementation $engine switch
*/
const ENGINE_LIBSODIUM = 5;
/**
* Base value for the openssl / gcm implementation $engine switch
*/
const ENGINE_OPENSSL_GCM = 6;
/**#@-*/
/**
@ -141,10 +157,12 @@ abstract class SymmetricKey
* @see \phpseclib\Crypt\Common\SymmetricKey::getEngine()
*/
const ENGINE_MAP = [
self::ENGINE_INTERNAL => 'PHP',
self::ENGINE_EVAL => 'Eval',
self::ENGINE_MCRYPT => 'mcrypt',
self::ENGINE_OPENSSL => 'OpenSSL'
self::ENGINE_INTERNAL => 'PHP',
self::ENGINE_EVAL => 'Eval',
self::ENGINE_MCRYPT => 'mcrypt',
self::ENGINE_OPENSSL => 'OpenSSL',
self::ENGINE_LIBSODIUM => 'libsodium',
self::ENGINE_OPENSSL_GCM => 'OpenSSL (GCM)'
];
/**
@ -350,10 +368,12 @@ abstract class SymmetricKey
* which will be determined automatically on __construct()
*
* Currently available $engines are:
* - self::ENGINE_OPENSSL (very fast, php-extension: openssl, extension_loaded('openssl') required)
* - self::ENGINE_MCRYPT (fast, php-extension: mcrypt, extension_loaded('mcrypt') required)
* - self::ENGINE_EVAL (medium, pure php-engine, no php-extension required)
* - self::ENGINE_INTERNAL (slower, pure php-engine, no php-extension required)
* - self::ENGINE_LIBSODIUM (very fast, php-extension: libsodium, extension_loaded('libsodium') required)
* - self::ENGINE_OPENSSL_GCM (very fast, php-extension: openssl, extension_loaded('openssl') required)
* - self::ENGINE_OPENSSL (very fast, php-extension: openssl, extension_loaded('openssl') required)
* - self::ENGINE_MCRYPT (fast, php-extension: mcrypt, extension_loaded('mcrypt') required)
* - self::ENGINE_EVAL (medium, pure php-engine, no php-extension required)
* - self::ENGINE_INTERNAL (slower, pure php-engine, no php-extension required)
*
* @see self::setEngine()
* @see self::encrypt()
@ -471,6 +491,74 @@ abstract class SymmetricKey
*/
protected $explicit_key_length = false;
/**
* Hash subkey for GHASH
*
* @see self::setupGCM()
* @see self::ghash()
* @var BinaryField\Integer
* @access private
*/
private $h;
/**
* Additional authenticated data
*
* @var string
* @access private
*/
protected $aad = '';
/**
* Authentication Tag produced after a round of encryption
*
* @var string
* @access private
*/
protected $newtag = false;
/**
* Authentication Tag to be verified during decryption
*
* @var string
* @access private
*/
protected $oldtag = false;
/**
* GCM Binary Field
*
* @see self::initialize_static_variables()
* @see self::ghash()
* @var BinaryField
* @access private
*/
private static $gcmField;
/**
* The Original Initialization Vector
*
* GCM uses the nonce to build the IV but we want to be able to distinguish between nonce-derived
* IV's and user-set IV's
*
* @see self::setIV()
* @var string
* @access private
*/
private $origIV = false;
/**
* Nonce
*
* Only used with GCM. We could re-use setIV() but nonce's can be of a different length and
* toggling between GCM and other modes could be more complicated if we re-used setIV()
*
* @see self::setNonce()
* @var string
* @access private
*/
protected $nonce = false;
/**
* Default Constructor.
*
@ -484,14 +572,22 @@ abstract class SymmetricKey
*
* - cfb
*
* - cfb8
*
* - ofb
*
* - gcm
*
* @param string $mode
* @access public
* @throws \InvalidArgumentException if an invalid / unsupported mode is provided
*/
public function __construct($mode)
{
if (!isset(self::$gcmField)) {
self::initialize_static_variables();
}
$mode = strtolower($mode);
// necessary because of 5.6 compatibility; we can't do isset(self::MODE_MAP[$mode]) in 5.6
$map = self::MODE_MAP;
@ -514,6 +610,12 @@ abstract class SymmetricKey
case self::MODE_STREAM:
$this->paddable = false;
break;
case self::MODE_GCM:
if ($this->block_size != 16) {
throw new \InvalidArgumentException('GCM is only valid for block ciphers with a block size of 128 bits');
}
$this->paddable = false;
break;
default:
throw new \InvalidArgumentException('No valid mode has been specified');
}
@ -521,10 +623,20 @@ abstract class SymmetricKey
$this->mode = $mode;
}
/**
* Initialize static variables
*
* @access private
*/
private static function initialize_static_variables()
{
self::$gcmField = new BinaryField(128, 7, 2, 1, 0);
}
/**
* Sets the initialization vector.
*
* setIV() is not required when self::MODE_ECB (or ie for AES: \phpseclib\Crypt\AES::MODE_ECB) is being used.
* setIV() is not required when ecb or gcm modes are being used.
*
* @access public
* @param string $iv
@ -538,6 +650,10 @@ abstract class SymmetricKey
throw new \InvalidArgumentException('This mode does not require an IV.');
}
if ($this->mode == self::MODE_GCM) {
throw new \InvalidArgumentException('Use setNonce instead');
}
if (!$this->usesIV()) {
throw new \InvalidArgumentException('This algorithm does not use an IV.');
}
@ -546,10 +662,48 @@ abstract class SymmetricKey
throw new \LengthException('Received initialization vector of size ' . strlen($iv) . ', but size ' . $this->block_size . ' is required');
}
$this->iv = $iv;
$this->iv = $this->origIV = $iv;
$this->changed = true;
}
/**
* Sets the nonce.
*
* setNonce() is only required when gcm is used
*
* @access public
* @param string $nonce
* @throws \RuntimeException if an IV is provided when one shouldn't be
*/
public function setNonce($nonce)
{
if ($this->mode != self::MODE_GCM) {
throw new \RuntimeException('Nonces are only used in GCM mode.');
}
$this->nonce = $nonce;
$this->changed = true;
$this->setEngine();
}
/**
* Sets additional authenticated data
*
* setAAD() is only used by gcm
*
* @access public
* @param string $aad
* @throws \RuntimeException if mode isn't GCM
*/
public function setAAD($aad)
{
if ($this->mode != self::MODE_GCM) {
throw new \RuntimeException('Additional authenticated data is only utilized in GCM mode');
}
$this->aad = $aad;
}
/**
* Returns whether or not the algorithm uses an IV
*
@ -558,7 +712,18 @@ abstract class SymmetricKey
*/
public function usesIV()
{
return true;
return $this->mode != self::MODE_GCM;
}
/**
* Returns whether or not the algorithm uses a nonce
*
* @access public
* @return bool
*/
public function usesNonce()
{
return $this->mode == self::MODE_GCM;
}
/**
@ -868,6 +1033,30 @@ abstract class SymmetricKey
$plaintext = $this->pad($plaintext);
}
if ($this->mode == self::MODE_GCM) {
if ($this->changed) {
$this->genericSetup();
$this->changed = false;
}
$oldIV = $this->iv;
Strings::increment_str($this->iv);
$cipher = new static('ctr');
$cipher->setKey($this->key);
$cipher->setIV($this->iv);
$ciphertext = $cipher->encrypt($plaintext);
$s = $this->ghash(
self::nullPad128($this->aad) .
self::nullPad128($ciphertext) .
self::len64($this->aad) .
self::len64($ciphertext)
);
$cipher->encryptIV = $this->iv = $this->encryptIV = $this->decryptIV = $oldIV;
$this->newtag = $cipher->encrypt($s);
return $ciphertext;
}
if ($this->engine === self::ENGINE_OPENSSL) {
if ($this->changed) {
$this->clearBuffers();
@ -1062,7 +1251,7 @@ abstract class SymmetricKey
if (strlen($block) > strlen($buffer['ciphertext'])) {
$buffer['ciphertext'].= $this->encryptBlock($xor);
}
$this->increment_str($xor);
Strings::increment_str($xor);
$key = Strings::shift($buffer['ciphertext'], $block_size);
$ciphertext.= $block ^ $key;
}
@ -1070,7 +1259,7 @@ abstract class SymmetricKey
for ($i = 0; $i < strlen($plaintext); $i+=$block_size) {
$block = substr($plaintext, $i, $block_size);
$key = $this->encryptBlock($xor);
$this->increment_str($xor);
Strings::increment_str($xor);
$ciphertext.= $block ^ $key;
}
}
@ -1194,6 +1383,38 @@ abstract class SymmetricKey
throw new \LengthException('The ciphertext length (' . strlen($ciphertext) . ') needs to be a multiple of the block size (' . $this->block_size . ')');
}
if ($this->mode == self::MODE_GCM) {
if ($this->changed) {
$this->genericSetup();
$this->changed = false;
}
if ($this->oldtag === false) {
throw new \UnexpectedValueException('Authentication Tag has not been set');
}
$oldIV = $this->iv;
Strings::increment_str($this->iv);
$cipher = new static('ctr');
$cipher->setKey($this->key);
$cipher->setIV($this->iv);
$plaintext = $cipher->decrypt($ciphertext);
$s = $this->ghash(
self::nullPad128($this->aad) .
self::nullPad128($ciphertext) .
self::len64($this->aad) .
self::len64($ciphertext)
);
$cipher->encryptIV = $this->iv = $this->encryptIV = $this->decryptIV = $oldIV;
$newtag = $cipher->encrypt($s);
if ($this->oldtag != substr($newtag, 0, strlen($newtag))) {
$this->oldtag = false;
throw new \UnexpectedValueException('Derived authentication tag and supplied authentication tag do not match');
}
$this->oldtag = false;
return $plaintext;
}
if ($this->engine === self::ENGINE_OPENSSL) {
if ($this->changed) {
$this->clearBuffers();
@ -1374,7 +1595,7 @@ abstract class SymmetricKey
if (strlen($block) > strlen($buffer['ciphertext'])) {
$buffer['ciphertext'].= $this->encryptBlock($xor);
}
$this->increment_str($xor);
Strings::increment_str($xor);
$key = Strings::shift($buffer['ciphertext'], $block_size);
$plaintext.= $block ^ $key;
}
@ -1382,7 +1603,7 @@ abstract class SymmetricKey
for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) {
$block = substr($ciphertext, $i, $block_size);
$key = $this->encryptBlock($xor);
$this->increment_str($xor);
Strings::increment_str($xor);
$plaintext.= $block ^ $key;
}
}
@ -1485,6 +1706,62 @@ abstract class SymmetricKey
return $this->paddable ? $this->unpad($plaintext) : $plaintext;
}
/**
* Get the authentication tag
*
* Only used in GCM mode
*
* @see self::encrypt()
* @param int $length optional
* @return string
* @access public
* @throws \LengthException if $length isn't of a sufficient length
* @throws \RuntimeException if GCM mode isn't being used
*/
public function getTag($length = 16)
{
if ($this->mode != self::MODE_GCM) {
throw new \RuntimeException('Only GCM mode utilizes authentication tags');
}
// the tag is basically a single encrypted block of a 128-bit cipher. it can't be greater than 16
// bytes because that's bigger than a block is. if it were 0 you might as well be doing CTR and
// less than 4 provides minimal security that could be trivially easily brute forced.
// see https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf#page=36
// for more info
if ($length < 4 || $length > 16) {
throw new \LengthException('The authentication tag must be between 4 and 16 bytes long');
}
return $length == 16 ?
$this->newtag :
substr($this->newtag, 0, $length);
}
/**
* Sets the authentication tag
*
* Only used in GCM mode
*
* @see self::decrypt()
* @param string $tag
* @access public
* @throws \LengthException if $length isn't of a sufficient length
* @throws \RuntimeException if GCM mode isn't being used
*/
public function setTag($tag)
{
if ($this->mode != self::MODE_GCM) {
throw new \RuntimeException('Only GCM mode utilizes authentication tags');
}
$length = strlen($tag);
if ($length < 4 || $length > 16) {
throw new \LengthException('The authentication tag must be between 4 and 16 bytes long');
}
$this->oldtag = $tag;
}
/**
* Get the IV
*
@ -1496,7 +1773,7 @@ abstract class SymmetricKey
* @return string
* @access private
*/
public function getIV($iv)
protected function getIV($iv)
{
return $this->mode == self::MODE_ECB ? str_repeat("\0", $this->block_size) : $iv;
}
@ -1532,7 +1809,7 @@ abstract class SymmetricKey
if (strlen($block) > strlen($buffer['ciphertext'])) {
$buffer['ciphertext'].= openssl_encrypt($xor, $this->cipher_name_openssl_ecb, $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
}
$this->increment_str($xor);
Strings::increment_str($xor);
$otp = Strings::shift($buffer['ciphertext'], $block_size);
$ciphertext.= $block ^ $otp;
}
@ -1540,7 +1817,7 @@ abstract class SymmetricKey
for ($i = 0; $i < strlen($plaintext); $i+=$block_size) {
$block = substr($plaintext, $i, $block_size);
$otp = openssl_encrypt($xor, $this->cipher_name_openssl_ecb, $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
$this->increment_str($xor);
Strings::increment_str($xor);
$ciphertext.= $block ^ $otp;
}
}
@ -1583,7 +1860,7 @@ abstract class SymmetricKey
if ($this->continuousBuffer) {
$encryptIV = openssl_decrypt($encryptIV, $this->cipher_name_openssl_ecb, $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
if ($overflow) {
$this->increment_str($encryptIV);
Strings::increment_str($encryptIV);
}
}
@ -1659,6 +1936,7 @@ abstract class SymmetricKey
case self::MODE_CBC:
return 'cbc';
case self::MODE_CTR:
case self::MODE_GCM:
return 'ctr';
case self::MODE_CFB:
return 'cfb';
@ -1744,6 +2022,10 @@ abstract class SymmetricKey
return;
}
if ($this->mode == self::MODE_GCM) {
throw new \RuntimeException('This mode does not run in continuous mode');
}
$this->continuousBuffer = true;
$this->setEngine();
@ -1850,6 +2132,8 @@ abstract class SymmetricKey
*
* Currently, $engine could be:
*
* - libsodium[very fast]
*
* - OpenSSL [very fast]
*
* - mcrypt [fast]
@ -1872,7 +2156,7 @@ abstract class SymmetricKey
$reverseMap = array_flip($reverseMap);
}
$engine = strtolower($engine);
$this->preferredEngine = isset($reverseMap[$engine]) ? $reverseMap[$engine] : self::ENGINE_OPENSSL;
$this->preferredEngine = isset($reverseMap[$engine]) ? $reverseMap[$engine] : self::ENGINE_LIBSODIUM;
$this->setEngine();
}
@ -1900,6 +2184,8 @@ abstract class SymmetricKey
$candidateEngines = [
$this->preferredEngine,
self::ENGINE_LIBSODIUM,
self::ENGINE_OPENSSL_GCM,
self::ENGINE_OPENSSL,
self::ENGINE_MCRYPT,
self::ENGINE_EVAL
@ -1999,6 +2285,26 @@ abstract class SymmetricKey
}
}
/**
* Calls the appropriate setup method
*
* @access private
*/
protected function genericSetup()
{
switch ($this->engine) {
case self::ENGINE_MCRYPT:
$this->setupMcrypt();
break;
case self::ENGINE_EVAL:
case self::ENGINE_INTERNAL:
$this->setup();
break;
default:
$this->clearBuffers();
}
}
/**
* Setup the self::ENGINE_MCRYPT $engine
*
@ -2125,12 +2431,26 @@ abstract class SymmetricKey
* @internal Could, but not must, extend by the child Crypt_* class
* @throws \UnexpectedValueException when an IV is required but not defined
*/
private function clearBuffers()
public function clearBuffers()
{
$this->enbuffer = $this->debuffer = ['ciphertext' => '', 'xor' => '', 'pos' => 0, 'enmcrypt_init' => true];
//$this->newtag = $this->oldtag = false;
if ($this->mode == self::MODE_GCM) {
if ($this->nonce === false) {
throw new \UnexpectedValueException('No nonce has been defined');
}
if (!in_array($this->engine, [self::ENGINE_LIBSODIUM, self::ENGINE_OPENSSL_GCM])) {
$this->setupGCM();
}
} else {
$this->iv = $this->origIV;
}
if ($this->iv === false && !in_array($this->mode, [self::MODE_STREAM, self::MODE_ECB])) {
throw new \UnexpectedValueException('No IV has been defined');
if ($this->mode != self::MODE_GCM || !in_array($this->engine, [self::ENGINE_LIBSODIUM, self::ENGINE_OPENSSL_GCM])) {
throw new \UnexpectedValueException('No IV has been defined');
}
}
if ($this->key === false) {
@ -2140,43 +2460,6 @@ abstract class SymmetricKey
$this->encryptIV = $this->decryptIV = $this->iv;
}
/**
* Increment the current string
*
* @see self::decrypt()
* @see self::encrypt()
* @param string $var
* @access private
*/
protected function increment_str(&$var)
{
for ($i = 4; $i <= strlen($var); $i+= 4) {
$temp = substr($var, -$i, 4);
switch ($temp) {
case "\xFF\xFF\xFF\xFF":
$var = substr_replace($var, "\x00\x00\x00\x00", -$i, 4);
break;
case "\x7F\xFF\xFF\xFF":
$var = substr_replace($var, "\x80\x00\x00\x00", -$i, 4);
return;
default:
$temp = unpack('Nnum', $temp);
$var = substr_replace($var, pack('N', $temp['num'] + 1), -$i, 4);
return;
}
}
$remainder = strlen($var) % 4;
if ($remainder == 0) {
return;
}
$temp = unpack('Nnum', str_pad(substr($var, 0, $remainder), 4, "\0", STR_PAD_LEFT));
$temp = substr(pack('N', $temp['num'] + 1), -$remainder);
$var = substr_replace($var, $temp, 0, $remainder);
}
/**
* Setup the performance-optimized function for de/encrypt()
*
@ -2404,7 +2687,7 @@ abstract class SymmetricKey
if (strlen($_block) > strlen($_buffer["ciphertext"])) {
$in = $_xor;
'.$encrypt_block.'
$this->increment_str($_xor);
\phpseclib\Common\Functions\Strings::increment_str($_xor);
$_buffer["ciphertext"].= $in;
}
$_key = \phpseclib\Common\Functions\Strings::shift($_buffer["ciphertext"], '.$block_size.');
@ -2415,7 +2698,7 @@ abstract class SymmetricKey
$_block = substr($_text, $_i, '.$block_size.');
$in = $_xor;
'.$encrypt_block.'
$this->increment_str($_xor);
\phpseclib\Common\Functions\Strings::increment_str($_xor);
$_key = $in;
$_ciphertext.= $_block ^ $_key;
}
@ -2442,7 +2725,7 @@ abstract class SymmetricKey
if (strlen($_block) > strlen($_buffer["ciphertext"])) {
$in = $_xor;
'.$encrypt_block.'
$this->increment_str($_xor);
\phpseclib\Common\Functions\Strings::increment_str($_xor);
$_buffer["ciphertext"].= $in;
}
$_key = \phpseclib\Common\Functions\Strings::shift($_buffer["ciphertext"], '.$block_size.');
@ -2453,7 +2736,7 @@ abstract class SymmetricKey
$_block = substr($_text, $_i, '.$block_size.');
$in = $_xor;
'.$encrypt_block.'
$this->increment_str($_xor);
\phpseclib\Common\Functions\Strings::increment_str($_xor);
$_key = $in;
$_plaintext.= $_block ^ $_key;
}
@ -2745,8 +3028,6 @@ abstract class SymmetricKey
// Before discrediting this, please read the following:
// @see https://github.com/phpseclib/phpseclib/issues/1293
// @see https://github.com/phpseclib/phpseclib/pull/1143
// to summarize, manual code generation/inlining/unrolling are employed for a massive
// performance increase
eval('$func = function ($_action, $_text) { ' . $init_crypt . 'if ($_action == "encrypt") { ' . $encrypt . ' } else { ' . $decrypt . ' }};');
return \Closure::bind($func, $this, static::class);
@ -2791,4 +3072,104 @@ abstract class SymmetricKey
return $safeint . '((fmod(floor($temp / 0x80000000), 2) & 1) << 31))';
}
}
/**
* Sets up GCM parameters
*
* See steps 1-2 of https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf#page=23
* for more info
*
* @access private
*/
private function setupGCM()
{
// don't keep on re-calculating $this->h
if (!$this->h || $this->h->key != $this->key) {
$cipher = new static('ecb');
$cipher->setKey($this->key);
$cipher->disablePadding();
$this->h = self::$gcmField->newInteger(
Strings::switchEndianness($cipher->encrypt("\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"))
);
$this->h->key = $this->key;
}
if (strlen($this->nonce) == 12) {
$this->iv = $this->nonce . "\0\0\0\1";
} else {
$s = 16 * ceil(strlen($this->nonce) / 16) - strlen($this->nonce);
$this->iv = $this->ghash(
self::nullPad128($this->nonce) . str_repeat("\0", 8) . self::len64($this->nonce)
);
}
}
/**
* Performs GHASH operation
*
* See https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf#page=20
* for more info
*
* @see self::decrypt()
* @see self::encrypt()
* @access private
* @param string $x
* @return string
*/
private function ghash($x)
{
$h = $this->h;
$y = ["\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"];
$x = str_split($x, 16);
$n = 0;
// the switchEndianness calls are necessary because the multiplication algorithm in BinaryField/Integer
// interprets strings as polynomials in big endian order whereas in GCM they're interpreted in little
// endian order per https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf#page=19.
// big endian order is what binary field elliptic curves use per http://www.secg.org/sec1-v2.pdf#page=18.
// we could switchEndianness here instead of in the while loop but doing so in the while loop seems like it
// might be slightly more performant
//$x = Strings::switchEndianness($x);
foreach ($x as $xn) {
$xn = Strings::switchEndianness($xn);
$t = $y[$n] ^ $xn;
$temp = self::$gcmField->newInteger($t);
$y[++$n] = $temp->multiply($h)->toBytes();
$y[$n] = substr($y[$n], 1);
}
$y[$n] = Strings::switchEndianness($y[$n]);
return $y[$n];
}
/**
* Returns the bit length of a string in a packed format
*
* @see self::decrypt()
* @see self::encrypt()
* @see self::setupGCM()
* @access private
* @param string $str
* @return string
*/
private static function len64($str)
{
return "\0\0\0\0" . pack('N', 8 * strlen($str));
}
/**
* NULL pads a string to be a multiple of 128
*
* @see self::decrypt()
* @see self::encrypt()
* @see self::setupGCM()
* @access private
* @param string $str
* @return string
*/
private static function nullPad128($str)
{
$len = strlen($str);
return $str . str_repeat("\0", 16 * ceil($len / 16) - $len);
}
}

View File

@ -59,6 +59,9 @@ class BinaryField extends FiniteField
$mStart = 2 * $m - 2;
$t = ceil($m / 8);
$finalMask = chr((1 << ($m % 8)) - 1);
if ($finalMask == "\0") {
$finalMask = "\xFF";
}
$bitLen = $mStart + 1;
$pad = ceil($bitLen / 8);
$h = $bitLen & 7;

View File

@ -163,10 +163,6 @@ class Integer extends Base
*/
private static function polynomialDivide($x, $y)
{
if (strcmp($x, str_pad($y, strlen($x), "\0", STR_PAD_LEFT)) < 0) {
return ['', ltrim($x, "\0")];
}
// in wikipedia's description of the algorithm, lc() is the leading coefficient. over a binary field that's
// always going to be 1.

View File

@ -55,6 +55,7 @@ use phpseclib\Crypt\Hash;
use phpseclib\Crypt\Random;
use phpseclib\Crypt\RC4;
use phpseclib\Crypt\Rijndael;
use phpseclib\Crypt\AES;
use phpseclib\Crypt\RSA;
use phpseclib\Crypt\TripleDES;
use phpseclib\Crypt\Twofish;
@ -906,14 +907,6 @@ class SSH2
*/
private $bad_key_size_fix = false;
/**
* The selected decryption algorithm
*
* @var string
* @access private
*/
private $decrypt_algorithm = '';
/**
* Should we try to re-connect to re-establish keys?
*
@ -1355,6 +1348,10 @@ class SSH2
//'arcfour', // OPTIONAL the ARCFOUR stream cipher with a 128-bit key
// from <https://tools.ietf.org/html/rfc5647>:
'aes128-gcm@openssh.com',
'aes256-gcm@openssh.com',
// CTR modes from <http://tools.ietf.org/html/rfc4344#section-4>:
'aes128-ctr', // RECOMMENDED AES (Rijndael) in SDCTR mode, with 128-bit key
'aes192-ctr', // RECOMMENDED AES with 192-bit key
@ -1381,7 +1378,7 @@ class SSH2
'3des-ctr', // RECOMMENDED Three-key 3DES in SDCTR mode
'3des-cbc', // REQUIRED three-key 3DES in CBC mode
//'none' // OPTIONAL no encryption; NOT RECOMMENDED
//'none' // OPTIONAL no encryption; NOT RECOMMENDED
];
if (extension_loaded('openssl') && !extension_loaded('mcrypt')) {
@ -1456,7 +1453,6 @@ class SSH2
$encryption_algorithms_server_to_client = $encryption_algorithms_client_to_server = implode(',', $encryption_algorithms);
$mac_algorithms_server_to_client = $mac_algorithms_client_to_server = implode(',', $mac_algorithms);
$compression_algorithms_server_to_client = $compression_algorithms_client_to_server = implode(',', $compression_algorithms);
$client_cookie = Random::string(16);
$kexinit_payload_client = pack(
@ -1673,20 +1669,20 @@ class SSH2
// http://tools.ietf.org/html/rfc2412, appendex E
case 'diffie-hellman-group1-sha1':
$prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
'020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
'4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF';
'020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
'4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF';
break;
// see http://tools.ietf.org/html/rfc3526#section-3
case 'diffie-hellman-group14-sha1':
$prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
'020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
'4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' .
'98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' .
'9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' .
'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' .
'3995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF';
'020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
'4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' .
'98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' .
'9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' .
'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' .
'3995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF';
break;
}
// For both diffie-hellman-group1-sha1 and diffie-hellman-group14-sha1
@ -1860,8 +1856,6 @@ class SSH2
throw new \UnexpectedValueException('Expected SSH_MSG_NEWKEYS');
}
$this->decrypt_algorithm = $decrypt;
$keyBytes = pack('Na*', strlen($keyBytes), $keyBytes);
$this->encrypt = $this->encryption_algorithm_to_crypt_instance($encrypt);
@ -1872,7 +1866,6 @@ class SSH2
if ($this->encrypt->getBlockLengthInBytes()) {
$this->encrypt_block_size = $this->encrypt->getBlockLengthInBytes();
}
$this->encrypt->enableContinuousBuffer();
$this->encrypt->disablePadding();
if ($this->encrypt->usesIV()) {
@ -1883,11 +1876,22 @@ class SSH2
$this->encrypt->setIV(substr($iv, 0, $this->encrypt_block_size));
}
// currently, only AES GCM uses a nonce and per RFC5647,
// "SSH AES-GCM requires a 12-octet Initial IV"
if (!$this->encrypt->usesNonce()) {
$this->encrypt->enableContinuousBuffer();
} else {
$nonce = $kexHash->hash($keyBytes . $this->exchange_hash . 'A' . $this->session_id);
$this->encrypt->fixed = substr($nonce, 0, 4);
$this->encrypt->invocation_counter = substr($nonce, 4, 8);
}
$key = $kexHash->hash($keyBytes . $this->exchange_hash . 'C' . $this->session_id);
while ($encryptKeyLength > strlen($key)) {
$key.= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
}
$this->encrypt->setKey(substr($key, 0, $encryptKeyLength));
$this->encrypt->name = $encrypt;
}
$this->decrypt = $this->encryption_algorithm_to_crypt_instance($decrypt);
@ -1898,7 +1902,6 @@ class SSH2
if ($this->decrypt->getBlockLengthInBytes()) {
$this->decrypt_block_size = $this->decrypt->getBlockLengthInBytes();
}
$this->decrypt->enableContinuousBuffer();
$this->decrypt->disablePadding();
if ($this->decrypt->usesIV()) {
@ -1909,11 +1912,21 @@ class SSH2
$this->decrypt->setIV(substr($iv, 0, $this->decrypt_block_size));
}
if (!$this->decrypt->usesNonce()) {
$this->decrypt->enableContinuousBuffer();
} else {
// see https://tools.ietf.org/html/rfc5647#section-7.1
$nonce = $kexHash->hash($keyBytes . $this->exchange_hash . 'B' . $this->session_id);
$this->decrypt->fixed = substr($nonce, 0, 4);
$this->decrypt->invocation_counter = substr($nonce, 4, 8);
}
$key = $kexHash->hash($keyBytes . $this->exchange_hash . 'D' . $this->session_id);
while ($decryptKeyLength > strlen($key)) {
$key.= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
}
$this->decrypt->setKey(substr($key, 0, $decryptKeyLength));
$this->decrypt->name = $decrypt;
}
/* The "arcfour128" algorithm is the RC4 cipher, as described in
@ -1936,6 +1949,10 @@ class SSH2
throw new NoSupportedAlgorithmsException('No compatible client to server message authentication algorithms found');
}
if ($this->encrypt->usesNonce()) {
$mac_algorithm = 'none';
}
$createKeyLength = 0; // ie. $mac_algorithm == 'none'
switch ($mac_algorithm) {
case 'hmac-sha2-256':
@ -1959,12 +1976,25 @@ class SSH2
$createKeyLength = 16;
}
if ($this->hmac_create) {
$key = $kexHash->hash($keyBytes . $this->exchange_hash . 'E' . $this->session_id);
while ($createKeyLength > strlen($key)) {
$key.= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
}
$this->hmac_create->setKey(substr($key, 0, $createKeyLength));
$this->hmac_create->name = $mac_algorithm;
}
$mac_algorithm = $this->array_intersect_first($mac_algorithms, $this->mac_algorithms_server_to_client);
if ($mac_algorithm === false) {
$this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
throw new NoSupportedAlgorithmsException('No compatible server to client message authentication algorithms found');
}
if ($this->decrypt->usesNonce()) {
$mac_algorithm = 'none';
}
$checkKeyLength = 0;
$this->hmac_size = 0;
switch ($mac_algorithm) {
@ -1994,17 +2024,14 @@ class SSH2
$this->hmac_size = 12;
}
$key = $kexHash->hash($keyBytes . $this->exchange_hash . 'E' . $this->session_id);
while ($createKeyLength > strlen($key)) {
$key.= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
if ($this->hmac_check) {
$key = $kexHash->hash($keyBytes . $this->exchange_hash . 'F' . $this->session_id);
while ($checkKeyLength > strlen($key)) {
$key.= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
}
$this->hmac_check->setKey(substr($key, 0, $checkKeyLength));
$this->hmac_check->name = $mac_algorithm;
}
$this->hmac_create->setKey(substr($key, 0, $createKeyLength));
$key = $kexHash->hash($keyBytes . $this->exchange_hash . 'F' . $this->session_id);
while ($checkKeyLength > strlen($key)) {
$key.= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
}
$this->hmac_check->setKey(substr($key, 0, $checkKeyLength));
$compression_algorithm = $this->array_intersect_first($compression_algorithms, $this->compression_algorithms_server_to_client);
if ($compression_algorithm === false) {
@ -2039,6 +2066,7 @@ class SSH2
switch ($algorithm) {
case 'none':
return 0;
case 'aes128-gcm@openssh.com':
case 'aes128-cbc':
case 'aes128-ctr':
case 'arcfour':
@ -2055,6 +2083,7 @@ class SSH2
case 'twofish192-cbc':
case 'twofish192-ctr':
return 24;
case 'aes256-gcm@openssh.com':
case 'aes256-cbc':
case 'aes256-ctr':
case 'arcfour256':
@ -2106,6 +2135,9 @@ class SSH2
case 'arcfour128':
case 'arcfour256':
return new RC4();
case 'aes128-gcm@openssh.com':
case 'aes256-gcm@openssh.com':
return new AES('gcm');
}
return null;
}
@ -3370,7 +3402,29 @@ class SSH2
}
if ($this->decrypt !== false) {
$raw = $this->decrypt->decrypt($raw);
// only aes128-gcm@openssh.com and aes256-gcm@openssh.com use nonces
if (!$this->decrypt->usesNonce()) {
$raw = $this->decrypt->decrypt($raw);
} else {
$this->decrypt->setNonce(
$this->decrypt->fixed .
$this->decrypt->invocation_counter
);
Strings::increment_str($this->decrypt->invocation_counter);
$this->decrypt->setAAD($temp = Strings::shift($raw, 4));
extract(unpack('Npacket_length', $temp));
/**
* @var integer $packet_length
*/
$raw.= $this->read_remaining_bytes($packet_length - $this->decrypt_block_size + 4);
$stop = microtime(true);
$tag = stream_get_contents($this->fsock, $this->decrypt_block_size);
$this->decrypt->setTag($tag);
$raw = $this->decrypt->decrypt($raw);
$raw = $temp . $raw;
$remaining_length = 0;
}
}
if (strlen($raw) < 5) {
@ -3382,13 +3436,16 @@ class SSH2
* @var integer $padding_length
*/
$remaining_length = $packet_length + 4 - $this->decrypt_block_size;
if (!isset($remaining_length)) {
$remaining_length = $packet_length + 4 - $this->decrypt_block_size;
}
// quoting <http://tools.ietf.org/html/rfc4253#section-6.1>,
// "implementations SHOULD check that the packet length is reasonable"
// PuTTY uses 0x9000 as the actual max packet size and so to shall we
// don't do this when GCM mode is used since GCM mode doesn't encrypt the length
if ($remaining_length < -$this->decrypt_block_size || $remaining_length > 0x9000 || $remaining_length % $this->decrypt_block_size != 0) {
if (!$this->bad_key_size_fix && $this->bad_algorithm_candidate($this->decrypt_algorithm) && !($this->bitmap & SSH2::MASK_LOGIN)) {
if (!$this->bad_key_size_fix && $this->bad_algorithm_candidate($this->decrypt ? $this->decrypt->name : '') && !($this->bitmap & SSH2::MASK_LOGIN)) {
$this->bad_key_size_fix = true;
$this->reset_connection(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
return false;
@ -3396,18 +3453,11 @@ class SSH2
throw new \RuntimeException('Invalid size');
}
$buffer = '';
while ($remaining_length > 0) {
$temp = stream_get_contents($this->fsock, $remaining_length);
if ($temp === false || feof($this->fsock)) {
$this->bitmap = 0;
throw new \RuntimeException('Error reading from socket');
}
$buffer.= $temp;
$remaining_length-= strlen($temp);
}
$buffer = $this->read_remaining_bytes($remaining_length);
$stop = microtime(true);
if (!isset($stop)) {
$stop = microtime(true);
}
if (strlen($buffer)) {
$raw.= $this->decrypt !== false ? $this->decrypt->decrypt($buffer) : $buffer;
}
@ -3443,6 +3493,30 @@ class SSH2
return $this->filter($payload, $skip_channel_filter);
}
/**
* Read Remaining Bytes
*
* @see self::get_binary_packet()
* @param int $remaining_length
* @return string
* @access private
*/
private function read_remaining_bytes($remaining_length)
{
$buffer = '';
while ($remaining_length > 0) {
$temp = stream_get_contents($this->fsock, $remaining_length);
if ($temp === false || feof($this->fsock)) {
$this->bitmap = 0;
throw new \RuntimeException('Error reading from socket');
}
$buffer.= $temp;
$remaining_length-= strlen($temp);
}
return $buffer;
}
/**
* Filter Binary Packets
*
@ -4012,10 +4086,17 @@ class SSH2
// 4 (packet length) + 1 (padding length) + 4 (minimal padding amount) == 9
$packet_length = strlen($data) + 9;
if ($this->encrypt !== false && $this->encrypt->usesNonce()) {
$packet_length-= 4;
}
// round up to the nearest $this->encrypt_block_size
$packet_length+= (($this->encrypt_block_size - 1) * $packet_length) % $this->encrypt_block_size;
// subtracting strlen($data) is obvious - subtracting 5 is necessary because of packet_length and padding_length
$padding_length = $packet_length - strlen($data) - 5;
if ($this->encrypt !== false && $this->encrypt->usesNonce()) {
$padding_length+= 4;
$packet_length+= 4;
}
$padding = Random::string($padding_length);
// we subtract 4 from packet_length because the packet_length field isn't supposed to include itself
@ -4025,10 +4106,20 @@ class SSH2
$this->send_seq_no++;
if ($this->encrypt !== false) {
$packet = $this->encrypt->encrypt($packet);
if (!$this->encrypt->usesNonce()) {
$packet = $this->encrypt->encrypt($packet);
} else {
$this->encrypt->setNonce(
$this->encrypt->fixed .
$this->encrypt->invocation_counter
);
Strings::increment_str($this->encrypt->invocation_counter);
$this->encrypt->setAAD($temp = substr($packet, 0, 4));
$packet = $temp . $this->encrypt->encrypt(substr($packet, 4));
}
}
$packet.= $hmac;
$packet.= $this->encrypt !== false && $this->encrypt->usesNonce() ? $this->encrypt->getTag() : $hmac;
$start = microtime(true);
$result = strlen($packet) == fputs($this->fsock, $packet);

View File

@ -0,0 +1,237 @@
<?php
/**
* @author Andreas Fischer <bantu@phpbb.com>
* @copyright 2013 Andreas Fischer
* @license http://www.opensource.org/licenses/mit-license.html MIT License
*/
use phpseclib\Crypt\AES;
class Unit_Crypt_GCMTest extends PhpseclibTestCase
{
/**
* Produces all combinations of test values.
*
* @return array
*/
public function engine128Vectors()
{
$engines = [
'PHP',
'Eval',
'mcrypt',
'OpenSSL',
'OpenSSL (GCM)'
];
// test vectors come from the following URL:
// http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-spec.pdf
$p1 = '00000000000000000000000000000000';
$p2 = 'd9313225f88406e5a55909c5aff5269a' .
'86a7a9531534f7da2e4c303d8a318a72' .
'1c3c0c95956809532fcf0e2449a6b525' .
'b16aedf5aa0de657ba637b391aafd255';
$p3 = 'd9313225f88406e5a55909c5aff5269a' .
'86a7a9531534f7da2e4c303d8a318a72' .
'1c3c0c95956809532fcf0e2449a6b525' .
'b16aedf5aa0de657ba637b39';
$n1 = '000000000000000000000000';
$n2 = 'cafebabefacedbaddecaf888';
$n3 = 'cafebabefacedbad';
$n4 = '9313225df88406e555909c5aff5269aa' .
'6a7a9538534f7da1e4c303d2a318a728' .
'c3c0c95156809539fcf0e2429a6b5254' .
'16aedbf5a0de6a57a637b39b';
$k1 = '00000000000000000000000000000000';
$k2 = 'feffe9928665731c6d6a8f9467308308';
$c1 = '0388dace60b6a392f328c2b971b2fe78';
$c2 = '42831ec2217774244b7221b784d0d49c' .
'e3aa212f2c02a4e035c17e2329aca12e' .
'21d514b25466931c7d8f6a5aac84aa05' .
'1ba30b396a0aac973d58e091473f5985';
$c3 = '42831ec2217774244b7221b784d0d49c' .
'e3aa212f2c02a4e035c17e2329aca12e' .
'21d514b25466931c7d8f6a5aac84aa05' .
'1ba30b396a0aac973d58e091';
$c4 = '61353b4c2806934a777ff51fa22a4755' .
'699b2a714fcdc6f83766e5f97b6c7423' .
'73806900e49f24b22b097544d4896b42' .
'4989b5e1ebac0f07c23f4598';
$c5 = '8ce24998625615b603a033aca13fb894' .
'be9112a5c3a211a8ba262a3cca7e2ca7' .
'01e4a9a4fba43c90ccdcb281d48c7c6f' .
'd62875d2aca417034c34aee5';
$a1 = 'feedfacedeadbeeffeedfacedeadbeef' .
'abaddad2';
$subvectors = [
// key, plaintext, nonce, aad, ciphertext, tag
// Test Case 1
[$k1, '', $n1, '', '', '58e2fccefa7e3061367f1d57a4e7455a'],
// Test Case 2
[$k1, $p1, $n1, '', $c1, 'ab6e47d42cec13bdf53a67b21257bddf'],
// Test Case 3
[$k2, $p2, $n2, '', $c2, '4d5c2af327cd64a62cf35abd2ba6fab4'],
// Test Case 4
[$k2, $p3, $n2, $a1, $c3, '5bc94fbc3221a5db94fae95ae7121a47'],
// Test Case 5
[$k2, $p3, $n3, $a1, $c4, '3612d2e79e3b0785561be14aaca2fccb'],
// Test Case 6
[$k2, $p3, $n4, $a1, $c5, '619cc5aefffe0bfa462af43c1699d050']
];
$vectors = [];
for ($i = 0; $i < count($subvectors); $i++) {
for ($j = 0; $j < count($subvectors[$i]); $j++) {
$subvectors[$i][$j] = pack('H*', $subvectors[$i][$j]);
}
foreach ($engines as $engine) {
$temp = $subvectors[$i];
array_unshift($temp, $engine);
$vectors[] = $temp;
}
}
return $vectors;
}
/**
* @dataProvider engine128Vectors
*/
public function test128Vectors($engine, $key, $plaintext, $nonce, $aad, $ciphertext, $tag)
{
$aes = new AES('gcm');
$aes->setKey($key);
$aes->setNonce($nonce);
$aes->setAAD($aad);
if (!$aes->isValidEngine($engine)) {
self::markTestSkipped("Unable to initialize $engine engine");
}
$aes->setPreferredEngine($engine);
$ciphertext2 = $aes->encrypt($plaintext);
$this->assertEquals($ciphertext, $ciphertext2);
$this->assertEquals($tag, $aes->getTag());
$aes->setTag($tag);
$this->assertEquals($plaintext, $aes->decrypt($ciphertext));
}
/**
* Produces all combinations of test values.
*
* @return array
*/
public function engine256Vectors()
{
$engines = [
'PHP',
'Eval',
'mcrypt',
'OpenSSL',
'OpenSSL (GCM)',
'libsodium'
];
$p1 = '00000000000000000000000000000000';
$p2 = 'd9313225f88406e5a55909c5aff5269a' .
'86a7a9531534f7da2e4c303d8a318a72' .
'1c3c0c95956809532fcf0e2449a6b525' .
'b16aedf5aa0de657ba637b391aafd255';
$p3 = 'd9313225f88406e5a55909c5aff5269a' .
'86a7a9531534f7da2e4c303d8a318a72' .
'1c3c0c95956809532fcf0e2449a6b525' .
'b16aedf5aa0de657ba637b39';
$n1 = '000000000000000000000000';
$n2 = 'cafebabefacedbaddecaf888';
$n3 = 'cafebabefacedbad';
$n4 = '9313225df88406e555909c5aff5269aa' .
'6a7a9538534f7da1e4c303d2a318a728' .
'c3c0c95156809539fcf0e2429a6b5254' .
'16aedbf5a0de6a57a637b39b';
$k1 = '00000000000000000000000000000000' .
'00000000000000000000000000000000';
$k2 = 'feffe9928665731c6d6a8f9467308308' .
'feffe9928665731c6d6a8f9467308308';
$c1 = 'cea7403d4d606b6e074ec5d3baf39d18';
$c2 = '522dc1f099567d07f47f37a32a84427d' .
'643a8cdcbfe5c0c97598a2bd2555d1aa' .
'8cb08e48590dbb3da7b08b1056828838' .
'c5f61e6393ba7a0abcc9f662898015ad';
$c3 = '522dc1f099567d07f47f37a32a84427d' .
'643a8cdcbfe5c0c97598a2bd2555d1aa' .
'8cb08e48590dbb3da7b08b1056828838' .
'c5f61e6393ba7a0abcc9f662';
$c4 = 'c3762df1ca787d32ae47c13bf19844cb' .
'af1ae14d0b976afac52ff7d79bba9de0' .
'feb582d33934a4f0954cc2363bc73f78' .
'62ac430e64abe499f47c9b1f';
$c5 = '5a8def2f0c9e53f1f75d7853659e2a20' .
'eeb2b22aafde6419a058ab4f6f746bf4' .
'0fc0c3b780f244452da3ebf1c5d82cde' .
'a2418997200ef82e44ae7e3f';
$a1 = 'feedfacedeadbeeffeedfacedeadbeef' .
'abaddad2';
$subvectors = [
// key, plaintext, nonce, aad, ciphertext, tag
// Test Case 13
[$k1, '', $n1, '', '', '530f8afbc74536b9a963b4f1c4cb738b'],
// Test Case 14
[$k1, $p1, $n1, '', $c1, 'd0d1c8a799996bf0265b98b5d48ab919'],
// Test Case 15
[$k2, $p2, $n2, '', $c2, 'b094dac5d93471bdec1a502270e3cc6c'],
// Test Case 16
[$k2, $p3, $n2, $a1, $c3, '76fc6ece0f4e1768cddf8853bb2d551b'],
// Test Case 17
[$k2, $p3, $n3, $a1, $c4, '3a337dbf46a792c45e454913fe2ea8f2'],
// Test Case 18
[$k2, $p3, $n4, $a1, $c5, 'a44a8266ee1c8eb0c8b5d4cf5ae9f19a']
];
$vectors = [];
for ($i = 0; $i < count($subvectors); $i++) {
for ($j = 0; $j < count($subvectors[$i]); $j++) {
$subvectors[$i][$j] = pack('H*', $subvectors[$i][$j]);
}
foreach ($engines as $engine) {
$temp = $subvectors[$i];
array_unshift($temp, $engine);
$vectors[] = $temp;
}
}
return $vectors;
}
/**
* @dataProvider engine256Vectors
*/
public function test256Vectors($engine, $key, $plaintext, $nonce, $aad, $ciphertext, $tag)
{
$aes = new AES('gcm');
$aes->setKey($key);
$aes->setNonce($nonce);
$aes->setAAD($aad);
if (!$aes->isValidEngine($engine)) {
self::markTestSkipped("Unable to initialize $engine engine");
}
$aes->setPreferredEngine($engine);
$ciphertext2 = $aes->encrypt($plaintext);
$this->assertEquals($ciphertext, $ciphertext2);
$this->assertEquals($tag, $aes->getTag());
$aes->setTag($tag);
$this->assertEquals($plaintext, $aes->decrypt($ciphertext));
}
}