mirror of
https://github.com/danog/phpseclib.git
synced 2024-11-30 04:39:21 +01:00
Merge pull request #1403 from terrafrost/hmac-additions
add new HMAC algorithms
This commit is contained in:
commit
45d787a578
@ -35,7 +35,10 @@ namespace phpseclib\Crypt;
|
||||
|
||||
use phpseclib\Math\BigInteger;
|
||||
use phpseclib\Exception\UnsupportedAlgorithmException;
|
||||
use phpseclib\Exception\InsufficientSetupException;
|
||||
use phpseclib\Common\Functions\Strings;
|
||||
use phpseclib\Crypt\AES;
|
||||
use phpseclib\Math\PrimeField;
|
||||
|
||||
/**
|
||||
* @package Hash
|
||||
@ -101,6 +104,15 @@ class Hash
|
||||
*/
|
||||
private $key = false;
|
||||
|
||||
/**
|
||||
* Nonce
|
||||
*
|
||||
* @see self::setNonce()
|
||||
* @var string
|
||||
* @access private
|
||||
*/
|
||||
private $nonce = false;
|
||||
|
||||
/**
|
||||
* Hash Parameters
|
||||
*
|
||||
@ -140,6 +152,51 @@ class Hash
|
||||
*/
|
||||
private $ipad;
|
||||
|
||||
/**
|
||||
* Recompute AES Key
|
||||
*
|
||||
* Used only for umac
|
||||
*
|
||||
* @see self::hash()
|
||||
* @var boolean
|
||||
* @access private
|
||||
*/
|
||||
private $recomputeAESKey;
|
||||
|
||||
/**
|
||||
* umac cipher object
|
||||
*
|
||||
* @see self::hash()
|
||||
* @var \phpseclib\Crypt\AES
|
||||
* @access private
|
||||
*/
|
||||
private $c;
|
||||
|
||||
/**
|
||||
* umac pad
|
||||
*
|
||||
* @see self::hash()
|
||||
* @var string
|
||||
* @access private
|
||||
*/
|
||||
private $pad;
|
||||
|
||||
/**#@+
|
||||
* UMAC variables
|
||||
*
|
||||
* @var PrimeField
|
||||
*/
|
||||
private static $factory36;
|
||||
private static $factory64;
|
||||
private static $factory128;
|
||||
private static $offset64;
|
||||
private static $offset128;
|
||||
private static $marker64;
|
||||
private static $marker128;
|
||||
private static $maxwordrange64;
|
||||
private static $maxwordrange128;
|
||||
/**#@-*/
|
||||
|
||||
/**
|
||||
* Default Constructor.
|
||||
*
|
||||
@ -163,6 +220,28 @@ class Hash
|
||||
{
|
||||
$this->key = $key;
|
||||
$this->computeKey();
|
||||
$this->recomputeAESKey = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the nonce for UMACs
|
||||
*
|
||||
* Keys can be of any length.
|
||||
*
|
||||
* @access public
|
||||
* @param string $nonce
|
||||
*/
|
||||
public function setNonce($nonce = false)
|
||||
{
|
||||
switch (true) {
|
||||
case !is_string($nonce):
|
||||
case strlen($nonce) > 0 && strlen($nonce) <= 16:
|
||||
$this->recomputeAESKey = true;
|
||||
$this->nonce = $nonce;
|
||||
return;
|
||||
}
|
||||
|
||||
throw new \LengthException('The nonce length must be between 1 and 16 bytes, inclusive');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -217,6 +296,14 @@ class Hash
|
||||
{
|
||||
$this->hashParam = $hash = strtolower($hash);
|
||||
switch ($hash) {
|
||||
case 'umac-32':
|
||||
case 'umac-64':
|
||||
case 'umac-96':
|
||||
case 'umac-128':
|
||||
$this->blockSize = 128;
|
||||
$this->length = abs(substr($hash, -3)) >> 3;
|
||||
$this->hash = 'umac';
|
||||
return;
|
||||
case 'md2-96':
|
||||
case 'md5-96':
|
||||
case 'sha1-96':
|
||||
@ -358,7 +445,339 @@ class Hash
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the HMAC.
|
||||
* KDF: Key-Derivation Function
|
||||
*
|
||||
* The key-derivation function generates pseudorandom bits used to key the hash functions.
|
||||
*
|
||||
* @param int $index a non-negative integer less than 2^64
|
||||
* @param int $numbytes a non-negative integer less than 2^64
|
||||
* @return string string of length numbytes bytes
|
||||
*/
|
||||
private function kdf($index, $numbytes)
|
||||
{
|
||||
$this->c->setIV(pack('N4', 0, $index, 0, 1));
|
||||
|
||||
return $this->c->encrypt(str_repeat("\0", $numbytes));
|
||||
}
|
||||
|
||||
/**
|
||||
* PDF Algorithm
|
||||
*
|
||||
* @return string string of length taglen bytes.
|
||||
*/
|
||||
private function pdf()
|
||||
{
|
||||
$k = $this->key;
|
||||
$nonce = $this->nonce;
|
||||
$taglen = $this->length;
|
||||
|
||||
//
|
||||
// Extract and zero low bit(s) of Nonce if needed
|
||||
//
|
||||
if ($taglen <= 8) {
|
||||
$last = strlen($nonce) - 1;
|
||||
$mask = $taglen == 4 ? "\3" : "\1";
|
||||
$index = $nonce[$last] & $mask;
|
||||
$nonce[$last] = $nonce[$last] ^ $index;
|
||||
}
|
||||
|
||||
//
|
||||
// Make Nonce BLOCKLEN bytes by appending zeroes if needed
|
||||
//
|
||||
$nonce = str_pad($nonce, 16, "\0");
|
||||
|
||||
//
|
||||
// Generate subkey, encipher and extract indexed substring
|
||||
//
|
||||
$kp = $this->kdf(0, 16);
|
||||
$c = new AES('ctr');
|
||||
$c->disablePadding();
|
||||
$c->setKey($kp);
|
||||
$c->setIV($nonce);
|
||||
$t = $c->encrypt("\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0");
|
||||
|
||||
// we could use ord() but per https://paragonie.com/blog/2016/06/constant-time-encoding-boring-cryptography-rfc-4648-and-you
|
||||
// unpack() doesn't leak timing info
|
||||
return $taglen <= 8 ?
|
||||
substr($t, unpack('C', $index)[1] * $taglen, $taglen) :
|
||||
substr($t, 0, $taglen);
|
||||
}
|
||||
|
||||
/**
|
||||
* UHASH Algorithm
|
||||
*
|
||||
* @param string $m string of length less than 2^67 bits.
|
||||
* @param int $taglen the integer 4, 8, 12 or 16.
|
||||
* @return string string of length taglen bytes.
|
||||
*/
|
||||
private function uhash($m, $taglen)
|
||||
{
|
||||
//
|
||||
// One internal iteration per 4 bytes of output
|
||||
//
|
||||
$iters = $taglen >> 2;
|
||||
|
||||
//
|
||||
// Define total key needed for all iterations using KDF.
|
||||
// L1Key reuses most key material between iterations.
|
||||
//
|
||||
//$L1Key = $this->kdf(1, 1024 + ($iters - 1) * 16);
|
||||
$L1Key = $this->kdf(1, (1024 + ($iters - 1)) * 16);
|
||||
$L2Key = $this->kdf(2, $iters * 24);
|
||||
$L3Key1 = $this->kdf(3, $iters * 64);
|
||||
$L3Key2 = $this->kdf(4, $iters * 4);
|
||||
|
||||
//
|
||||
// For each iteration, extract key and do three-layer hash.
|
||||
// If bytelength(M) <= 1024, then skip L2-HASH.
|
||||
//
|
||||
$y = '';
|
||||
for ($i = 0; $i < $iters; $i++) {
|
||||
$L1Key_i = substr($L1Key, $i * 16, 1024);
|
||||
$L2Key_i = substr($L2Key, $i * 24, 24);
|
||||
$L3Key1_i = substr($L3Key1, $i * 64, 64);
|
||||
$L3Key2_i = substr($L3Key2, $i * 4, 4);
|
||||
|
||||
$a = self::L1Hash($L1Key_i, $m);
|
||||
$b = strlen($m) <= 1024 ? "\0\0\0\0\0\0\0\0$a" : self::L2Hash($L2Key_i, $a);
|
||||
$c = self::L3Hash($L3Key1_i, $L3Key2_i, $b);
|
||||
$y.= $c;
|
||||
}
|
||||
|
||||
return $y;
|
||||
}
|
||||
|
||||
/**
|
||||
* L1-HASH Algorithm
|
||||
*
|
||||
* The first-layer hash breaks the message into 1024-byte chunks and
|
||||
* hashes each with a function called NH. Concatenating the results
|
||||
* forms a string, which is up to 128 times shorter than the original.
|
||||
*
|
||||
* @param string $k string of length 1024 bytes.
|
||||
* @param string $m string of length less than 2^67 bits.
|
||||
* @return string string of length (8 * ceil(bitlength(M)/8192)) bytes.
|
||||
*/
|
||||
private static function L1Hash($k, $m)
|
||||
{
|
||||
//
|
||||
// Break M into 1024 byte chunks (final chunk may be shorter)
|
||||
//
|
||||
$m = str_split($m, 1024);
|
||||
|
||||
//
|
||||
// For each chunk, except the last: endian-adjust, NH hash
|
||||
// and add bit-length. Use results to build Y.
|
||||
//
|
||||
$length = new BigInteger(1024 * 8);
|
||||
$y = '';
|
||||
for ($i = 0; $i < count($m) - 1; $i++) {
|
||||
$m[$i] = pack('N*', ...unpack('V*', $m[$i])); // ENDIAN-SWAP
|
||||
$y.= static::nh($k, $m[$i], $length);
|
||||
}
|
||||
|
||||
//
|
||||
// For the last chunk: pad to 32-byte boundary, endian-adjust,
|
||||
// NH hash and add bit-length. Concatenate the result to Y.
|
||||
//
|
||||
$length = strlen($m[$i]);
|
||||
$pad = 32 - ($length % 32);
|
||||
$pad = max(32, $length + $pad % 32);
|
||||
$m[$i] = str_pad($m[$i], $pad, "\0"); // zeropad
|
||||
$m[$i] = pack('N*', ...unpack('V*', $m[$i])); // ENDIAN-SWAP
|
||||
|
||||
$y.= static::nh($k, $m[$i], new BigInteger($length * 8));
|
||||
|
||||
return $y;
|
||||
}
|
||||
|
||||
/**
|
||||
* NH Algorithm
|
||||
*
|
||||
* @param string $k string of length 1024 bytes.
|
||||
* @param string $m string with length divisible by 32 bytes.
|
||||
* @return string string of length 8 bytes.
|
||||
*/
|
||||
private static function nh($k, $m, $length)
|
||||
{
|
||||
$toUInt32 = function($x) {
|
||||
$x = new BigInteger($x, 256);
|
||||
$x->setPrecision(32);
|
||||
return $x;
|
||||
};
|
||||
|
||||
//
|
||||
// Break M and K into 4-byte chunks
|
||||
//
|
||||
//$t = strlen($m) >> 2;
|
||||
$m = str_split($m, 4);
|
||||
$t = count($m);
|
||||
$k = str_split($k, 4);
|
||||
$k = array_pad(array_slice($k, 0, $t), $t, 0);
|
||||
|
||||
$m = array_map($toUInt32, $m);
|
||||
$k = array_map($toUInt32, $k);
|
||||
|
||||
//
|
||||
// Perform NH hash on the chunks, pairing words for multiplication
|
||||
// which are 4 apart to accommodate vector-parallelism.
|
||||
//
|
||||
$y = new BigInteger;
|
||||
$y->setPrecision(64);
|
||||
$i = 0;
|
||||
while ($i < $t) {
|
||||
$temp = $m[$i]->add($k[$i]);
|
||||
$temp->setPrecision(64);
|
||||
$temp = $temp->multiply($m[$i + 4]->add($k[$i + 4]));
|
||||
$y = $y->add($temp);
|
||||
|
||||
$temp = $m[$i + 1]->add($k[$i + 1]);
|
||||
$temp->setPrecision(64);
|
||||
$temp = $temp->multiply($m[$i + 5]->add($k[$i + 5]));
|
||||
$y = $y->add($temp);
|
||||
|
||||
$temp = $m[$i + 2]->add($k[$i + 2]);
|
||||
$temp->setPrecision(64);
|
||||
$temp = $temp->multiply($m[$i + 6]->add($k[$i + 6]));
|
||||
$y = $y->add($temp);
|
||||
|
||||
$temp = $m[$i + 3]->add($k[$i + 3]);
|
||||
$temp->setPrecision(64);
|
||||
$temp = $temp->multiply($m[$i + 7]->add($k[$i + 7]));
|
||||
$y = $y->add($temp);
|
||||
|
||||
$i+= 8;
|
||||
}
|
||||
|
||||
return $y->add($length)->toBytes();
|
||||
}
|
||||
|
||||
/**
|
||||
* L2-HASH: Second-Layer Hash
|
||||
*
|
||||
* The second-layer rehashes the L1-HASH output using a polynomial hash
|
||||
* called POLY. If the L1-HASH output is long, then POLY is called once
|
||||
* on a prefix of the L1-HASH output and called using different settings
|
||||
* on the remainder. (This two-step hashing of the L1-HASH output is
|
||||
* needed only if the message length is greater than 16 megabytes.)
|
||||
* Careful implementation of POLY is necessary to avoid a possible
|
||||
* timing attack (see Section 6.6 for more information).
|
||||
*
|
||||
* @param string $k string of length 24 bytes.
|
||||
* @param string $m string of length less than 2^64 bytes.
|
||||
* @return string string of length 16 bytes.
|
||||
*/
|
||||
private static function L2Hash($k, $m)
|
||||
{
|
||||
//
|
||||
// Extract keys and restrict to special key-sets
|
||||
//
|
||||
$k64 = $k & "\x01\xFF\xFF\xFF\x01\xFF\xFF\xFF";
|
||||
$k64 = new BigInteger($k64, 256);
|
||||
$k128 = substr($k, 8) & "\x01\xFF\xFF\xFF\x01\xFF\xFF\xFF\x01\xFF\xFF\xFF\x01\xFF\xFF\xFF";
|
||||
$k128 = new BigInteger($k128, 256);
|
||||
|
||||
//
|
||||
// If M is no more than 2^17 bytes, hash under 64-bit prime,
|
||||
// otherwise, hash first 2^17 bytes under 64-bit prime and
|
||||
// remainder under 128-bit prime.
|
||||
//
|
||||
if (strlen($m) <= 0x20000) { // 2^14 64-bit words
|
||||
$y = self::poly(64, self::$maxwordrange64, $k64, $m);
|
||||
} else {
|
||||
$m_1 = substr($m, 0, 0x20000); // 1 << 17
|
||||
$m_2 = substr($m, 0x20000) . "\x80";
|
||||
$length = strlen($m_2);
|
||||
$pad = 16 - ($length % 16);
|
||||
$pad%= 16;
|
||||
$m_2 = str_pad($m_2, $length + $pad, "\0"); // zeropad
|
||||
$y = self::poly(64, self::$maxwordrange64, $k64, $m_1);
|
||||
$y = str_pad($y, 16, "\0", STR_PAD_LEFT);
|
||||
$y = self::poly(128, self::$maxwordrange128, $k128, $y . $m_2);
|
||||
}
|
||||
|
||||
return str_pad($y, 16, "\0", STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
/**
|
||||
* POLY Algorithm
|
||||
*
|
||||
* @param int $wordbits the integer 64 or 128.
|
||||
* @param BigInteger $maxwordrange positive integer less than 2^wordbits.
|
||||
* @param BigInteger $k integer in the range 0 ... prime(wordbits) - 1.
|
||||
* @param string $m string with length divisible by (wordbits / 8) bytes.
|
||||
* @return integer in the range 0 ... prime(wordbits) - 1.
|
||||
*/
|
||||
private static function poly($wordbits, $maxwordrange, $k, $m)
|
||||
{
|
||||
//
|
||||
// Define constants used for fixing out-of-range words
|
||||
//
|
||||
$wordbytes = $wordbits >> 3;
|
||||
if ($wordbits == 128) {
|
||||
$factory = self::$factory128;
|
||||
$offset = self::$offset128;
|
||||
$marker = self::$marker128;
|
||||
} else {
|
||||
$factory = self::$factory64;
|
||||
$offset = self::$offset64;
|
||||
$marker = self::$marker64;
|
||||
}
|
||||
|
||||
$k = $factory->newInteger($k);
|
||||
|
||||
//
|
||||
// Break M into chunks of length wordbytes bytes
|
||||
//
|
||||
$m_i = str_split($m, $wordbytes);
|
||||
|
||||
//
|
||||
// Each input word m is compared with maxwordrange. If not smaller
|
||||
// then 'marker' and (m - offset), both in range, are hashed.
|
||||
//
|
||||
$y = $factory->newInteger(new BigInteger(1));
|
||||
foreach ($m_i as $m) {
|
||||
$m = $factory->newInteger(new BigInteger($m, 256));
|
||||
if ($m->compare($maxwordrange) >= 0) {
|
||||
$y = $k->multiply($y)->add($marker);
|
||||
$y = $k->multiply($y)->add($m->subtract($offset));
|
||||
} else {
|
||||
$y = $k->multiply($y)->add($m);
|
||||
}
|
||||
}
|
||||
|
||||
return $y->toBytes();
|
||||
}
|
||||
|
||||
/**
|
||||
* L3-HASH: Third-Layer Hash
|
||||
*
|
||||
* The output from L2-HASH is 16 bytes long. This final hash function
|
||||
* hashes the 16-byte string to a fixed length of 4 bytes.
|
||||
*
|
||||
* @param string $k1 string of length 64 bytes.
|
||||
* @param string $k2 string of length 4 bytes.
|
||||
* @param string $m string of length 16 bytes.
|
||||
* @return string string of length 4 bytes.
|
||||
*/
|
||||
private static function L3Hash($k1, $k2, $m)
|
||||
{
|
||||
$factory = self::$factory36;
|
||||
|
||||
$y = $factory->newInteger(new BigInteger());
|
||||
for ($i = 0; $i < 8; $i++) {
|
||||
$m_i = $factory->newInteger(new BigInteger(substr($m, 2 * $i, 2), 256));
|
||||
$k_i = $factory->newInteger(new BigInteger(substr($k1, 8 * $i, 8), 256));
|
||||
$y = $y->add($m_i->multiply($k_i));
|
||||
}
|
||||
$y = str_pad(substr($y->toBytes(), -4), 4, "\0", STR_PAD_LEFT);
|
||||
$y = $y ^ $k2;
|
||||
|
||||
return $y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the Hash / HMAC / UMAC.
|
||||
*
|
||||
* @access public
|
||||
* @param string $text
|
||||
@ -366,6 +785,58 @@ class Hash
|
||||
*/
|
||||
public function hash($text)
|
||||
{
|
||||
if ($this->hash == 'umac') {
|
||||
if ($this->recomputeAESKey) {
|
||||
if (!is_string($this->nonce)) {
|
||||
throw new InsufficientSetupException('No nonce has been set');
|
||||
}
|
||||
if (!is_string($this->key)) {
|
||||
throw new InsufficientSetupException('No key has been set');
|
||||
}
|
||||
if (strlen($this->key) != 16) {
|
||||
throw new \LengthException('Key must be 16 bytes long');
|
||||
}
|
||||
|
||||
if (!isset(self::$maxwordrange64)) {
|
||||
$one = new BigInteger(1);
|
||||
|
||||
$prime36 = new BigInteger("\x00\x00\x00\x0F\xFF\xFF\xFF\xFB", 256);
|
||||
self::$factory36 = new PrimeField($prime36);
|
||||
|
||||
$prime64 = new BigInteger("\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC5", 256);
|
||||
self::$factory64 = new PrimeField($prime64);
|
||||
|
||||
$prime128 = new BigInteger("\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x61", 256);
|
||||
self::$factory128 = new PrimeField($prime128);
|
||||
|
||||
self::$offset64 = new BigInteger("\1\0\0\0\0\0\0\0\0", 256);
|
||||
self::$offset64 = self::$factory64->newInteger(self::$offset64->subtract($prime64));
|
||||
self::$offset128 = new BigInteger("\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 256);
|
||||
self::$offset128 = self::$factory128->newInteger(self::$offset128->subtract($prime128));
|
||||
|
||||
self::$marker64 = self::$factory64->newInteger($prime64->subtract($one));
|
||||
self::$marker128 = self::$factory128->newInteger($prime128->subtract($one));
|
||||
|
||||
$maxwordrange64 = $one->bitwise_leftShift(64)->subtract($one->bitwise_leftShift(32));
|
||||
self::$maxwordrange64 = self::$factory64->newInteger($maxwordrange64);
|
||||
|
||||
$maxwordrange128 = $one->bitwise_leftShift(128)->subtract($one->bitwise_leftShift(96));
|
||||
self::$maxwordrange128 = self::$factory128->newInteger($maxwordrange128);
|
||||
}
|
||||
|
||||
$this->c = new AES('ctr');
|
||||
$this->c->disablePadding();
|
||||
$this->c->setKey($this->key);
|
||||
|
||||
$this->pad = $this->pdf();
|
||||
|
||||
$this->recomputeAESKey = false;
|
||||
}
|
||||
|
||||
$hashedmessage = $this->uhash($text, $this->length);
|
||||
return $hashedmessage ^ $this->pad;
|
||||
}
|
||||
|
||||
if (is_array($this->hash)) {
|
||||
if (empty($this->key) || !is_string($this->key)) {
|
||||
return substr(call_user_func($this->hash, $text, ...array_values($this->parameters)), 0, $this->length);
|
||||
|
@ -1464,14 +1464,14 @@ class SSH2
|
||||
|
||||
// we don't initialize any crypto-objects, yet - we do that, later. for now, we need the lengths to make the
|
||||
// diffie-hellman key exchange as fast as possible
|
||||
$decrypt = $this->array_intersect_first($s2c_encryption_algorithms, $this->encryption_algorithms_server_to_client);
|
||||
$decrypt = self::array_intersect_first($s2c_encryption_algorithms, $this->encryption_algorithms_server_to_client);
|
||||
$decryptKeyLength = $this->encryption_algorithm_to_key_size($decrypt);
|
||||
if ($decryptKeyLength === null) {
|
||||
$this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
|
||||
throw new NoSupportedAlgorithmsException('No compatible server to client encryption algorithms found');
|
||||
}
|
||||
|
||||
$encrypt = $this->array_intersect_first($c2s_encryption_algorithms, $this->encryption_algorithms_client_to_server);
|
||||
$encrypt = self::array_intersect_first($c2s_encryption_algorithms, $this->encryption_algorithms_client_to_server);
|
||||
$encryptKeyLength = $this->encryption_algorithm_to_key_size($encrypt);
|
||||
if ($encryptKeyLength === null) {
|
||||
$this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
|
||||
@ -1479,7 +1479,7 @@ class SSH2
|
||||
}
|
||||
|
||||
// through diffie-hellman key exchange a symmetric key is obtained
|
||||
$this->kex_algorithm = $this->array_intersect_first($kex_algorithms, $this->kex_algorithms);
|
||||
$this->kex_algorithm = self::array_intersect_first($kex_algorithms, $this->kex_algorithms);
|
||||
if ($this->kex_algorithm === false) {
|
||||
$this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
|
||||
throw new NoSupportedAlgorithmsException('No compatible key exchange algorithms found');
|
||||
@ -1623,7 +1623,7 @@ class SSH2
|
||||
$this->session_id = $this->exchange_hash;
|
||||
}
|
||||
|
||||
$server_host_key_algorithm = $this->array_intersect_first($server_host_key_algorithms, $this->server_host_key_algorithms);
|
||||
$server_host_key_algorithm = self::array_intersect_first($server_host_key_algorithms, $this->server_host_key_algorithms);
|
||||
if ($server_host_key_algorithm === false) {
|
||||
$this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
|
||||
throw new NoSupportedAlgorithmsException('No compatible server host key algorithms found');
|
||||
@ -1768,39 +1768,19 @@ class SSH2
|
||||
$this->decrypt->decrypt(str_repeat("\0", 1536));
|
||||
}
|
||||
|
||||
$mac_algorithm = $this->array_intersect_first($c2s_mac_algorithms, $this->mac_algorithms_client_to_server);
|
||||
$mac_algorithm = self::array_intersect_first($c2s_mac_algorithms, $this->mac_algorithms_client_to_server);
|
||||
if ($mac_algorithm === false) {
|
||||
$this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
|
||||
throw new NoSupportedAlgorithmsException('No compatible client to server message authentication algorithms found');
|
||||
}
|
||||
|
||||
if ($this->encrypt->usesNonce()) {
|
||||
if (!$this->encrypt->usesNonce()) {
|
||||
list($this->hmac_create, $createKeyLength) = self::mac_algorithm_to_hash_instance($mac_algorithm);
|
||||
} else {
|
||||
$this->hmac_create = new \stdClass;
|
||||
$this->hmac_create->name = $mac_algorithm;
|
||||
$mac_algorithm = 'none';
|
||||
}
|
||||
|
||||
$createKeyLength = 0; // ie. $mac_algorithm == 'none'
|
||||
switch ($mac_algorithm) {
|
||||
case 'hmac-sha2-256':
|
||||
$this->hmac_create = new Hash('sha256');
|
||||
$createKeyLength = 32;
|
||||
break;
|
||||
case 'hmac-sha1':
|
||||
$this->hmac_create = new Hash('sha1');
|
||||
$createKeyLength = 20;
|
||||
break;
|
||||
case 'hmac-sha1-96':
|
||||
$this->hmac_create = new Hash('sha1-96');
|
||||
$createKeyLength = 20;
|
||||
break;
|
||||
case 'hmac-md5':
|
||||
$this->hmac_create = new Hash('md5');
|
||||
$createKeyLength = 16;
|
||||
break;
|
||||
case 'hmac-md5-96':
|
||||
$this->hmac_create = new Hash('md5-96');
|
||||
$createKeyLength = 16;
|
||||
//$mac_algorithm = 'none';
|
||||
$createKeyLength = 0;
|
||||
}
|
||||
|
||||
if ($this->hmac_create instanceof Hash) {
|
||||
@ -1810,47 +1790,24 @@ class SSH2
|
||||
}
|
||||
$this->hmac_create->setKey(substr($key, 0, $createKeyLength));
|
||||
$this->hmac_create->name = $mac_algorithm;
|
||||
$this->hmac_create->etm = preg_match('#-etm@openssh\.com$#', $mac_algorithm);
|
||||
}
|
||||
|
||||
$mac_algorithm = $this->array_intersect_first($s2c_mac_algorithms, $this->mac_algorithms_server_to_client);
|
||||
$mac_algorithm = self::array_intersect_first($s2c_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()) {
|
||||
if (!$this->decrypt->usesNonce()) {
|
||||
list($this->hmac_check, $checkKeyLength) = self::mac_algorithm_to_hash_instance($mac_algorithm);
|
||||
$this->hmac_size = $this->hmac_check->getLengthInBytes();
|
||||
} else {
|
||||
$this->hmac_check = new \stdClass;
|
||||
$this->hmac_check->name = $mac_algorithm;
|
||||
$mac_algorithm = 'none';
|
||||
}
|
||||
|
||||
//$mac_algorithm = 'none';
|
||||
$checkKeyLength = 0;
|
||||
$this->hmac_size = 0;
|
||||
switch ($mac_algorithm) {
|
||||
case 'hmac-sha2-256':
|
||||
$this->hmac_check = new Hash('sha256');
|
||||
$checkKeyLength = 32;
|
||||
$this->hmac_size = 32;
|
||||
break;
|
||||
case 'hmac-sha1':
|
||||
$this->hmac_check = new Hash('sha1');
|
||||
$checkKeyLength = 20;
|
||||
$this->hmac_size = 20;
|
||||
break;
|
||||
case 'hmac-sha1-96':
|
||||
$this->hmac_check = new Hash('sha1-96');
|
||||
$checkKeyLength = 20;
|
||||
$this->hmac_size = 12;
|
||||
break;
|
||||
case 'hmac-md5':
|
||||
$this->hmac_check = new Hash('md5');
|
||||
$checkKeyLength = 16;
|
||||
$this->hmac_size = 16;
|
||||
break;
|
||||
case 'hmac-md5-96':
|
||||
$this->hmac_check = new Hash('md5-96');
|
||||
$checkKeyLength = 16;
|
||||
$this->hmac_size = 12;
|
||||
}
|
||||
|
||||
if ($this->hmac_check instanceof Hash) {
|
||||
@ -1860,16 +1817,17 @@ class SSH2
|
||||
}
|
||||
$this->hmac_check->setKey(substr($key, 0, $checkKeyLength));
|
||||
$this->hmac_check->name = $mac_algorithm;
|
||||
$this->hmac_check->etm = preg_match('#-etm@openssh\.com$#', $mac_algorithm);
|
||||
}
|
||||
|
||||
$compression_algorithm = $this->array_intersect_first($s2c_compression_algorithms, $this->compression_algorithms_server_to_client);
|
||||
$compression_algorithm = self::array_intersect_first($s2c_compression_algorithms, $this->compression_algorithms_server_to_client);
|
||||
if ($compression_algorithm === false) {
|
||||
$this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
|
||||
throw new NoSupportedAlgorithmsException('No compatible server to client compression algorithms found');
|
||||
}
|
||||
$this->decompress = $compression_algorithm == 'zlib';
|
||||
|
||||
$compression_algorithm = $this->array_intersect_first($c2s_compression_algorithms, $this->compression_algorithms_client_to_server);
|
||||
$compression_algorithm = self::array_intersect_first($c2s_compression_algorithms, $this->compression_algorithms_client_to_server);
|
||||
if ($compression_algorithm === false) {
|
||||
$this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
|
||||
throw new NoSupportedAlgorithmsException('No compatible client to server compression algorithms found');
|
||||
@ -1928,10 +1886,10 @@ class SSH2
|
||||
|
||||
/**
|
||||
* Maps an encryption algorithm name to an instance of a subclass of
|
||||
* \phpseclib\Crypt\Base.
|
||||
* \phpseclib\Crypt\Common\SymmetricKey.
|
||||
*
|
||||
* @param string $algorithm Name of the encryption algorithm
|
||||
* @return mixed Instance of \phpseclib\Crypt\Base or null for unknown
|
||||
* @return mixed Instance of \phpseclib\Crypt\Common\SymmetricKey or null for unknown
|
||||
* @access private
|
||||
*/
|
||||
private static function encryption_algorithm_to_crypt_instance($algorithm)
|
||||
@ -1975,6 +1933,41 @@ class SSH2
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps an encryption algorithm name to an instance of a subclass of
|
||||
* \phpseclib\Crypt\Hash.
|
||||
*
|
||||
* @param string $algorithm Name of the encryption algorithm
|
||||
* @return mixed Instance of \phpseclib\Crypt\Hash or null for unknown
|
||||
* @access private
|
||||
*/
|
||||
private static function mac_algorithm_to_hash_instance($algorithm)
|
||||
{
|
||||
switch ($algorithm) {
|
||||
case 'umac-64@openssh.com':
|
||||
case 'umac-64-etm@openssh.com':
|
||||
return [new Hash('umac-64'), 16];
|
||||
case 'umac-128@openssh.com':
|
||||
case 'umac-128-etm@openssh.com':
|
||||
return [new Hash('umac-128'), 16];
|
||||
case 'hmac-sha2-512':
|
||||
case 'hmac-sha2-512-etm@openssh.com':
|
||||
return [new Hash('sha512'), 64];
|
||||
case 'hmac-sha2-256':
|
||||
case 'hmac-sha2-256-etm@openssh.com':
|
||||
return [new Hash('sha256'), 32];
|
||||
case 'hmac-sha1':
|
||||
case 'hmac-sha1-etm@openssh.com':
|
||||
return [new Hash('sha1'), 20];
|
||||
case 'hmac-sha1-96':
|
||||
return [new Hash('sha1-96'), 20];
|
||||
case 'hmac-md5':
|
||||
return [new Hash('md5'), 16];
|
||||
case 'hmac-md5-96':
|
||||
return [new Hash('md5-96'), 16];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Tests whether or not proposed algorithm has a potential for issues
|
||||
*
|
||||
@ -3198,7 +3191,19 @@ class SSH2
|
||||
$remaining_length = 0;
|
||||
break;
|
||||
default:
|
||||
if (!$this->hmac_check instanceof Hash || !$this->hmac_check->etm) {
|
||||
$raw = $this->decrypt->decrypt($raw);
|
||||
break;
|
||||
}
|
||||
extract(unpack('Npacket_length', $temp = Strings::shift($raw, 4)));
|
||||
/**
|
||||
* @var integer $packet_length
|
||||
*/
|
||||
$raw.= $this->read_remaining_bytes($packet_length - $this->decrypt_block_size + 4);
|
||||
$stop = microtime(true);
|
||||
$encrypted = $temp . $raw;
|
||||
$raw = $temp . $this->decrypt->decrypt($raw);
|
||||
$remaining_length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -3232,10 +3237,22 @@ class SSH2
|
||||
if ($hmac === false || strlen($hmac) != $this->hmac_size) {
|
||||
$this->bitmap = 0;
|
||||
throw new \RuntimeException('Error reading socket');
|
||||
} elseif ($hmac != $this->hmac_check->hash(pack('NNCa*', $this->get_seq_no, $packet_length, $padding_length, $payload . $padding))) {
|
||||
}
|
||||
|
||||
$reconstructed = !$this->hmac_check->etm ?
|
||||
pack('NCa*', $packet_length, $padding_length, $payload . $padding) :
|
||||
$encrypted;
|
||||
if (($this->hmac_check->getHash() & "\xFF\xFF\xFF\xFF") == 'umac') {
|
||||
$this->hmac_check->setNonce("\0\0\0\0" . pack('N', $this->get_seq_no));
|
||||
if ($hmac != $this->hmac_check->hash($reconstructed)) {
|
||||
throw new \RuntimeException('Invalid UMAC');
|
||||
}
|
||||
} else {
|
||||
if ($hmac != $this->hmac_check->hash(pack('Na*', $this->get_seq_no, $reconstructed))) {
|
||||
throw new \RuntimeException('Invalid HMAC');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//if ($this->decompress) {
|
||||
// $payload = gzinflate(substr($payload, 2));
|
||||
@ -3271,10 +3288,11 @@ class SSH2
|
||||
|
||||
$adjustLength = false;
|
||||
if ($this->decrypt) {
|
||||
switch ($this->decrypt->name) {
|
||||
case 'aes128-gcm@openssh.com':
|
||||
case 'aes256-gcm@openssh.com':
|
||||
case 'chacha20-poly1305@openssh.com':
|
||||
switch (true) {
|
||||
case $this->decrypt->name == 'aes128-gcm@openssh.com':
|
||||
case $this->decrypt->name == 'aes256-gcm@openssh.com':
|
||||
case $this->decrypt->name == 'chacha20-poly1305@openssh.com':
|
||||
case $this->hmac_check instanceof Hash && $this->hmac_check->etm:
|
||||
$remaining_length+= $this->decrypt_block_size - 4;
|
||||
$adjustLength = true;
|
||||
}
|
||||
@ -3787,17 +3805,27 @@ class SSH2
|
||||
$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 && $this->encrypt->usesNonce()) {
|
||||
switch (true) {
|
||||
case $this->encrypt && $this->encrypt->usesNonce():
|
||||
case $this->hmac_create instanceof Hash && $this->hmac_create->etm:
|
||||
$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
|
||||
$packet = pack('NCa*', $packet_length - 4, $padding_length, $data . $padding);
|
||||
|
||||
$hmac = $this->hmac_create instanceof Hash ? $this->hmac_create->hash(pack('Na*', $this->send_seq_no, $packet)) : '';
|
||||
$this->send_seq_no++;
|
||||
$hmac = '';
|
||||
if ($this->hmac_create instanceof Hash && !$this->hmac_create->etm) {
|
||||
if (($this->hmac_create->getHash() & "\xFF\xFF\xFF\xFF") == 'umac') {
|
||||
$this->hmac_create->setNonce("\0\0\0\0" . pack('N', $this->send_seq_no));
|
||||
$hmac = $this->hmac_create->hash($packet);
|
||||
} else {
|
||||
$hmac = $this->hmac_create->hash(pack('Na*', $this->send_seq_no, $packet));
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->encrypt) {
|
||||
switch ($this->encrypt->name) {
|
||||
@ -3812,7 +3840,7 @@ class SSH2
|
||||
$packet = $temp . $this->encrypt->encrypt(substr($packet, 4));
|
||||
break;
|
||||
case 'chacha20-poly1305@openssh.com':
|
||||
$nonce = pack('N2', 0, $this->send_seq_no - 1);
|
||||
$nonce = pack('N2', 0, $this->send_seq_no);
|
||||
|
||||
$this->encrypt->setNonce($nonce);
|
||||
$this->lengthEncrypt->setNonce($nonce);
|
||||
@ -3831,10 +3859,23 @@ class SSH2
|
||||
$packet = $length . $this->encrypt->encrypt(substr($packet, 4));
|
||||
break;
|
||||
default:
|
||||
$packet = $this->encrypt->encrypt($packet);
|
||||
$packet = $this->hmac_create instanceof Hash && $this->hmac_create->etm ?
|
||||
($packet & "\xFF\xFF\xFF\xFF") . $this->encrypt->encrypt(substr($packet, 4)) :
|
||||
$this->encrypt->encrypt($packet);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->hmac_create instanceof Hash && $this->hmac_create->etm) {
|
||||
if (($this->hmac_create->getHash() & "\xFF\xFF\xFF\xFF") == 'umac') {
|
||||
$this->hmac_create->setNonce("\0\0\0\0" . pack('N', $this->send_seq_no));
|
||||
$hmac = $this->hmac_create->hash($packet);
|
||||
} else {
|
||||
$hmac = $this->hmac_create->hash(pack('Na*', $this->send_seq_no, $packet));
|
||||
}
|
||||
}
|
||||
|
||||
$this->send_seq_no++;
|
||||
|
||||
$packet.= $this->encrypt && $this->encrypt->usesNonce() ? $this->encrypt->getTag() : $hmac;
|
||||
|
||||
$start = microtime(true);
|
||||
@ -4155,7 +4196,7 @@ class SSH2
|
||||
* @return mixed False if intersection is empty, else intersected value.
|
||||
* @access private
|
||||
*/
|
||||
private function array_intersect_first($array1, $array2)
|
||||
private static function array_intersect_first($array1, $array2)
|
||||
{
|
||||
foreach ($array1 as $value) {
|
||||
if (in_array($value, $array2)) {
|
||||
@ -4401,8 +4442,19 @@ class SSH2
|
||||
public static function getSupportedMACAlgorithms()
|
||||
{
|
||||
return [
|
||||
'hmac-sha2-256-etm@openssh.com',
|
||||
'hmac-sha2-512-etm@openssh.com',
|
||||
'umac-64-etm@openssh.com',
|
||||
'umac-128-etm@openssh.com',
|
||||
'hmac-sha1-etm@openssh.com',
|
||||
|
||||
// from <http://www.ietf.org/rfc/rfc6668.txt>:
|
||||
'hmac-sha2-256',// RECOMMENDED HMAC-SHA256 (digest length = key length = 32)
|
||||
'hmac-sha2-512',// OPTIONAL HMAC-SHA512 (digest length = key length = 64)
|
||||
|
||||
// from <https://tools.ietf.org/html/draft-miller-secsh-umac-01>:
|
||||
'umac-64@openssh.com',
|
||||
'umac-128@openssh.com',
|
||||
|
||||
'hmac-sha1-96', // RECOMMENDED first 96 bits of HMAC-SHA1 (digest length = 12, key length = 20)
|
||||
'hmac-sha1', // REQUIRED HMAC-SHA1 (digest length = key length = 20)
|
||||
|
@ -419,4 +419,49 @@ class Unit_Crypt_HashTest extends PhpseclibTestCase
|
||||
['sha512', 64],
|
||||
];
|
||||
}
|
||||
|
||||
public function UMACs()
|
||||
{
|
||||
return [
|
||||
['', 'umac-32', '113145FB', "umac-32 and message of <empty>"],
|
||||
['', 'umac-64', '6E155FAD26900BE1', "umac-64 and message of <empty>"],
|
||||
['', 'umac-96', '32FEDB100C79AD58F07FF764', "umac-96 and message of <empty>"],
|
||||
['aaa', 'umac-32', '3B91D102', "umac-32 and message of 'a' * 3"],
|
||||
['aaa', 'umac-64', '44B5CB542F220104', "umac-64 and message of 'a' * 3"],
|
||||
['aaa', 'umac-96', '185E4FE905CBA7BD85E4C2DC', "umac-96 and message of 'a' * 3"],
|
||||
[str_repeat('a', 1 << 10), 'umac-32', '599B350B', "umac-32 and message of 'a' * 2^10"],
|
||||
[str_repeat('a', 1 << 10), 'umac-64', '26BF2F5D60118BD9', "umac-64 and message of 'a' * 2^10"],
|
||||
[str_repeat('a', 1 << 10), 'umac-96', '7A54ABE04AF82D60FB298C3C', "umac-96 and message of 'a' * 2^10"],
|
||||
[str_repeat('a', 1 << 15), 'umac-32', '58DCF532', "umac-32 and message of 'a' * 2^15"],
|
||||
[str_repeat('a', 1 << 15), 'umac-64', '27F8EF643B0D118D', "umac-64 and message of 'a' * 2^15"],
|
||||
[str_repeat('a', 1 << 15), 'umac-96', '7B136BD911E4B734286EF2BE', "umac-96 and message of 'a' * 2^15"],
|
||||
//[str_repeat('a', 1 << 20), 'umac-32', 'DB6364D1', "umac-32 and message of 'a' * 2^20"],
|
||||
//[str_repeat('a', 1 << 20), 'umac-64', 'A4477E87E9F55853', "umac-64 and message of 'a' * 2^20"],
|
||||
//[str_repeat('a', 1 << 20), 'umac-96', 'F8ACFA3AC31CFEEA047F7B11', "umac-96 and message of 'a' * 2^20"],
|
||||
//[str_repeat('a', 1 << 25), 'umac-32', '5109A660', "umac-32 and message of 'a' * 2^25"],
|
||||
//[str_repeat('a', 1 << 25), 'umac-64', '2E2DBC36860A0A5F', "umac-64 and message of 'a' * 2^25"],
|
||||
//[str_repeat('a', 1 << 25), 'umac-96', '72C6388BACE3ACE6FBF062D9', "umac-96 and message of 'a' * 2^25"],
|
||||
['abc', 'umac-32', 'ABF3A3A0', "umac-32 and message of 'abc' * 1"],
|
||||
['abc', 'umac-64', 'D4D7B9F6BD4FBFCF', "umac-64 and message of 'abc' * 1"],
|
||||
['abc', 'umac-96', '883C3D4B97A61976FFCF2323', "umac-96 and message of 'abc' * 1"],
|
||||
[str_repeat('abc', 500), 'umac-32', 'ABEB3C8B', "umac-32 and message of 'abc' * 500"],
|
||||
[str_repeat('abc', 500), 'umac-64', 'D4CF26DDEFD5C01A', "umac-64 and message of 'abc' * 500"],
|
||||
[str_repeat('abc', 500), 'umac-96', '8824A260C53C66A36C9260A6', "umac-96 and message of 'abc' * 500"],
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider UMACs
|
||||
*/
|
||||
public function testUMACs($message, $algo, $tag, $error)
|
||||
{
|
||||
$k = 'abcdefghijklmnop'; // A 16-byte UMAC key
|
||||
$n = 'bcdefghi'; // An 8-byte nonce
|
||||
|
||||
$hash = new Hash($algo);
|
||||
$hash->setNonce($n);
|
||||
$hash->setKey($k);
|
||||
$this->assertSame($hash->hash($message), pack('H*', $tag), $error);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user