add support for Galois/Counter Mode (GCM)
This commit is contained in:
parent
a30cfff79c
commit
01c92a59f8
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue