mirror of
https://github.com/danog/tgseclib.git
synced 2024-11-27 04:34:45 +01:00
add Salsa20 / ChaCha20 stream ciphers
This commit is contained in:
parent
8ce392f218
commit
494d20efc8
783
phpseclib/Crypt/ChaCha20.php
Normal file
783
phpseclib/Crypt/ChaCha20.php
Normal file
@ -0,0 +1,783 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Pure-PHP implementation of ChaCha20.
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* @category Crypt
|
||||
* @package ChaCha20
|
||||
* @author Jim Wigginton <terrafrost@php.net>
|
||||
* @copyright 2019 Jim Wigginton
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @link http://phpseclib.sourceforge.net
|
||||
*/
|
||||
|
||||
namespace phpseclib\Crypt;
|
||||
|
||||
use phpseclib\Crypt\Common\StreamCipher;
|
||||
use phpseclib\Exception\InsufficientSetupException;
|
||||
use phpseclib\Exception\BadDecryptionException;
|
||||
|
||||
/**
|
||||
* Pure-PHP implementation of ChaCha20.
|
||||
*
|
||||
* @package ChaCha20
|
||||
* @author Jim Wigginton <terrafrost@php.net>
|
||||
* @access public
|
||||
*/
|
||||
class ChaCha20 extends Salsa20
|
||||
{
|
||||
/**
|
||||
* The OpenSSL specific name of the cipher
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $cipher_name_openssl = 'chacha20';
|
||||
|
||||
/**
|
||||
* 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:
|
||||
// PHP 7.2.0 (30 Nov 2017) added support for libsodium
|
||||
|
||||
// we could probably make it so that if $this->counter == 0 then the first block would be done with either OpenSSL
|
||||
// or PHP and then subsequent blocks would then be done with libsodium but idk - it's not a high priority atm
|
||||
|
||||
// we could also make it so that if $this->counter == 0 and $this->continuousBuffer then do the first string
|
||||
// with libsodium and subsequent strings with openssl or pure-PHP but again not a high priority
|
||||
return function_exists('sodium_crypto_aead_chacha20poly1305_ietf_encrypt') &&
|
||||
$this->key_length == 32 &&
|
||||
(($this->usePoly1305 && !isset($this->poly1305Key) && $this->counter == 0) || $this->counter == 1) &&
|
||||
!$this->continuousBuffer;
|
||||
case self::ENGINE_OPENSSL:
|
||||
// OpenSSL 1.1.0 (released 25 Aug 2016) added support for chacha20.
|
||||
// PHP didn't support OpenSSL 1.1.0 until 7.0.19 (11 May 2017)
|
||||
|
||||
// if you attempt to provide openssl with a 128 bit key (as opposed to a 256 bit key) openssl will null
|
||||
// pad the key to 256 bits and still use the expansion constant for 256-bit keys. the fact that
|
||||
// openssl treats the IV as both the counter and nonce, however, let's us use openssl in continuous mode
|
||||
// whereas libsodium does not
|
||||
if ($this->key_length != 32) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return parent::isValidEngineHelper($engine);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts a message.
|
||||
*
|
||||
* @see \phpseclib\Crypt\Common\SymmetricKey::decrypt()
|
||||
* @see self::crypt()
|
||||
* @param string $plaintext
|
||||
* @return string $ciphertext
|
||||
*/
|
||||
public function encrypt($plaintext)
|
||||
{
|
||||
$this->setup();
|
||||
|
||||
if ($this->engine == self::ENGINE_LIBSODIUM) {
|
||||
return $this->encrypt_with_libsodium($plaintext);
|
||||
}
|
||||
|
||||
return parent::encrypt($plaintext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts a message.
|
||||
*
|
||||
* $this->decrypt($this->encrypt($plaintext)) == $this->encrypt($this->encrypt($plaintext)).
|
||||
* At least if the continuous buffer is disabled.
|
||||
*
|
||||
* @see \phpseclib\Crypt\Common\SymmetricKey::encrypt()
|
||||
* @see self::crypt()
|
||||
* @param string $ciphertext
|
||||
* @return string $plaintext
|
||||
*/
|
||||
public function decrypt($ciphertext)
|
||||
{
|
||||
$this->setup();
|
||||
|
||||
if ($this->engine == self::ENGINE_LIBSODIUM) {
|
||||
return $this->decrypt_with_libsodium($ciphertext);
|
||||
}
|
||||
|
||||
return parent::decrypt($ciphertext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts a message with libsodium
|
||||
*
|
||||
* @see self::encrypt()
|
||||
* @param string $plaintext
|
||||
* @return string $text
|
||||
*/
|
||||
private function encrypt_with_libsodium($plaintext)
|
||||
{
|
||||
$params = [$plaintext, $this->aad, $this->nonce, $this->key];
|
||||
$ciphertext = strlen($this->nonce) == 8 ?
|
||||
sodium_crypto_aead_chacha20poly1305_encrypt(...$params) :
|
||||
sodium_crypto_aead_chacha20poly1305_ietf_encrypt(...$params);
|
||||
if (!$this->usePoly1305) {
|
||||
return substr($ciphertext, 0, strlen($plaintext));
|
||||
}
|
||||
|
||||
$newciphertext = substr($ciphertext, 0, strlen($plaintext));
|
||||
|
||||
$this->newtag = $this->usingGeneratedPoly1305Key && strlen($this->nonce) == 12 ?
|
||||
substr($ciphertext, strlen($plaintext)) :
|
||||
$this->poly1305($newciphertext);
|
||||
|
||||
return $newciphertext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts a message with libsodium
|
||||
*
|
||||
* @see self::decrypt()
|
||||
* @param string $ciphertext
|
||||
* @return string $text
|
||||
*/
|
||||
private function decrypt_with_libsodium($ciphertext)
|
||||
{
|
||||
$params = [$ciphertext, $this->aad, $this->nonce, $this->key];
|
||||
|
||||
if (isset($this->poly1305Key)) {
|
||||
if ($this->oldtag === false) {
|
||||
throw new InsufficientSetupException('Authentication Tag has not been set');
|
||||
}
|
||||
if ($this->usingGeneratedPoly1305Key && strlen($this->nonce) == 12) {
|
||||
$plaintext = sodium_crypto_aead_chacha20poly1305_ietf_decrypt(...$params);
|
||||
$this->oldtag = false;
|
||||
if ($plaintext === false) {
|
||||
throw new BadDecryptionException('Derived authentication tag and supplied authentication tag do not match');
|
||||
}
|
||||
return $plaintext;
|
||||
}
|
||||
$newtag = $this->poly1305($ciphertext);
|
||||
if ($this->oldtag != substr($newtag, 0, strlen($this->oldtag))) {
|
||||
$this->oldtag = false;
|
||||
throw new BadDecryptionException('Derived authentication tag and supplied authentication tag do not match');
|
||||
}
|
||||
$this->oldtag = false;
|
||||
}
|
||||
|
||||
$plaintext = strlen($this->nonce) == 8 ?
|
||||
sodium_crypto_aead_chacha20poly1305_encrypt(...$params) :
|
||||
sodium_crypto_aead_chacha20poly1305_ietf_encrypt(...$params);
|
||||
|
||||
return substr($plaintext, 0, strlen($ciphertext));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the nonce.
|
||||
*
|
||||
* @param string $nonce
|
||||
*/
|
||||
public function setNonce($nonce)
|
||||
{
|
||||
if (!is_string($nonce)) {
|
||||
throw new \UnexpectedValueException('The nonce should be a string');
|
||||
}
|
||||
|
||||
/*
|
||||
from https://tools.ietf.org/html/rfc7539#page-7
|
||||
|
||||
"Note also that the original ChaCha had a 64-bit nonce and 64-bit
|
||||
block count. We have modified this here to be more consistent with
|
||||
recommendations in Section 3.2 of [RFC5116]."
|
||||
*/
|
||||
switch (strlen($nonce)) {
|
||||
case 8: // 64 bits
|
||||
case 12: // 96 bits
|
||||
break;
|
||||
default:
|
||||
throw new \LengthException('Nonce of size ' . strlen($nonce) . ' not supported by this algorithm. Only 64-bit nonces or 96-bit nonces are supported');
|
||||
}
|
||||
|
||||
$this->nonce = $nonce;
|
||||
$this->changed = true;
|
||||
$this->setEngine();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the self::ENGINE_INTERNAL $engine
|
||||
*
|
||||
* (re)init, if necessary, the internal cipher $engine
|
||||
*
|
||||
* _setup() will be called each time if $changed === true
|
||||
* typically this happens when using one or more of following public methods:
|
||||
*
|
||||
* - setKey()
|
||||
*
|
||||
* - setNonce()
|
||||
*
|
||||
* - First run of encrypt() / decrypt() with no init-settings
|
||||
*
|
||||
* @see self::setKey()
|
||||
* @see self::setNonce()
|
||||
* @see self::disableContinuousBuffer()
|
||||
*/
|
||||
protected function setup()
|
||||
{
|
||||
if (!$this->changed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->enbuffer = $this->debuffer = ['ciphertext' => '', 'counter' => $this->counter];
|
||||
|
||||
$this->changed = false;
|
||||
|
||||
if ($this->nonce === false) {
|
||||
throw new InsufficientSetupException('No nonce has been defined');
|
||||
}
|
||||
|
||||
if ($this->key === false) {
|
||||
throw new InsufficientSetupException('No key has been defined');
|
||||
}
|
||||
|
||||
if ($this->usePoly1305 && !isset($this->poly1305Key)) {
|
||||
$this->usingGeneratedPoly1305Key = true;
|
||||
if ($this->engine == self::ENGINE_LIBSODIUM) {
|
||||
return;
|
||||
}
|
||||
$this->createPoly1305Key();
|
||||
}
|
||||
|
||||
$key = $this->key;
|
||||
if (strlen($key) == 16) {
|
||||
$constant = 'expand 16-byte k';
|
||||
$key.= $key;
|
||||
} else {
|
||||
$constant = 'expand 32-byte k';
|
||||
}
|
||||
|
||||
$this->p1 = $constant . $key;
|
||||
$this->p2 = $this->nonce;
|
||||
if (strlen($this->nonce) == 8) {
|
||||
$this->p2 = "\0\0\0\0" . $this->p2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The quarterround function
|
||||
*
|
||||
* @param int $a
|
||||
* @param int $b
|
||||
* @param int $c
|
||||
* @param int $d
|
||||
*/
|
||||
protected static function quarterRound(&$a, &$b, &$c, &$d)
|
||||
{
|
||||
$a+= $b; $d = self::leftRotate($d ^ $a, 16);
|
||||
$c+= $d; $b = self::leftRotate($b ^ $c, 12);
|
||||
$a+= $b; $d = self::leftRotate($d ^ $a, 8);
|
||||
$c+= $d; $b = self::leftRotate($b ^ $c, 7);
|
||||
}
|
||||
|
||||
/**
|
||||
* The doubleround function
|
||||
*
|
||||
* @param int $x0...$x16
|
||||
*/
|
||||
protected static function doubleRound(&$x0, &$x1, &$x2, &$x3, &$x4, &$x5, &$x6, &$x7, &$x8, &$x9, &$x10, &$x11, &$x12, &$x13, &$x14, &$x15)
|
||||
{
|
||||
// columnRound
|
||||
static::quarterRound($x0, $x4, $x8, $x12);
|
||||
static::quarterRound($x1, $x5, $x9, $x13);
|
||||
static::quarterRound($x2, $x6, $x10, $x14);
|
||||
static::quarterRound($x3, $x7, $x11, $x15);
|
||||
// rowRound
|
||||
static::quarterRound($x0, $x5, $x10, $x15);
|
||||
static::quarterRound($x1, $x6, $x11, $x12);
|
||||
static::quarterRound($x2, $x7, $x8, $x13);
|
||||
static::quarterRound($x3, $x4, $x9, $x14);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Salsa20 hash function function
|
||||
*
|
||||
* On my laptop this loop unrolled / function dereferenced version of parent::salsa20 encrypts 1mb of text in
|
||||
* 0.65s vs the 0.85s that it takes with the parent method.
|
||||
*
|
||||
* If we were free to assume that the host OS would always be 64-bits then the if condition in leftRotate could
|
||||
* be eliminated and we could knock this done to 0.60s.
|
||||
*
|
||||
* For comparison purposes, RC4 takes 0.16s and AES in CTR mode with the Eval engine takes 0.48s.
|
||||
* AES in CTR mode with the PHP engine takes 1.19s. Salsa20 / ChaCha20 do not benefit as much from the Eval
|
||||
* approach due to the fact that there are a lot less variables to de-reference, fewer loops to unroll, etc
|
||||
*
|
||||
* @param string $x
|
||||
*/
|
||||
protected static function salsa20($x)
|
||||
{
|
||||
list(, $x0, $x1, $x2, $x3, $x4, $x5, $x6, $x7, $x8, $x9, $x10, $x11, $x12, $x13, $x14, $x15) = unpack('V*', $x);
|
||||
$z0 = $x0;
|
||||
$z1 = $x1;
|
||||
$z2 = $x2;
|
||||
$z3 = $x3;
|
||||
$z4 = $x4;
|
||||
$z5 = $x5;
|
||||
$z6 = $x6;
|
||||
$z7 = $x7;
|
||||
$z8 = $x8;
|
||||
$z9 = $x9;
|
||||
$z10 = $x10;
|
||||
$z11 = $x11;
|
||||
$z12 = $x12;
|
||||
$z13 = $x13;
|
||||
$z14 = $x14;
|
||||
$z15 = $x15;
|
||||
|
||||
// columnRound
|
||||
$x0+= $x4; $x12 = self::leftRotate($x12 ^ $x0, 16);
|
||||
$x8+= $x12; $x4 = self::leftRotate($x4 ^ $x8, 12);
|
||||
$x0+= $x4; $x12 = self::leftRotate($x12 ^ $x0, 8);
|
||||
$x8+= $x12; $x4 = self::leftRotate($x4 ^ $x8, 7);
|
||||
|
||||
$x1+= $x5; $x13 = self::leftRotate($x13 ^ $x1, 16);
|
||||
$x9+= $x13; $x5 = self::leftRotate($x5 ^ $x9, 12);
|
||||
$x1+= $x5; $x13 = self::leftRotate($x13 ^ $x1, 8);
|
||||
$x9+= $x13; $x5 = self::leftRotate($x5 ^ $x9, 7);
|
||||
|
||||
$x2+= $x6; $x14 = self::leftRotate($x14 ^ $x2, 16);
|
||||
$x10+= $x14; $x6 = self::leftRotate($x6 ^ $x10, 12);
|
||||
$x2+= $x6; $x14 = self::leftRotate($x14 ^ $x2, 8);
|
||||
$x10+= $x14; $x6 = self::leftRotate($x6 ^ $x10, 7);
|
||||
|
||||
$x3+= $x7; $x15 = self::leftRotate($x15 ^ $x3, 16);
|
||||
$x11+= $x15; $x7 = self::leftRotate($x7 ^ $x11, 12);
|
||||
$x3+= $x7; $x15 = self::leftRotate($x15 ^ $x3, 8);
|
||||
$x11+= $x15; $x7 = self::leftRotate($x7 ^ $x11, 7);
|
||||
|
||||
// rowRound
|
||||
$x0+= $x5; $x15 = self::leftRotate($x15 ^ $x0, 16);
|
||||
$x10+= $x15; $x5 = self::leftRotate($x5 ^ $x10, 12);
|
||||
$x0+= $x5; $x15 = self::leftRotate($x15 ^ $x0, 8);
|
||||
$x10+= $x15; $x5 = self::leftRotate($x5 ^ $x10, 7);
|
||||
|
||||
$x1+= $x6; $x12 = self::leftRotate($x12 ^ $x1, 16);
|
||||
$x11+= $x12; $x6 = self::leftRotate($x6 ^ $x11, 12);
|
||||
$x1+= $x6; $x12 = self::leftRotate($x12 ^ $x1, 8);
|
||||
$x11+= $x12; $x6 = self::leftRotate($x6 ^ $x11, 7);
|
||||
|
||||
$x2+= $x7; $x13 = self::leftRotate($x13 ^ $x2, 16);
|
||||
$x8+= $x13; $x7 = self::leftRotate($x7 ^ $x8, 12);
|
||||
$x2+= $x7; $x13 = self::leftRotate($x13 ^ $x2, 8);
|
||||
$x8+= $x13; $x7 = self::leftRotate($x7 ^ $x8, 7);
|
||||
|
||||
$x3+= $x4; $x14 = self::leftRotate($x14 ^ $x3, 16);
|
||||
$x9+= $x14; $x4 = self::leftRotate($x4 ^ $x9, 12);
|
||||
$x3+= $x4; $x14 = self::leftRotate($x14 ^ $x3, 8);
|
||||
$x9+= $x14; $x4 = self::leftRotate($x4 ^ $x9, 7);
|
||||
|
||||
// columnRound
|
||||
$x0+= $x4; $x12 = self::leftRotate($x12 ^ $x0, 16);
|
||||
$x8+= $x12; $x4 = self::leftRotate($x4 ^ $x8, 12);
|
||||
$x0+= $x4; $x12 = self::leftRotate($x12 ^ $x0, 8);
|
||||
$x8+= $x12; $x4 = self::leftRotate($x4 ^ $x8, 7);
|
||||
|
||||
$x1+= $x5; $x13 = self::leftRotate($x13 ^ $x1, 16);
|
||||
$x9+= $x13; $x5 = self::leftRotate($x5 ^ $x9, 12);
|
||||
$x1+= $x5; $x13 = self::leftRotate($x13 ^ $x1, 8);
|
||||
$x9+= $x13; $x5 = self::leftRotate($x5 ^ $x9, 7);
|
||||
|
||||
$x2+= $x6; $x14 = self::leftRotate($x14 ^ $x2, 16);
|
||||
$x10+= $x14; $x6 = self::leftRotate($x6 ^ $x10, 12);
|
||||
$x2+= $x6; $x14 = self::leftRotate($x14 ^ $x2, 8);
|
||||
$x10+= $x14; $x6 = self::leftRotate($x6 ^ $x10, 7);
|
||||
|
||||
$x3+= $x7; $x15 = self::leftRotate($x15 ^ $x3, 16);
|
||||
$x11+= $x15; $x7 = self::leftRotate($x7 ^ $x11, 12);
|
||||
$x3+= $x7; $x15 = self::leftRotate($x15 ^ $x3, 8);
|
||||
$x11+= $x15; $x7 = self::leftRotate($x7 ^ $x11, 7);
|
||||
|
||||
// rowRound
|
||||
$x0+= $x5; $x15 = self::leftRotate($x15 ^ $x0, 16);
|
||||
$x10+= $x15; $x5 = self::leftRotate($x5 ^ $x10, 12);
|
||||
$x0+= $x5; $x15 = self::leftRotate($x15 ^ $x0, 8);
|
||||
$x10+= $x15; $x5 = self::leftRotate($x5 ^ $x10, 7);
|
||||
|
||||
$x1+= $x6; $x12 = self::leftRotate($x12 ^ $x1, 16);
|
||||
$x11+= $x12; $x6 = self::leftRotate($x6 ^ $x11, 12);
|
||||
$x1+= $x6; $x12 = self::leftRotate($x12 ^ $x1, 8);
|
||||
$x11+= $x12; $x6 = self::leftRotate($x6 ^ $x11, 7);
|
||||
|
||||
$x2+= $x7; $x13 = self::leftRotate($x13 ^ $x2, 16);
|
||||
$x8+= $x13; $x7 = self::leftRotate($x7 ^ $x8, 12);
|
||||
$x2+= $x7; $x13 = self::leftRotate($x13 ^ $x2, 8);
|
||||
$x8+= $x13; $x7 = self::leftRotate($x7 ^ $x8, 7);
|
||||
|
||||
$x3+= $x4; $x14 = self::leftRotate($x14 ^ $x3, 16);
|
||||
$x9+= $x14; $x4 = self::leftRotate($x4 ^ $x9, 12);
|
||||
$x3+= $x4; $x14 = self::leftRotate($x14 ^ $x3, 8);
|
||||
$x9+= $x14; $x4 = self::leftRotate($x4 ^ $x9, 7);
|
||||
|
||||
// columnRound
|
||||
$x0+= $x4; $x12 = self::leftRotate($x12 ^ $x0, 16);
|
||||
$x8+= $x12; $x4 = self::leftRotate($x4 ^ $x8, 12);
|
||||
$x0+= $x4; $x12 = self::leftRotate($x12 ^ $x0, 8);
|
||||
$x8+= $x12; $x4 = self::leftRotate($x4 ^ $x8, 7);
|
||||
|
||||
$x1+= $x5; $x13 = self::leftRotate($x13 ^ $x1, 16);
|
||||
$x9+= $x13; $x5 = self::leftRotate($x5 ^ $x9, 12);
|
||||
$x1+= $x5; $x13 = self::leftRotate($x13 ^ $x1, 8);
|
||||
$x9+= $x13; $x5 = self::leftRotate($x5 ^ $x9, 7);
|
||||
|
||||
$x2+= $x6; $x14 = self::leftRotate($x14 ^ $x2, 16);
|
||||
$x10+= $x14; $x6 = self::leftRotate($x6 ^ $x10, 12);
|
||||
$x2+= $x6; $x14 = self::leftRotate($x14 ^ $x2, 8);
|
||||
$x10+= $x14; $x6 = self::leftRotate($x6 ^ $x10, 7);
|
||||
|
||||
$x3+= $x7; $x15 = self::leftRotate($x15 ^ $x3, 16);
|
||||
$x11+= $x15; $x7 = self::leftRotate($x7 ^ $x11, 12);
|
||||
$x3+= $x7; $x15 = self::leftRotate($x15 ^ $x3, 8);
|
||||
$x11+= $x15; $x7 = self::leftRotate($x7 ^ $x11, 7);
|
||||
|
||||
// rowRound
|
||||
$x0+= $x5; $x15 = self::leftRotate($x15 ^ $x0, 16);
|
||||
$x10+= $x15; $x5 = self::leftRotate($x5 ^ $x10, 12);
|
||||
$x0+= $x5; $x15 = self::leftRotate($x15 ^ $x0, 8);
|
||||
$x10+= $x15; $x5 = self::leftRotate($x5 ^ $x10, 7);
|
||||
|
||||
$x1+= $x6; $x12 = self::leftRotate($x12 ^ $x1, 16);
|
||||
$x11+= $x12; $x6 = self::leftRotate($x6 ^ $x11, 12);
|
||||
$x1+= $x6; $x12 = self::leftRotate($x12 ^ $x1, 8);
|
||||
$x11+= $x12; $x6 = self::leftRotate($x6 ^ $x11, 7);
|
||||
|
||||
$x2+= $x7; $x13 = self::leftRotate($x13 ^ $x2, 16);
|
||||
$x8+= $x13; $x7 = self::leftRotate($x7 ^ $x8, 12);
|
||||
$x2+= $x7; $x13 = self::leftRotate($x13 ^ $x2, 8);
|
||||
$x8+= $x13; $x7 = self::leftRotate($x7 ^ $x8, 7);
|
||||
|
||||
$x3+= $x4; $x14 = self::leftRotate($x14 ^ $x3, 16);
|
||||
$x9+= $x14; $x4 = self::leftRotate($x4 ^ $x9, 12);
|
||||
$x3+= $x4; $x14 = self::leftRotate($x14 ^ $x3, 8);
|
||||
$x9+= $x14; $x4 = self::leftRotate($x4 ^ $x9, 7);
|
||||
|
||||
// columnRound
|
||||
$x0+= $x4; $x12 = self::leftRotate($x12 ^ $x0, 16);
|
||||
$x8+= $x12; $x4 = self::leftRotate($x4 ^ $x8, 12);
|
||||
$x0+= $x4; $x12 = self::leftRotate($x12 ^ $x0, 8);
|
||||
$x8+= $x12; $x4 = self::leftRotate($x4 ^ $x8, 7);
|
||||
|
||||
$x1+= $x5; $x13 = self::leftRotate($x13 ^ $x1, 16);
|
||||
$x9+= $x13; $x5 = self::leftRotate($x5 ^ $x9, 12);
|
||||
$x1+= $x5; $x13 = self::leftRotate($x13 ^ $x1, 8);
|
||||
$x9+= $x13; $x5 = self::leftRotate($x5 ^ $x9, 7);
|
||||
|
||||
$x2+= $x6; $x14 = self::leftRotate($x14 ^ $x2, 16);
|
||||
$x10+= $x14; $x6 = self::leftRotate($x6 ^ $x10, 12);
|
||||
$x2+= $x6; $x14 = self::leftRotate($x14 ^ $x2, 8);
|
||||
$x10+= $x14; $x6 = self::leftRotate($x6 ^ $x10, 7);
|
||||
|
||||
$x3+= $x7; $x15 = self::leftRotate($x15 ^ $x3, 16);
|
||||
$x11+= $x15; $x7 = self::leftRotate($x7 ^ $x11, 12);
|
||||
$x3+= $x7; $x15 = self::leftRotate($x15 ^ $x3, 8);
|
||||
$x11+= $x15; $x7 = self::leftRotate($x7 ^ $x11, 7);
|
||||
|
||||
// rowRound
|
||||
$x0+= $x5; $x15 = self::leftRotate($x15 ^ $x0, 16);
|
||||
$x10+= $x15; $x5 = self::leftRotate($x5 ^ $x10, 12);
|
||||
$x0+= $x5; $x15 = self::leftRotate($x15 ^ $x0, 8);
|
||||
$x10+= $x15; $x5 = self::leftRotate($x5 ^ $x10, 7);
|
||||
|
||||
$x1+= $x6; $x12 = self::leftRotate($x12 ^ $x1, 16);
|
||||
$x11+= $x12; $x6 = self::leftRotate($x6 ^ $x11, 12);
|
||||
$x1+= $x6; $x12 = self::leftRotate($x12 ^ $x1, 8);
|
||||
$x11+= $x12; $x6 = self::leftRotate($x6 ^ $x11, 7);
|
||||
|
||||
$x2+= $x7; $x13 = self::leftRotate($x13 ^ $x2, 16);
|
||||
$x8+= $x13; $x7 = self::leftRotate($x7 ^ $x8, 12);
|
||||
$x2+= $x7; $x13 = self::leftRotate($x13 ^ $x2, 8);
|
||||
$x8+= $x13; $x7 = self::leftRotate($x7 ^ $x8, 7);
|
||||
|
||||
$x3+= $x4; $x14 = self::leftRotate($x14 ^ $x3, 16);
|
||||
$x9+= $x14; $x4 = self::leftRotate($x4 ^ $x9, 12);
|
||||
$x3+= $x4; $x14 = self::leftRotate($x14 ^ $x3, 8);
|
||||
$x9+= $x14; $x4 = self::leftRotate($x4 ^ $x9, 7);
|
||||
|
||||
// columnRound
|
||||
$x0+= $x4; $x12 = self::leftRotate($x12 ^ $x0, 16);
|
||||
$x8+= $x12; $x4 = self::leftRotate($x4 ^ $x8, 12);
|
||||
$x0+= $x4; $x12 = self::leftRotate($x12 ^ $x0, 8);
|
||||
$x8+= $x12; $x4 = self::leftRotate($x4 ^ $x8, 7);
|
||||
|
||||
$x1+= $x5; $x13 = self::leftRotate($x13 ^ $x1, 16);
|
||||
$x9+= $x13; $x5 = self::leftRotate($x5 ^ $x9, 12);
|
||||
$x1+= $x5; $x13 = self::leftRotate($x13 ^ $x1, 8);
|
||||
$x9+= $x13; $x5 = self::leftRotate($x5 ^ $x9, 7);
|
||||
|
||||
$x2+= $x6; $x14 = self::leftRotate($x14 ^ $x2, 16);
|
||||
$x10+= $x14; $x6 = self::leftRotate($x6 ^ $x10, 12);
|
||||
$x2+= $x6; $x14 = self::leftRotate($x14 ^ $x2, 8);
|
||||
$x10+= $x14; $x6 = self::leftRotate($x6 ^ $x10, 7);
|
||||
|
||||
$x3+= $x7; $x15 = self::leftRotate($x15 ^ $x3, 16);
|
||||
$x11+= $x15; $x7 = self::leftRotate($x7 ^ $x11, 12);
|
||||
$x3+= $x7; $x15 = self::leftRotate($x15 ^ $x3, 8);
|
||||
$x11+= $x15; $x7 = self::leftRotate($x7 ^ $x11, 7);
|
||||
|
||||
// rowRound
|
||||
$x0+= $x5; $x15 = self::leftRotate($x15 ^ $x0, 16);
|
||||
$x10+= $x15; $x5 = self::leftRotate($x5 ^ $x10, 12);
|
||||
$x0+= $x5; $x15 = self::leftRotate($x15 ^ $x0, 8);
|
||||
$x10+= $x15; $x5 = self::leftRotate($x5 ^ $x10, 7);
|
||||
|
||||
$x1+= $x6; $x12 = self::leftRotate($x12 ^ $x1, 16);
|
||||
$x11+= $x12; $x6 = self::leftRotate($x6 ^ $x11, 12);
|
||||
$x1+= $x6; $x12 = self::leftRotate($x12 ^ $x1, 8);
|
||||
$x11+= $x12; $x6 = self::leftRotate($x6 ^ $x11, 7);
|
||||
|
||||
$x2+= $x7; $x13 = self::leftRotate($x13 ^ $x2, 16);
|
||||
$x8+= $x13; $x7 = self::leftRotate($x7 ^ $x8, 12);
|
||||
$x2+= $x7; $x13 = self::leftRotate($x13 ^ $x2, 8);
|
||||
$x8+= $x13; $x7 = self::leftRotate($x7 ^ $x8, 7);
|
||||
|
||||
$x3+= $x4; $x14 = self::leftRotate($x14 ^ $x3, 16);
|
||||
$x9+= $x14; $x4 = self::leftRotate($x4 ^ $x9, 12);
|
||||
$x3+= $x4; $x14 = self::leftRotate($x14 ^ $x3, 8);
|
||||
$x9+= $x14; $x4 = self::leftRotate($x4 ^ $x9, 7);
|
||||
|
||||
// columnRound
|
||||
$x0+= $x4; $x12 = self::leftRotate($x12 ^ $x0, 16);
|
||||
$x8+= $x12; $x4 = self::leftRotate($x4 ^ $x8, 12);
|
||||
$x0+= $x4; $x12 = self::leftRotate($x12 ^ $x0, 8);
|
||||
$x8+= $x12; $x4 = self::leftRotate($x4 ^ $x8, 7);
|
||||
|
||||
$x1+= $x5; $x13 = self::leftRotate($x13 ^ $x1, 16);
|
||||
$x9+= $x13; $x5 = self::leftRotate($x5 ^ $x9, 12);
|
||||
$x1+= $x5; $x13 = self::leftRotate($x13 ^ $x1, 8);
|
||||
$x9+= $x13; $x5 = self::leftRotate($x5 ^ $x9, 7);
|
||||
|
||||
$x2+= $x6; $x14 = self::leftRotate($x14 ^ $x2, 16);
|
||||
$x10+= $x14; $x6 = self::leftRotate($x6 ^ $x10, 12);
|
||||
$x2+= $x6; $x14 = self::leftRotate($x14 ^ $x2, 8);
|
||||
$x10+= $x14; $x6 = self::leftRotate($x6 ^ $x10, 7);
|
||||
|
||||
$x3+= $x7; $x15 = self::leftRotate($x15 ^ $x3, 16);
|
||||
$x11+= $x15; $x7 = self::leftRotate($x7 ^ $x11, 12);
|
||||
$x3+= $x7; $x15 = self::leftRotate($x15 ^ $x3, 8);
|
||||
$x11+= $x15; $x7 = self::leftRotate($x7 ^ $x11, 7);
|
||||
|
||||
// rowRound
|
||||
$x0+= $x5; $x15 = self::leftRotate($x15 ^ $x0, 16);
|
||||
$x10+= $x15; $x5 = self::leftRotate($x5 ^ $x10, 12);
|
||||
$x0+= $x5; $x15 = self::leftRotate($x15 ^ $x0, 8);
|
||||
$x10+= $x15; $x5 = self::leftRotate($x5 ^ $x10, 7);
|
||||
|
||||
$x1+= $x6; $x12 = self::leftRotate($x12 ^ $x1, 16);
|
||||
$x11+= $x12; $x6 = self::leftRotate($x6 ^ $x11, 12);
|
||||
$x1+= $x6; $x12 = self::leftRotate($x12 ^ $x1, 8);
|
||||
$x11+= $x12; $x6 = self::leftRotate($x6 ^ $x11, 7);
|
||||
|
||||
$x2+= $x7; $x13 = self::leftRotate($x13 ^ $x2, 16);
|
||||
$x8+= $x13; $x7 = self::leftRotate($x7 ^ $x8, 12);
|
||||
$x2+= $x7; $x13 = self::leftRotate($x13 ^ $x2, 8);
|
||||
$x8+= $x13; $x7 = self::leftRotate($x7 ^ $x8, 7);
|
||||
|
||||
$x3+= $x4; $x14 = self::leftRotate($x14 ^ $x3, 16);
|
||||
$x9+= $x14; $x4 = self::leftRotate($x4 ^ $x9, 12);
|
||||
$x3+= $x4; $x14 = self::leftRotate($x14 ^ $x3, 8);
|
||||
$x9+= $x14; $x4 = self::leftRotate($x4 ^ $x9, 7);
|
||||
|
||||
// columnRound
|
||||
$x0+= $x4; $x12 = self::leftRotate($x12 ^ $x0, 16);
|
||||
$x8+= $x12; $x4 = self::leftRotate($x4 ^ $x8, 12);
|
||||
$x0+= $x4; $x12 = self::leftRotate($x12 ^ $x0, 8);
|
||||
$x8+= $x12; $x4 = self::leftRotate($x4 ^ $x8, 7);
|
||||
|
||||
$x1+= $x5; $x13 = self::leftRotate($x13 ^ $x1, 16);
|
||||
$x9+= $x13; $x5 = self::leftRotate($x5 ^ $x9, 12);
|
||||
$x1+= $x5; $x13 = self::leftRotate($x13 ^ $x1, 8);
|
||||
$x9+= $x13; $x5 = self::leftRotate($x5 ^ $x9, 7);
|
||||
|
||||
$x2+= $x6; $x14 = self::leftRotate($x14 ^ $x2, 16);
|
||||
$x10+= $x14; $x6 = self::leftRotate($x6 ^ $x10, 12);
|
||||
$x2+= $x6; $x14 = self::leftRotate($x14 ^ $x2, 8);
|
||||
$x10+= $x14; $x6 = self::leftRotate($x6 ^ $x10, 7);
|
||||
|
||||
$x3+= $x7; $x15 = self::leftRotate($x15 ^ $x3, 16);
|
||||
$x11+= $x15; $x7 = self::leftRotate($x7 ^ $x11, 12);
|
||||
$x3+= $x7; $x15 = self::leftRotate($x15 ^ $x3, 8);
|
||||
$x11+= $x15; $x7 = self::leftRotate($x7 ^ $x11, 7);
|
||||
|
||||
// rowRound
|
||||
$x0+= $x5; $x15 = self::leftRotate($x15 ^ $x0, 16);
|
||||
$x10+= $x15; $x5 = self::leftRotate($x5 ^ $x10, 12);
|
||||
$x0+= $x5; $x15 = self::leftRotate($x15 ^ $x0, 8);
|
||||
$x10+= $x15; $x5 = self::leftRotate($x5 ^ $x10, 7);
|
||||
|
||||
$x1+= $x6; $x12 = self::leftRotate($x12 ^ $x1, 16);
|
||||
$x11+= $x12; $x6 = self::leftRotate($x6 ^ $x11, 12);
|
||||
$x1+= $x6; $x12 = self::leftRotate($x12 ^ $x1, 8);
|
||||
$x11+= $x12; $x6 = self::leftRotate($x6 ^ $x11, 7);
|
||||
|
||||
$x2+= $x7; $x13 = self::leftRotate($x13 ^ $x2, 16);
|
||||
$x8+= $x13; $x7 = self::leftRotate($x7 ^ $x8, 12);
|
||||
$x2+= $x7; $x13 = self::leftRotate($x13 ^ $x2, 8);
|
||||
$x8+= $x13; $x7 = self::leftRotate($x7 ^ $x8, 7);
|
||||
|
||||
$x3+= $x4; $x14 = self::leftRotate($x14 ^ $x3, 16);
|
||||
$x9+= $x14; $x4 = self::leftRotate($x4 ^ $x9, 12);
|
||||
$x3+= $x4; $x14 = self::leftRotate($x14 ^ $x3, 8);
|
||||
$x9+= $x14; $x4 = self::leftRotate($x4 ^ $x9, 7);
|
||||
|
||||
// columnRound
|
||||
$x0+= $x4; $x12 = self::leftRotate($x12 ^ $x0, 16);
|
||||
$x8+= $x12; $x4 = self::leftRotate($x4 ^ $x8, 12);
|
||||
$x0+= $x4; $x12 = self::leftRotate($x12 ^ $x0, 8);
|
||||
$x8+= $x12; $x4 = self::leftRotate($x4 ^ $x8, 7);
|
||||
|
||||
$x1+= $x5; $x13 = self::leftRotate($x13 ^ $x1, 16);
|
||||
$x9+= $x13; $x5 = self::leftRotate($x5 ^ $x9, 12);
|
||||
$x1+= $x5; $x13 = self::leftRotate($x13 ^ $x1, 8);
|
||||
$x9+= $x13; $x5 = self::leftRotate($x5 ^ $x9, 7);
|
||||
|
||||
$x2+= $x6; $x14 = self::leftRotate($x14 ^ $x2, 16);
|
||||
$x10+= $x14; $x6 = self::leftRotate($x6 ^ $x10, 12);
|
||||
$x2+= $x6; $x14 = self::leftRotate($x14 ^ $x2, 8);
|
||||
$x10+= $x14; $x6 = self::leftRotate($x6 ^ $x10, 7);
|
||||
|
||||
$x3+= $x7; $x15 = self::leftRotate($x15 ^ $x3, 16);
|
||||
$x11+= $x15; $x7 = self::leftRotate($x7 ^ $x11, 12);
|
||||
$x3+= $x7; $x15 = self::leftRotate($x15 ^ $x3, 8);
|
||||
$x11+= $x15; $x7 = self::leftRotate($x7 ^ $x11, 7);
|
||||
|
||||
// rowRound
|
||||
$x0+= $x5; $x15 = self::leftRotate($x15 ^ $x0, 16);
|
||||
$x10+= $x15; $x5 = self::leftRotate($x5 ^ $x10, 12);
|
||||
$x0+= $x5; $x15 = self::leftRotate($x15 ^ $x0, 8);
|
||||
$x10+= $x15; $x5 = self::leftRotate($x5 ^ $x10, 7);
|
||||
|
||||
$x1+= $x6; $x12 = self::leftRotate($x12 ^ $x1, 16);
|
||||
$x11+= $x12; $x6 = self::leftRotate($x6 ^ $x11, 12);
|
||||
$x1+= $x6; $x12 = self::leftRotate($x12 ^ $x1, 8);
|
||||
$x11+= $x12; $x6 = self::leftRotate($x6 ^ $x11, 7);
|
||||
|
||||
$x2+= $x7; $x13 = self::leftRotate($x13 ^ $x2, 16);
|
||||
$x8+= $x13; $x7 = self::leftRotate($x7 ^ $x8, 12);
|
||||
$x2+= $x7; $x13 = self::leftRotate($x13 ^ $x2, 8);
|
||||
$x8+= $x13; $x7 = self::leftRotate($x7 ^ $x8, 7);
|
||||
|
||||
$x3+= $x4; $x14 = self::leftRotate($x14 ^ $x3, 16);
|
||||
$x9+= $x14; $x4 = self::leftRotate($x4 ^ $x9, 12);
|
||||
$x3+= $x4; $x14 = self::leftRotate($x14 ^ $x3, 8);
|
||||
$x9+= $x14; $x4 = self::leftRotate($x4 ^ $x9, 7);
|
||||
|
||||
// columnRound
|
||||
$x0+= $x4; $x12 = self::leftRotate($x12 ^ $x0, 16);
|
||||
$x8+= $x12; $x4 = self::leftRotate($x4 ^ $x8, 12);
|
||||
$x0+= $x4; $x12 = self::leftRotate($x12 ^ $x0, 8);
|
||||
$x8+= $x12; $x4 = self::leftRotate($x4 ^ $x8, 7);
|
||||
|
||||
$x1+= $x5; $x13 = self::leftRotate($x13 ^ $x1, 16);
|
||||
$x9+= $x13; $x5 = self::leftRotate($x5 ^ $x9, 12);
|
||||
$x1+= $x5; $x13 = self::leftRotate($x13 ^ $x1, 8);
|
||||
$x9+= $x13; $x5 = self::leftRotate($x5 ^ $x9, 7);
|
||||
|
||||
$x2+= $x6; $x14 = self::leftRotate($x14 ^ $x2, 16);
|
||||
$x10+= $x14; $x6 = self::leftRotate($x6 ^ $x10, 12);
|
||||
$x2+= $x6; $x14 = self::leftRotate($x14 ^ $x2, 8);
|
||||
$x10+= $x14; $x6 = self::leftRotate($x6 ^ $x10, 7);
|
||||
|
||||
$x3+= $x7; $x15 = self::leftRotate($x15 ^ $x3, 16);
|
||||
$x11+= $x15; $x7 = self::leftRotate($x7 ^ $x11, 12);
|
||||
$x3+= $x7; $x15 = self::leftRotate($x15 ^ $x3, 8);
|
||||
$x11+= $x15; $x7 = self::leftRotate($x7 ^ $x11, 7);
|
||||
|
||||
// rowRound
|
||||
$x0+= $x5; $x15 = self::leftRotate($x15 ^ $x0, 16);
|
||||
$x10+= $x15; $x5 = self::leftRotate($x5 ^ $x10, 12);
|
||||
$x0+= $x5; $x15 = self::leftRotate($x15 ^ $x0, 8);
|
||||
$x10+= $x15; $x5 = self::leftRotate($x5 ^ $x10, 7);
|
||||
|
||||
$x1+= $x6; $x12 = self::leftRotate($x12 ^ $x1, 16);
|
||||
$x11+= $x12; $x6 = self::leftRotate($x6 ^ $x11, 12);
|
||||
$x1+= $x6; $x12 = self::leftRotate($x12 ^ $x1, 8);
|
||||
$x11+= $x12; $x6 = self::leftRotate($x6 ^ $x11, 7);
|
||||
|
||||
$x2+= $x7; $x13 = self::leftRotate($x13 ^ $x2, 16);
|
||||
$x8+= $x13; $x7 = self::leftRotate($x7 ^ $x8, 12);
|
||||
$x2+= $x7; $x13 = self::leftRotate($x13 ^ $x2, 8);
|
||||
$x8+= $x13; $x7 = self::leftRotate($x7 ^ $x8, 7);
|
||||
|
||||
$x3+= $x4; $x14 = self::leftRotate($x14 ^ $x3, 16);
|
||||
$x9+= $x14; $x4 = self::leftRotate($x4 ^ $x9, 12);
|
||||
$x3+= $x4; $x14 = self::leftRotate($x14 ^ $x3, 8);
|
||||
$x9+= $x14; $x4 = self::leftRotate($x4 ^ $x9, 7);
|
||||
|
||||
// columnRound
|
||||
$x0+= $x4; $x12 = self::leftRotate($x12 ^ $x0, 16);
|
||||
$x8+= $x12; $x4 = self::leftRotate($x4 ^ $x8, 12);
|
||||
$x0+= $x4; $x12 = self::leftRotate($x12 ^ $x0, 8);
|
||||
$x8+= $x12; $x4 = self::leftRotate($x4 ^ $x8, 7);
|
||||
|
||||
$x1+= $x5; $x13 = self::leftRotate($x13 ^ $x1, 16);
|
||||
$x9+= $x13; $x5 = self::leftRotate($x5 ^ $x9, 12);
|
||||
$x1+= $x5; $x13 = self::leftRotate($x13 ^ $x1, 8);
|
||||
$x9+= $x13; $x5 = self::leftRotate($x5 ^ $x9, 7);
|
||||
|
||||
$x2+= $x6; $x14 = self::leftRotate($x14 ^ $x2, 16);
|
||||
$x10+= $x14; $x6 = self::leftRotate($x6 ^ $x10, 12);
|
||||
$x2+= $x6; $x14 = self::leftRotate($x14 ^ $x2, 8);
|
||||
$x10+= $x14; $x6 = self::leftRotate($x6 ^ $x10, 7);
|
||||
|
||||
$x3+= $x7; $x15 = self::leftRotate($x15 ^ $x3, 16);
|
||||
$x11+= $x15; $x7 = self::leftRotate($x7 ^ $x11, 12);
|
||||
$x3+= $x7; $x15 = self::leftRotate($x15 ^ $x3, 8);
|
||||
$x11+= $x15; $x7 = self::leftRotate($x7 ^ $x11, 7);
|
||||
|
||||
// rowRound
|
||||
$x0+= $x5; $x15 = self::leftRotate($x15 ^ $x0, 16);
|
||||
$x10+= $x15; $x5 = self::leftRotate($x5 ^ $x10, 12);
|
||||
$x0+= $x5; $x15 = self::leftRotate($x15 ^ $x0, 8);
|
||||
$x10+= $x15; $x5 = self::leftRotate($x5 ^ $x10, 7);
|
||||
|
||||
$x1+= $x6; $x12 = self::leftRotate($x12 ^ $x1, 16);
|
||||
$x11+= $x12; $x6 = self::leftRotate($x6 ^ $x11, 12);
|
||||
$x1+= $x6; $x12 = self::leftRotate($x12 ^ $x1, 8);
|
||||
$x11+= $x12; $x6 = self::leftRotate($x6 ^ $x11, 7);
|
||||
|
||||
$x2+= $x7; $x13 = self::leftRotate($x13 ^ $x2, 16);
|
||||
$x8+= $x13; $x7 = self::leftRotate($x7 ^ $x8, 12);
|
||||
$x2+= $x7; $x13 = self::leftRotate($x13 ^ $x2, 8);
|
||||
$x8+= $x13; $x7 = self::leftRotate($x7 ^ $x8, 7);
|
||||
|
||||
$x3+= $x4; $x14 = self::leftRotate($x14 ^ $x3, 16);
|
||||
$x9+= $x14; $x4 = self::leftRotate($x4 ^ $x9, 12);
|
||||
$x3+= $x4; $x14 = self::leftRotate($x14 ^ $x3, 8);
|
||||
$x9+= $x14; $x4 = self::leftRotate($x4 ^ $x9, 7);
|
||||
|
||||
$x0+= $z0;
|
||||
$x1+= $z1;
|
||||
$x2+= $z2;
|
||||
$x3+= $z3;
|
||||
$x4+= $z4;
|
||||
$x5+= $z5;
|
||||
$x6+= $z6;
|
||||
$x7+= $z7;
|
||||
$x8+= $z8;
|
||||
$x9+= $z9;
|
||||
$x10+= $z10;
|
||||
$x11+= $z11;
|
||||
$x12+= $z12;
|
||||
$x13+= $z13;
|
||||
$x14+= $z14;
|
||||
$x15+= $z15;
|
||||
|
||||
return pack('V*', $x0, $x1, $x2, $x3, $x4, $x5, $x6, $x7, $x8, $x9, $x10, $x11, $x12, $x13, $x14, $x15);
|
||||
}
|
||||
}
|
@ -40,6 +40,7 @@ use phpseclib\Crypt\Hash;
|
||||
use phpseclib\Common\Functions\Strings;
|
||||
use phpseclib\Math\BigInteger;
|
||||
use phpseclib\Math\BinaryField;
|
||||
use phpseclib\Math\PrimeField;
|
||||
use phpseclib\Exception\BadDecryptionException;
|
||||
use phpseclib\Exception\BadModeException;
|
||||
use phpseclib\Exception\InconsistentSetupException;
|
||||
@ -533,13 +534,43 @@ abstract class SymmetricKey
|
||||
/**
|
||||
* GCM Binary Field
|
||||
*
|
||||
* @see self::initialize_static_variables()
|
||||
* @see self::__construct()
|
||||
* @see self::ghash()
|
||||
* @var BinaryField
|
||||
* @access private
|
||||
*/
|
||||
private static $gcmField;
|
||||
|
||||
/**
|
||||
* Poly1305 Prime Field
|
||||
*
|
||||
* @see self::enablePoly1305()
|
||||
* @see self::poly1305()
|
||||
* @var PrimeField
|
||||
* @access private
|
||||
*/
|
||||
private static $poly1305Field;
|
||||
|
||||
/**
|
||||
* Poly1305 Key
|
||||
*
|
||||
* @see self::setPoly1305Key()
|
||||
* @see self::poly1305()
|
||||
* @var string
|
||||
* @access private
|
||||
*/
|
||||
protected $poly1305Key;
|
||||
|
||||
/**
|
||||
* Poly1305 Flag
|
||||
*
|
||||
* @see self::setPoly1305Key()
|
||||
* @see self::enablePoly1305()
|
||||
* @var boolean
|
||||
* @access private
|
||||
*/
|
||||
protected $usePoly1305 = false;
|
||||
|
||||
/**
|
||||
* The Original Initialization Vector
|
||||
*
|
||||
@ -649,7 +680,7 @@ abstract class SymmetricKey
|
||||
}
|
||||
|
||||
if (!$this->usesIV()) {
|
||||
throw new \BadMethodCallExceptionn('This algorithm does not use an IV.');
|
||||
throw new \BadMethodCallException('This algorithm does not use an IV.');
|
||||
}
|
||||
|
||||
if (strlen($iv) != $this->block_size) {
|
||||
@ -660,6 +691,53 @@ abstract class SymmetricKey
|
||||
$this->changed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables Poly1305 mode.
|
||||
*
|
||||
* Once enabled Poly1305 cannot be disabled.
|
||||
*
|
||||
* @access public
|
||||
* @throws \BadMethodCallException if Poly1305 is enabled whilst in GCM mode
|
||||
*/
|
||||
public function enablePoly1305()
|
||||
{
|
||||
if ($this->mode == self::MODE_GCM) {
|
||||
throw new \BadMethodCallException('Poly1305 cannot be used in GCM mode');
|
||||
}
|
||||
|
||||
$this->usePoly1305 = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables Poly1305 mode.
|
||||
*
|
||||
* Once enabled Poly1305 cannot be disabled. If $key is not passed then an attempt to call createPoly1305Key
|
||||
* will be made.
|
||||
*
|
||||
* @access public
|
||||
* @param string $key optional
|
||||
* @throws \LengthException if the key isn't long enough
|
||||
* @throws \BadMethodCallException if Poly1305 is enabled whilst in GCM mode
|
||||
*/
|
||||
public function setPoly1305Key($key = null)
|
||||
{
|
||||
if ($this->mode == self::MODE_GCM) {
|
||||
throw new \BadMethodCallException('Poly1305 cannot be used in GCM mode');
|
||||
}
|
||||
|
||||
if (!is_string($key) || strlen($key) != 32) {
|
||||
throw new \LengthException('The Poly1305 key must be 32 bytes long (256 bits)');
|
||||
}
|
||||
|
||||
if (!isset(self::$poly1305Field)) {
|
||||
// 2^130-5
|
||||
self::$poly1305Field = new PrimeField(new BigInteger('3fffffffffffffffffffffffffffffffb', 16));
|
||||
}
|
||||
|
||||
$this->poly1305Key = $key;
|
||||
$this->usePoly1305 = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the nonce.
|
||||
*
|
||||
@ -667,7 +745,7 @@ abstract class SymmetricKey
|
||||
*
|
||||
* @access public
|
||||
* @param string $nonce
|
||||
* @throws \RuntimeException if an IV is provided when one shouldn't be
|
||||
* @throws \BadMethodCallException if an nonce is provided when one shouldn't be
|
||||
*/
|
||||
public function setNonce($nonce)
|
||||
{
|
||||
@ -683,16 +761,16 @@ abstract class SymmetricKey
|
||||
/**
|
||||
* Sets additional authenticated data
|
||||
*
|
||||
* setAAD() is only used by gcm
|
||||
* setAAD() is only used by gcm or in poly1305 mode
|
||||
*
|
||||
* @access public
|
||||
* @param string $aad
|
||||
* @throws \RuntimeException if mode isn't GCM
|
||||
* @throws \BadMethodCallException if mode isn't GCM or if poly1305 isn't being utilized
|
||||
*/
|
||||
public function setAAD($aad)
|
||||
{
|
||||
if ($this->mode != self::MODE_GCM) {
|
||||
throw new \RuntimeException('Additional authenticated data is only utilized in GCM mode');
|
||||
if ($this->mode != self::MODE_GCM && !$this->usePoly1305) {
|
||||
throw new \BadMethodCallException('Additional authenticated data is only utilized in GCM mode or with Poly1305');
|
||||
}
|
||||
|
||||
$this->aad = $aad;
|
||||
@ -1046,6 +1124,15 @@ abstract class SymmetricKey
|
||||
return $ciphertext;
|
||||
}
|
||||
|
||||
if (isset($this->poly1305Key)) {
|
||||
$cipher = clone $this;
|
||||
unset($cipher->poly1305Key);
|
||||
$this->usePoly1305 = false;
|
||||
$ciphertext = $cipher->encrypt($plaintext);
|
||||
$this->newtag = $this->poly1305($ciphertext);
|
||||
return $ciphertext;
|
||||
}
|
||||
|
||||
if ($this->engine === self::ENGINE_OPENSSL) {
|
||||
switch ($this->mode) {
|
||||
case self::MODE_STREAM:
|
||||
@ -1362,27 +1449,35 @@ abstract class SymmetricKey
|
||||
|
||||
$this->setup();
|
||||
|
||||
if ($this->mode == self::MODE_GCM) {
|
||||
if ($this->mode == self::MODE_GCM || isset($this->poly1305Key)) {
|
||||
if ($this->oldtag === false) {
|
||||
throw new InsufficientSetupException('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);
|
||||
if (isset($this->poly1305Key)) {
|
||||
$newtag = $this->poly1305($ciphertext);
|
||||
} else {
|
||||
$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);
|
||||
$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))) {
|
||||
$cipher = clone $this;
|
||||
unset($cipher->poly1305Key);
|
||||
$this->usePoly1305 = false;
|
||||
$plaintext = $cipher->decrypt($ciphertext);
|
||||
$this->oldtag = false;
|
||||
throw new BadDecryptionException('Derived authentication tag and supplied authentication tag do not match');
|
||||
}
|
||||
@ -1672,7 +1767,7 @@ abstract class SymmetricKey
|
||||
/**
|
||||
* Get the authentication tag
|
||||
*
|
||||
* Only used in GCM mode
|
||||
* Only used in GCM or Poly1305 mode
|
||||
*
|
||||
* @see self::encrypt()
|
||||
* @param int $length optional
|
||||
@ -1683,13 +1778,17 @@ abstract class SymmetricKey
|
||||
*/
|
||||
public function getTag($length = 16)
|
||||
{
|
||||
if ($this->mode != self::MODE_GCM) {
|
||||
throw new \BadMethodCallException('Only GCM mode utilizes authentication tags');
|
||||
if ($this->mode != self::MODE_GCM && !$this->usePoly1305) {
|
||||
throw new \BadMethodCallException('Authentication tags are only utilized in GCM mode or with Poly1305');
|
||||
}
|
||||
|
||||
// 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.
|
||||
if ($this->newtag === false) {
|
||||
throw new \BadMethodCallException('A tag can only be returned after a round of encryption has been performed');
|
||||
}
|
||||
|
||||
// the tag is 128-bits. it can't be greater than 16 bytes because that's bigger than the tag 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) {
|
||||
@ -1714,8 +1813,12 @@ abstract class SymmetricKey
|
||||
*/
|
||||
public function setTag($tag)
|
||||
{
|
||||
if ($this->mode != self::MODE_GCM) {
|
||||
throw new \BadMethodCallException('Only GCM mode utilizes authentication tags');
|
||||
if ($this->usePoly1305 && !isset($this->poly1305Key) && method_exists($this, 'createPoly1305Key')) {
|
||||
$this->createPoly1305Key();
|
||||
}
|
||||
|
||||
if ($this->mode != self::MODE_GCM && !$this->usePoly1305) {
|
||||
throw new \BadMethodCallException('Authentication tags are only utilized in GCM mode or with Poly1305');
|
||||
}
|
||||
|
||||
$length = strlen($tag);
|
||||
@ -2030,9 +2133,6 @@ abstract class SymmetricKey
|
||||
{
|
||||
switch ($engine) {
|
||||
case self::ENGINE_OPENSSL:
|
||||
if ($this->mode == self::MODE_STREAM && $this->continuousBuffer) {
|
||||
return false;
|
||||
}
|
||||
$this->openssl_emulate_ctr = false;
|
||||
$result = $this->cipher_name_openssl &&
|
||||
extension_loaded('openssl');
|
||||
@ -2146,13 +2246,19 @@ abstract class SymmetricKey
|
||||
$this->engine = null;
|
||||
|
||||
$candidateEngines = [
|
||||
$this->preferredEngine,
|
||||
self::ENGINE_LIBSODIUM,
|
||||
self::ENGINE_OPENSSL_GCM,
|
||||
self::ENGINE_OPENSSL,
|
||||
self::ENGINE_MCRYPT,
|
||||
self::ENGINE_EVAL
|
||||
];
|
||||
if (isset($this->preferredEngine)) {
|
||||
$temp = [$this->preferredEngine];
|
||||
$candidateEngines = array_merge(
|
||||
$temp,
|
||||
array_diff($candidateEngines, $temp)
|
||||
);
|
||||
}
|
||||
foreach ($candidateEngines as $engine) {
|
||||
if ($this->isValidEngineHelper($engine)) {
|
||||
$this->engine = $engine;
|
||||
@ -2246,14 +2352,18 @@ abstract class SymmetricKey
|
||||
|
||||
$this->changed = false;
|
||||
|
||||
if ($this->usePoly1305 && !isset($this->poly1305Key) && method_exists($this, 'createPoly1305Key')) {
|
||||
$this->createPoly1305Key();
|
||||
}
|
||||
|
||||
$this->enbuffer = $this->debuffer = ['ciphertext' => '', 'xor' => '', 'pos' => 0, 'enmcrypt_init' => true];
|
||||
//$this->newtag = $this->oldtag = false;
|
||||
|
||||
if ($this->mode == self::MODE_GCM) {
|
||||
if ($this->usesNonce()) {
|
||||
if ($this->nonce === false) {
|
||||
throw new InsufficientSetupException('No nonce has been defined');
|
||||
}
|
||||
if (!in_array($this->engine, [self::ENGINE_LIBSODIUM, self::ENGINE_OPENSSL_GCM])) {
|
||||
if ($this->mode == self::MODE_GCM && !in_array($this->engine, [self::ENGINE_LIBSODIUM, self::ENGINE_OPENSSL_GCM])) {
|
||||
$this->setupGCM();
|
||||
}
|
||||
} else {
|
||||
@ -3078,9 +3188,45 @@ abstract class SymmetricKey
|
||||
* @param string $str
|
||||
* @return string
|
||||
*/
|
||||
private static function nullPad128($str)
|
||||
protected static function nullPad128($str)
|
||||
{
|
||||
$len = strlen($str);
|
||||
return $str . str_repeat("\0", 16 * ceil($len / 16) - $len);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates Poly1305 MAC
|
||||
*
|
||||
* On my system ChaCha20, with libsodium, takes 0.5s. With this custom Poly1305 implementation
|
||||
* it takes 1.2s.
|
||||
*
|
||||
* @see self::decrypt()
|
||||
* @see self::encrypt()
|
||||
* @access private
|
||||
* @param string $text
|
||||
* @return string
|
||||
*/
|
||||
protected function poly1305($text)
|
||||
{
|
||||
$s = $this->poly1305Key; // strlen($this->poly1305Key) == 32
|
||||
$r = Strings::shift($s, 16);
|
||||
$r = strrev($r);
|
||||
$r&= "\x0f\xff\xff\xfc\x0f\xff\xff\xfc\x0f\xff\xff\xfc\x0f\xff\xff\xff";
|
||||
$s = strrev($s);
|
||||
|
||||
$r = self::$poly1305Field->newInteger(new BigInteger($r, 256));
|
||||
$s = self::$poly1305Field->newInteger(new BigInteger($s, 256));
|
||||
$a = self::$poly1305Field->newInteger(new BigInteger());
|
||||
|
||||
$blocks = str_split($text, 16);
|
||||
foreach ($blocks as $block) {
|
||||
$n = strrev($block . chr(1));
|
||||
$n = self::$poly1305Field->newInteger(new BigInteger($n, 256));
|
||||
$a = $a->add($n);
|
||||
$a = $a->multiply($r);
|
||||
}
|
||||
$r = $a->toBigInteger()->add($s->toBigInteger());
|
||||
$mask = "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF";
|
||||
return strrev($r->toBytes()) & $mask;
|
||||
}
|
||||
}
|
@ -145,6 +145,9 @@ class RC4 extends StreamCipher
|
||||
protected function isValidEngineHelper($engine)
|
||||
{
|
||||
if ($engine == self::ENGINE_OPENSSL) {
|
||||
if ($this->continuousBuffer) {
|
||||
return false;
|
||||
}
|
||||
if (version_compare(PHP_VERSION, '5.3.7') >= 0) {
|
||||
$this->cipher_name_openssl = 'rc4-40';
|
||||
} else {
|
||||
|
541
phpseclib/Crypt/Salsa20.php
Normal file
541
phpseclib/Crypt/Salsa20.php
Normal file
@ -0,0 +1,541 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Pure-PHP implementation of Salsa20.
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* @category Crypt
|
||||
* @package Salsa20
|
||||
* @author Jim Wigginton <terrafrost@php.net>
|
||||
* @copyright 2019 Jim Wigginton
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @link http://phpseclib.sourceforge.net
|
||||
*/
|
||||
|
||||
namespace phpseclib\Crypt;
|
||||
|
||||
use phpseclib\Crypt\Common\StreamCipher;
|
||||
use phpseclib\Exception\InsufficientSetupException;
|
||||
use phpseclib\Exception\BadDecryptionException;
|
||||
use phpseclib\Common\Functions\Strings;
|
||||
|
||||
/**
|
||||
* Pure-PHP implementation of Salsa20.
|
||||
*
|
||||
* @package Salsa20
|
||||
* @author Jim Wigginton <terrafrost@php.net>
|
||||
* @access public
|
||||
*/
|
||||
class Salsa20 extends StreamCipher
|
||||
{
|
||||
/**
|
||||
* Part 1 of the state
|
||||
*
|
||||
* @var string|false
|
||||
*/
|
||||
protected $p1 = false;
|
||||
|
||||
/**
|
||||
* Part 2 of the state
|
||||
*
|
||||
* @var string|false
|
||||
*/
|
||||
protected $p2 = false;
|
||||
|
||||
/**
|
||||
* Key Length (in bytes)
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $key_length = 32; // = 256 bits
|
||||
|
||||
/**
|
||||
* Block Length of the cipher
|
||||
*
|
||||
* Salsa20 is a stream cipher
|
||||
* so we the block_size to 0
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $block_size = 0;
|
||||
|
||||
/**#@+
|
||||
* @access private
|
||||
* @see \phpseclib\Crypt\Salsa20::crypt()
|
||||
*/
|
||||
const ENCRYPT = 0;
|
||||
const DECRYPT = 1;
|
||||
/**#@-*/
|
||||
|
||||
/**
|
||||
* Encryption buffer for continuous mode
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $enbuffer;
|
||||
|
||||
/**
|
||||
* Decryption buffer for continuous mode
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $debuffer;
|
||||
|
||||
/**
|
||||
* Counter
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $counter = 0;
|
||||
|
||||
/**
|
||||
* Using Generated Poly1305 Key
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $usingGeneratedPoly1305Key = false;
|
||||
|
||||
/**
|
||||
* Default Constructor.
|
||||
*
|
||||
* @see \phpseclib\Crypt\Common\SymmetricKey::__construct()
|
||||
* @return \phpseclib\Crypt\Salsa20
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('stream');
|
||||
}
|
||||
|
||||
/**
|
||||
* Salsa20 does not use an IV
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function usesIV()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Salsa20 uses a nonce
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function usesNonce()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the key.
|
||||
*
|
||||
* @param string $key
|
||||
* @throws \LengthException if the key length isn't supported
|
||||
*/
|
||||
public function setKey($key)
|
||||
{
|
||||
switch (strlen($key)) {
|
||||
case 16:
|
||||
case 32:
|
||||
break;
|
||||
default:
|
||||
throw new \LengthException('Key of size ' . strlen($key) . ' not supported by this algorithm. Only keys of sizes 16 or 32 are supported');
|
||||
}
|
||||
|
||||
parent::setKey($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the nonce.
|
||||
*
|
||||
* @param string $nonce
|
||||
*/
|
||||
public function setNonce($nonce)
|
||||
{
|
||||
if (strlen($nonce) != 8) {
|
||||
throw new \LengthException('Nonce of size ' . strlen($key) . ' not supported by this algorithm. Only an 64-bit nonce is supported');
|
||||
}
|
||||
|
||||
$this->nonce = $nonce;
|
||||
$this->changed = true;
|
||||
$this->setEngine();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the counter.
|
||||
*
|
||||
* @param int $counter
|
||||
*/
|
||||
public function setCounter($counter)
|
||||
{
|
||||
$this->counter = $counter;
|
||||
$this->setEngine();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Poly1305 key using the method discussed in RFC8439
|
||||
*
|
||||
* See https://tools.ietf.org/html/rfc8439#section-2.6.1
|
||||
*/
|
||||
protected function createPoly1305Key()
|
||||
{
|
||||
if ($this->nonce === false) {
|
||||
throw new InsufficientSetupException('No nonce has been defined');
|
||||
}
|
||||
|
||||
if ($this->key === false) {
|
||||
throw new InsufficientSetupException('No key has been defined');
|
||||
}
|
||||
|
||||
$c = clone $this;
|
||||
$c->setCounter(0);
|
||||
$c->usePoly1305 = false;
|
||||
$block = $c->encrypt(str_repeat("\0", 256));
|
||||
$this->setPoly1305Key(substr($block, 0, 32));
|
||||
|
||||
if ($this->counter == 0) {
|
||||
$this->counter++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the self::ENGINE_INTERNAL $engine
|
||||
*
|
||||
* (re)init, if necessary, the internal cipher $engine
|
||||
*
|
||||
* _setup() will be called each time if $changed === true
|
||||
* typically this happens when using one or more of following public methods:
|
||||
*
|
||||
* - setKey()
|
||||
*
|
||||
* - setNonce()
|
||||
*
|
||||
* - First run of encrypt() / decrypt() with no init-settings
|
||||
*
|
||||
* @see self::setKey()
|
||||
* @see self::setNonce()
|
||||
* @see self::disableContinuousBuffer()
|
||||
*/
|
||||
protected function setup()
|
||||
{
|
||||
if (!$this->changed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->enbuffer = $this->debuffer = ['ciphertext' => '', 'counter' => $this->counter];
|
||||
|
||||
$this->changed = false;
|
||||
|
||||
if ($this->nonce === false) {
|
||||
throw new InsufficientSetupException('No nonce has been defined');
|
||||
}
|
||||
|
||||
if ($this->key === false) {
|
||||
throw new InsufficientSetupException('No key has been defined');
|
||||
}
|
||||
|
||||
if ($this->usePoly1305 && !isset($this->poly1305Key)) {
|
||||
$this->usingGeneratedPoly1305Key = true;
|
||||
$this->createPoly1305Key();
|
||||
}
|
||||
|
||||
$key = $this->key;
|
||||
if (strlen($key) == 16) {
|
||||
$constant = 'expand 16-byte k';
|
||||
$key.= $key;
|
||||
} else {
|
||||
$constant = 'expand 32-byte k';
|
||||
}
|
||||
|
||||
$this->p1 = substr($constant, 0, 4) .
|
||||
substr($key, 0, 16) .
|
||||
substr($constant, 4, 4) .
|
||||
$this->nonce .
|
||||
"\0\0\0\0";
|
||||
$this->p2 = substr($constant, 8, 4) .
|
||||
substr($key, 16, 16) .
|
||||
substr($constant, 12, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the key (expansion)
|
||||
*/
|
||||
protected function setupKey()
|
||||
{
|
||||
// Salsa20 does not utilize this method
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts a message.
|
||||
*
|
||||
* @see \phpseclib\Crypt\Common\SymmetricKey::decrypt()
|
||||
* @see self::crypt()
|
||||
* @param string $plaintext
|
||||
* @return string $ciphertext
|
||||
*/
|
||||
public function encrypt($plaintext)
|
||||
{
|
||||
$ciphertext = $this->crypt($plaintext, self::ENCRYPT);
|
||||
if (isset($this->poly1305Key)) {
|
||||
$this->newtag = $this->poly1305($ciphertext);
|
||||
}
|
||||
return $ciphertext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts a message.
|
||||
*
|
||||
* $this->decrypt($this->encrypt($plaintext)) == $this->encrypt($this->encrypt($plaintext)).
|
||||
* At least if the continuous buffer is disabled.
|
||||
*
|
||||
* @see \phpseclib\Crypt\Common\SymmetricKey::encrypt()
|
||||
* @see self::crypt()
|
||||
* @param string $ciphertext
|
||||
* @return string $plaintext
|
||||
*/
|
||||
public function decrypt($ciphertext)
|
||||
{
|
||||
if (isset($this->poly1305Key)) {
|
||||
if ($this->oldtag === false) {
|
||||
throw new InsufficientSetupException('Authentication Tag has not been set');
|
||||
}
|
||||
$newtag = $this->poly1305($ciphertext);
|
||||
if ($this->oldtag != substr($newtag, 0, strlen($this->oldtag))) {
|
||||
$this->oldtag = false;
|
||||
throw new BadDecryptionException('Derived authentication tag and supplied authentication tag do not match');
|
||||
}
|
||||
$this->oldtag = false;
|
||||
}
|
||||
|
||||
return $this->crypt($ciphertext, self::DECRYPT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts a block
|
||||
*
|
||||
* @param string $in
|
||||
*/
|
||||
protected function encryptBlock($in)
|
||||
{
|
||||
// Salsa20 does not utilize this method
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts a block
|
||||
*
|
||||
* @param string $in
|
||||
*/
|
||||
protected function decryptBlock($in)
|
||||
{
|
||||
// Salsa20 does not utilize this method
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts or decrypts a message.
|
||||
*
|
||||
* @see self::encrypt()
|
||||
* @see self::decrypt()
|
||||
* @param string $text
|
||||
* @param int $mode
|
||||
* @return string $text
|
||||
*/
|
||||
private function crypt($text, $mode)
|
||||
{
|
||||
$this->setup();
|
||||
if (!$this->continuousBuffer) {
|
||||
if ($this->engine == self::ENGINE_OPENSSL) {
|
||||
$iv = pack('V', $this->counter) . $this->p2;
|
||||
return openssl_encrypt(
|
||||
$text,
|
||||
$this->cipher_name_openssl,
|
||||
$this->key,
|
||||
OPENSSL_RAW_DATA,
|
||||
$iv
|
||||
);
|
||||
}
|
||||
$i = $this->counter;
|
||||
$blocks = str_split($text, 64);
|
||||
foreach ($blocks as &$block) {
|
||||
$block^= static::salsa20($this->p1 . pack('V', $i++) . $this->p2);
|
||||
}
|
||||
|
||||
return implode('', $blocks);
|
||||
}
|
||||
|
||||
if ($mode == self::ENCRYPT) {
|
||||
$buffer = &$this->enbuffer;
|
||||
} else {
|
||||
$buffer = &$this->debuffer;
|
||||
}
|
||||
if (strlen($buffer['ciphertext'])) {
|
||||
$ciphertext = $text ^ Strings::shift($buffer['ciphertext'], strlen($text));
|
||||
$text = substr($text, strlen($ciphertext));
|
||||
if (!strlen($text)) {
|
||||
return $ciphertext;
|
||||
}
|
||||
}
|
||||
|
||||
$overflow = strlen($text) % 64; // & 0x3F
|
||||
if ($overflow) {
|
||||
$text2 = Strings::pop($text, $overflow);
|
||||
if ($this->engine == self::ENGINE_OPENSSL) {
|
||||
$iv = pack('V', $buffer['counter']) . $this->p2;
|
||||
// at this point $text should be a multiple of 64
|
||||
$buffer['counter']+= (strlen($text) >> 6) + 1; // ie. divide by 64
|
||||
$encrypted = openssl_encrypt(
|
||||
$text . str_repeat("\0", 64),
|
||||
$this->cipher_name_openssl,
|
||||
$this->key,
|
||||
OPENSSL_RAW_DATA,
|
||||
$iv
|
||||
);
|
||||
$temp = Strings::pop($encrypted, 64);
|
||||
} else {
|
||||
$blocks = str_split($text, 64);
|
||||
if (strlen($text)) {
|
||||
foreach ($blocks as &$block) {
|
||||
$block^= static::salsa20($this->p1 . pack('V', $buffer['counter']++) . $this->p2);
|
||||
}
|
||||
}
|
||||
$encrypted = implode('', $blocks);
|
||||
$temp = static::salsa20($this->p1 . pack('V', $buffer['counter']++) . $this->p2);
|
||||
}
|
||||
$ciphertext.= $encrypted . ($text2 ^ $temp);
|
||||
$buffer['ciphertext'] = substr($temp, $overflow);
|
||||
} elseif (!strlen($buffer['ciphertext'])) {
|
||||
if ($this->engine == self::ENGINE_OPENSSL) {
|
||||
$iv = pack('V', $buffer['counter']) . $this->p2;
|
||||
$buffer['counter']+= (strlen($text) >> 6);
|
||||
$ciphertext.= openssl_encrypt(
|
||||
$text,
|
||||
$this->cipher_name_openssl,
|
||||
$this->key,
|
||||
OPENSSL_RAW_DATA,
|
||||
$iv
|
||||
);
|
||||
} else {
|
||||
$blocks = str_split($text, 64);
|
||||
foreach ($blocks as &$block) {
|
||||
$block^= static::salsa20($this->p1 . pack('V', $buffer['counter']++) . $this->p2);
|
||||
}
|
||||
$ciphertext.= implode('', $blocks);
|
||||
}
|
||||
}
|
||||
|
||||
return $ciphertext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Left Rotate
|
||||
*
|
||||
* @param int $x
|
||||
* @param int $n
|
||||
* @return int
|
||||
*/
|
||||
protected static function leftRotate($x, $n)
|
||||
{
|
||||
$r1 = $x << $n;
|
||||
if (PHP_INT_SIZE == 8) {
|
||||
$r1&= 0xFFFFFFFF;
|
||||
$r2 = ($x & 0xFFFFFFFF) >> (32 - $n);
|
||||
} else {
|
||||
$r2 = $x >> (32 - $n);
|
||||
$r2&= (1 << $n) - 1;
|
||||
}
|
||||
return $r1 | $r2;
|
||||
}
|
||||
|
||||
/**
|
||||
* The quarterround function
|
||||
*
|
||||
* @param int $a
|
||||
* @param int $b
|
||||
* @param int $c
|
||||
* @param int $d
|
||||
*/
|
||||
protected static function quarterRound(&$a, &$b, &$c, &$d)
|
||||
{
|
||||
$b^= self::leftRotate($a + $d, 7);
|
||||
$c^= self::leftRotate($b + $a, 9);
|
||||
$d^= self::leftRotate($c + $b, 13);
|
||||
$a^= self::leftRotate($d + $c, 18);
|
||||
}
|
||||
|
||||
/**
|
||||
* The doubleround function
|
||||
*
|
||||
* @param int $x0...$x16
|
||||
*/
|
||||
protected static function doubleRound(&$x0, &$x1, &$x2, &$x3, &$x4, &$x5, &$x6, &$x7, &$x8, &$x9, &$x10, &$x11, &$x12, &$x13, &$x14, &$x15)
|
||||
{
|
||||
// columnRound
|
||||
static::quarterRound( $x0, $x4, $x8, $x12);
|
||||
static::quarterRound( $x5, $x9, $x13, $x1);
|
||||
static::quarterRound($x10, $x14, $x2, $x6);
|
||||
static::quarterRound($x15, $x3, $x7, $x11);
|
||||
// rowRound
|
||||
static::quarterRound( $x0, $x1, $x2, $x3);
|
||||
static::quarterRound( $x5, $x6, $x7, $x4);
|
||||
static::quarterRound($x10, $x11, $x8, $x9);
|
||||
static::quarterRound($x15, $x12, $x13, $x14);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Salsa20 hash function function
|
||||
*
|
||||
* @param string $x
|
||||
*/
|
||||
protected static function salsa20($x)
|
||||
{
|
||||
$z = $x = unpack('V*', $x);
|
||||
for ($i = 0; $i < 10; $i++) {
|
||||
static::doubleRound(...$z);
|
||||
}
|
||||
|
||||
for ($i = 1; $i <= 16; $i++) {
|
||||
$x[$i]+= $z[$i];
|
||||
}
|
||||
|
||||
return pack('V*', ...$x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates Poly1305 MAC
|
||||
*
|
||||
* @see self::decrypt()
|
||||
* @see self::encrypt()
|
||||
* @access private
|
||||
* @param string $text
|
||||
* @return string
|
||||
*/
|
||||
protected function poly1305($ciphertext)
|
||||
{
|
||||
if (!$this->usingGeneratedPoly1305Key) {
|
||||
return parent::poly1305($this->aad . $ciphertext);
|
||||
} else {
|
||||
/*
|
||||
sodium_crypto_aead_chacha20poly1305_encrypt does not calculate the poly1305 tag
|
||||
the same way sodium_crypto_aead_chacha20poly1305_ietf_encrypt does. you can see
|
||||
how the latter encrypts it in Salsa20::encrypt(). here's how the former encrypts
|
||||
it:
|
||||
|
||||
$this->newtag = $this->poly1305(
|
||||
$this->aad .
|
||||
pack('V', strlen($this->aad)) . "\0\0\0\0" .
|
||||
$ciphertext .
|
||||
pack('V', strlen($ciphertext)) . "\0\0\0\0"
|
||||
);
|
||||
|
||||
phpseclib opts to use the IETF construction, even when the nonce is 64-bits
|
||||
instead of 96-bits
|
||||
*/
|
||||
return parent::poly1305(
|
||||
self::nullPad128($this->aad) .
|
||||
self::nullPad128($ciphertext) .
|
||||
pack('V', strlen($this->aad)) . "\0\0\0\0" .
|
||||
pack('V', strlen($ciphertext)) . "\0\0\0\0"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -59,6 +59,7 @@ use phpseclib\Crypt\AES;
|
||||
use phpseclib\Crypt\RSA;
|
||||
use phpseclib\Crypt\TripleDES;
|
||||
use phpseclib\Crypt\Twofish;
|
||||
use phpseclib\Crypt\ChaCha20;
|
||||
use phpseclib\Math\BigInteger; // Used to do Diffie-Hellman key exchange and DSA/RSA signature verification.
|
||||
use phpseclib\System\SSH\Agent;
|
||||
use phpseclib\System\SSH\Agent\Identity as AgentIdentity;
|
||||
@ -355,6 +356,15 @@ class SSH2
|
||||
*/
|
||||
private $decrypt = false;
|
||||
|
||||
/**
|
||||
* Server to Client Length Encryption Object
|
||||
*
|
||||
* @see self::_get_binary_packet()
|
||||
* @var object
|
||||
* @access private
|
||||
*/
|
||||
private $lengthDecrypt = false;
|
||||
|
||||
/**
|
||||
* Client to Server Encryption Object
|
||||
*
|
||||
@ -364,6 +374,15 @@ class SSH2
|
||||
*/
|
||||
private $encrypt = false;
|
||||
|
||||
/**
|
||||
* Client to Server Length Encryption Object
|
||||
*
|
||||
* @see self::_send_binary_packet()
|
||||
* @var object
|
||||
* @access private
|
||||
*/
|
||||
private $lengthEncrypt = false;
|
||||
|
||||
/**
|
||||
* Client to Server HMAC Object
|
||||
*
|
||||
@ -938,7 +957,7 @@ class SSH2
|
||||
* @var array
|
||||
* @access private
|
||||
*/
|
||||
var $auth = array();
|
||||
var $auth = [];
|
||||
|
||||
/**
|
||||
* Default Constructor.
|
||||
@ -1349,15 +1368,25 @@ 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
|
||||
'aes256-ctr', // RECOMMENDED AES with 256-bit key
|
||||
|
||||
// from <https://git.io/fhxOl>:
|
||||
// one of the big benefits of chacha20-poly1305 is speed. the problem is...
|
||||
// libsodium doesn't generate the poly1305 keys in the way ssh does and openssl's PHP bindings don't even
|
||||
// seem to support poly1305 currently. so even if libsodium or openssl are being used for the chacha20
|
||||
// part, pure-PHP has to be used for the poly1305 part and that's gonna cause a big slow down.
|
||||
// speed-wise it winds up being faster to use AES (when openssl or mcrypt are available) and some HMAC
|
||||
// (which is always gonna be super fast to compute thanks to the hash extension, which
|
||||
// "is bundled and compiled into PHP by default")
|
||||
'chacha20-poly1305@openssh.com',
|
||||
|
||||
// from <https://tools.ietf.org/html/rfc5647>:
|
||||
'aes128-gcm@openssh.com',
|
||||
'aes256-gcm@openssh.com',
|
||||
|
||||
'twofish128-ctr', // OPTIONAL Twofish in SDCTR mode, with 128-bit key
|
||||
'twofish192-ctr', // OPTIONAL Twofish with 192-bit key
|
||||
'twofish256-ctr', // OPTIONAL Twofish with 256-bit key
|
||||
@ -1882,20 +1911,28 @@ 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);
|
||||
switch ($encrypt) {
|
||||
case 'aes128-gcm@openssh.com':
|
||||
case 'aes256-gcm@openssh.com':
|
||||
$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);
|
||||
case 'chacha20-poly1305@openssh.com':
|
||||
break;
|
||||
default:
|
||||
$this->encrypt->enableContinuousBuffer();
|
||||
}
|
||||
|
||||
$key = $kexHash->hash($keyBytes . $this->exchange_hash . 'C' . $this->session_id);
|
||||
while ($encryptKeyLength > strlen($key)) {
|
||||
$key.= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
|
||||
}
|
||||
switch ($encrypt) {
|
||||
case 'chacha20-poly1305@openssh.com':
|
||||
$encryptKeyLength = 32;
|
||||
$this->lengthEncrypt = $this->encryption_algorithm_to_crypt_instance($encrypt);
|
||||
$this->lengthEncrypt->setKey(substr($key, 32, 32));
|
||||
}
|
||||
$this->encrypt->setKey(substr($key, 0, $encryptKeyLength));
|
||||
$this->encrypt->name = $encrypt;
|
||||
}
|
||||
@ -1918,19 +1955,29 @@ 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);
|
||||
switch ($encrypt) {
|
||||
case 'aes128-gcm@openssh.com':
|
||||
case 'aes256-gcm@openssh.com':
|
||||
// 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);
|
||||
case 'chacha20-poly1305@openssh.com':
|
||||
break;
|
||||
default:
|
||||
$this->decrypt->enableContinuousBuffer();
|
||||
}
|
||||
|
||||
$key = $kexHash->hash($keyBytes . $this->exchange_hash . 'D' . $this->session_id);
|
||||
while ($decryptKeyLength > strlen($key)) {
|
||||
$key.= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
|
||||
}
|
||||
switch ($decrypt) {
|
||||
case 'chacha20-poly1305@openssh.com':
|
||||
$decryptKeyLength = 32;
|
||||
$this->lengthDecrypt = $this->encryption_algorithm_to_crypt_instance($decrypt);
|
||||
$this->lengthDecrypt->setKey(substr($key, 32, 32));
|
||||
}
|
||||
$this->decrypt->setKey(substr($key, 0, $decryptKeyLength));
|
||||
$this->decrypt->name = $decrypt;
|
||||
}
|
||||
@ -2097,6 +2144,8 @@ class SSH2
|
||||
case 'twofish256-cbc':
|
||||
case 'twofish256-ctr':
|
||||
return 32;
|
||||
case 'chacha20-poly1305@openssh.com':
|
||||
return 64;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -2144,6 +2193,8 @@ class SSH2
|
||||
case 'aes128-gcm@openssh.com':
|
||||
case 'aes256-gcm@openssh.com':
|
||||
return new AES('gcm');
|
||||
case 'chacha20-poly1305@openssh.com':
|
||||
return new ChaCha20();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -3409,28 +3460,59 @@ class SSH2
|
||||
}
|
||||
|
||||
if ($this->decrypt) {
|
||||
// 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
|
||||
*/
|
||||
switch ($this->decrypt->name) {
|
||||
case 'aes128-gcm@openssh.com':
|
||||
case 'aes256-gcm@openssh.com':
|
||||
$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;
|
||||
$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;
|
||||
break;
|
||||
case 'chacha20-poly1305@openssh.com':
|
||||
$nonce = pack('N2', 0, $this->get_seq_no);
|
||||
|
||||
$this->lengthDecrypt->setNonce($nonce);
|
||||
$temp = $this->lengthDecrypt->decrypt($aad = 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, 16);
|
||||
|
||||
$this->decrypt->setNonce($nonce);
|
||||
$this->decrypt->setCounter(0);
|
||||
// this is the same approach that's implemented in Salsa20::createPoly1305Key()
|
||||
// but we don't want to use the same AEAD construction that RFC8439 describes
|
||||
// for ChaCha20-Poly1305 so we won't rely on it (see Salsa20::poly1305())
|
||||
$this->decrypt->setPoly1305Key(
|
||||
$this->decrypt->encrypt(str_repeat("\0", 32))
|
||||
);
|
||||
$this->decrypt->setAAD($aad);
|
||||
$this->decrypt->setCounter(1);
|
||||
$this->decrypt->setTag($tag);
|
||||
$raw = $this->decrypt->decrypt($raw);
|
||||
$raw = $temp . $raw;
|
||||
$remaining_length = 0;
|
||||
break;
|
||||
default:
|
||||
$raw = $this->decrypt->decrypt($raw);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3447,19 +3529,6 @@ class SSH2
|
||||
$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 && self::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;
|
||||
}
|
||||
throw new \RuntimeException('Invalid size');
|
||||
}
|
||||
|
||||
$buffer = $this->read_remaining_bytes($remaining_length);
|
||||
|
||||
if (!isset($stop)) {
|
||||
@ -3510,6 +3579,38 @@ class SSH2
|
||||
*/
|
||||
private function read_remaining_bytes($remaining_length)
|
||||
{
|
||||
if (!$remaining_length) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$adjustLength = false;
|
||||
if ($this->decrypt) {
|
||||
switch ($this->decrypt->name) {
|
||||
case 'aes128-gcm@openssh.com':
|
||||
case 'aes256-gcm@openssh.com':
|
||||
case 'chacha20-poly1305@openssh.com':
|
||||
$remaining_length+= $this->decrypt_block_size - 4;
|
||||
$adjustLength = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 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 && self::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;
|
||||
}
|
||||
throw new \RuntimeException('Invalid size');
|
||||
}
|
||||
|
||||
if ($adjustLength) {
|
||||
$remaining_length-= $this->decrypt_block_size - 4;
|
||||
}
|
||||
|
||||
$buffer = '';
|
||||
while ($remaining_length > 0) {
|
||||
$temp = stream_get_contents($this->fsock, $remaining_length);
|
||||
@ -4113,16 +4214,38 @@ class SSH2
|
||||
$this->send_seq_no++;
|
||||
|
||||
if ($this->encrypt) {
|
||||
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));
|
||||
switch ($this->encrypt->name) {
|
||||
case 'aes128-gcm@openssh.com':
|
||||
case 'aes256-gcm@openssh.com':
|
||||
$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));
|
||||
break;
|
||||
case 'chacha20-poly1305@openssh.com':
|
||||
$nonce = pack('N2', 0, $this->send_seq_no - 1);
|
||||
|
||||
$this->encrypt->setNonce($nonce);
|
||||
$this->lengthEncrypt->setNonce($nonce);
|
||||
|
||||
$length = $this->lengthEncrypt->encrypt(substr($packet, 0, 4));
|
||||
|
||||
$this->encrypt->setCounter(0);
|
||||
// this is the same approach that's implemented in Salsa20::createPoly1305Key()
|
||||
// but we don't want to use the same AEAD construction that RFC8439 describes
|
||||
// for ChaCha20-Poly1305 so we won't rely on it (see Salsa20::poly1305())
|
||||
$this->encrypt->setPoly1305Key(
|
||||
$this->encrypt->encrypt(str_repeat("\0", 32))
|
||||
);
|
||||
$this->encrypt->setAAD($length);
|
||||
$this->encrypt->setCounter(1);
|
||||
$packet = $length . $this->encrypt->encrypt(substr($packet, 4));
|
||||
break;
|
||||
default:
|
||||
$packet = $this->encrypt->encrypt($packet);
|
||||
}
|
||||
}
|
||||
|
||||
|
218
tests/Unit/Crypt/ChaCha20.php
Normal file
218
tests/Unit/Crypt/ChaCha20.php
Normal file
@ -0,0 +1,218 @@
|
||||
<?php
|
||||
/**
|
||||
* @author Jim Wigginton <terrafrost@php.net>
|
||||
* @copyright 2014 Jim Wigginton
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
*/
|
||||
|
||||
use phpseclib\Crypt\ChaCha20;
|
||||
|
||||
class Unit_Crypt_ChaCha20Test extends PhpseclibTestCase
|
||||
{
|
||||
// see https://tools.ietf.org/html/rfc8439#section-2.3.2
|
||||
public function test232()
|
||||
{
|
||||
$key = implode('', range("\00", "\x1f"));
|
||||
|
||||
$nonce = '00:00:00:09:00:00:00:4a:00:00:00:00';
|
||||
$nonce = str_replace(':', '', $nonce);
|
||||
$nonce = pack('H*', $nonce);
|
||||
|
||||
$expected = '10 f1 e7 e4 d1 3b 59 15 50 0f dd 1f a3 20 71 c4' .
|
||||
'c7 d1 f4 c7 33 c0 68 03 04 22 aa 9a c3 d4 6c 4e' .
|
||||
'd2 82 64 46 07 9f aa 09 14 c2 d7 05 d9 8b 02 a2' .
|
||||
'b5 12 9c d1 de 16 4e b9 cb d0 83 e8 a2 50 3c 4e';
|
||||
$expected = str_replace(' ', '', $expected);
|
||||
$expected = pack('H*', $expected);
|
||||
|
||||
$engines = ['PHP', OpenSSL', 'libsodium'];
|
||||
for ($engines as $engine) {
|
||||
$c = new ChaCha20();
|
||||
$c->setKey($key);
|
||||
$c->setNonce($nonce);
|
||||
$c->setCounter(1);
|
||||
$c->setPreferredEngine($engine);
|
||||
if ($c->getEngine() != $engine) {
|
||||
continue;
|
||||
}
|
||||
$result = $c->encrypt(str_repeat("\0", 64));
|
||||
$this->assertSame($expected, $result, "Failed asserting that ciphertext matches expected value with $engine engine");
|
||||
}
|
||||
}
|
||||
|
||||
// see https://tools.ietf.org/html/rfc8439#section-2.4.2
|
||||
public function test242()
|
||||
{
|
||||
$key = implode('', range("\00", "\x1f"));
|
||||
|
||||
$nonce = '00:00:00:00:00:00:00:4a:00:00:00:00';
|
||||
$nonce = str_replace(':', '', $nonce);
|
||||
$nonce = pack('H*', $nonce);
|
||||
|
||||
$plaintext = 'Ladies and Gentlemen of the class of \'99: If I could offer you only one tip for the future,' .
|
||||
' sunscreen would be it.';
|
||||
|
||||
$expected = '6e 2e 35 9a 25 68 f9 80 41 ba 07 28 dd 0d 69 81' .
|
||||
'e9 7e 7a ec 1d 43 60 c2 0a 27 af cc fd 9f ae 0b' .
|
||||
'f9 1b 65 c5 52 47 33 ab 8f 59 3d ab cd 62 b3 57' .
|
||||
'16 39 d6 24 e6 51 52 ab 8f 53 0c 35 9f 08 61 d8' .
|
||||
'07 ca 0d bf 50 0d 6a 61 56 a3 8e 08 8a 22 b6 5e' .
|
||||
'52 bc 51 4d 16 cc f8 06 81 8c e9 1a b7 79 37 36' .
|
||||
'5a f9 0b bf 74 a3 5b e6 b4 0b 8e ed f2 78 5e 42' .
|
||||
'87 4d';
|
||||
$expected = str_replace(' ', '', $expected);
|
||||
$expected = pack('H*', $expected);
|
||||
|
||||
$engines = ['PHP', OpenSSL', 'libsodium'];
|
||||
for ($engines as $engine) {
|
||||
$c = new ChaCha20();
|
||||
$c->setKey($key);
|
||||
$c->setNonce($nonce);
|
||||
$c->setCounter(1);
|
||||
$c->setPreferredEngine($engine);
|
||||
if ($c->getEngine() != $engine) {
|
||||
continue;
|
||||
}
|
||||
$result = $c->encrypt($plaintext);
|
||||
$this->assertSame($expected, $result, "Failed asserting that ciphertext matches expected value with $engine engine");
|
||||
}
|
||||
}
|
||||
|
||||
// see https://tools.ietf.org/html/rfc8439#section-2.5.2
|
||||
public function test252()
|
||||
{
|
||||
$key = '85:d6:be:78:57:55:6d:33:7f:44:52:fe:42:d5:06:a8:01:0' .
|
||||
'3:80:8a:fb:0d:b2:fd:4a:bf:f6:af:41:49:f5:1b';
|
||||
$key = str_replace(':', '', $key);
|
||||
$key = pack('H*', $key);
|
||||
|
||||
$plaintext = 'Cryptographic Forum Research Group';
|
||||
|
||||
$expected = 'a8:06:1d:c1:30:51:36:c6:c2:2b:8b:af:0c:01:27:a9';
|
||||
$expected = str_replace(':', '', $expected);
|
||||
$expected = pack('H*', $expected);
|
||||
|
||||
$c = new ChaCha20;
|
||||
$r = new \ReflectionClass(get_class($c));
|
||||
$p = $r->getProperty('poly1305Key');
|
||||
$p->setAccessible(true);
|
||||
$p->setValue($c, $key);
|
||||
|
||||
$m = $r->getMethod('poly1305');
|
||||
$m->setAccessible(true);
|
||||
$result = $m->invokeArgs($c, [$plaintext]);
|
||||
|
||||
$this->assertSame($expected, $result, 'Failed asserting that poly1305 matches expected value');
|
||||
}
|
||||
|
||||
// see https://tools.ietf.org/html/rfc8439#section-2.6.2
|
||||
public function test262()
|
||||
{
|
||||
$key = implode('', range("\80", "\x9f"));
|
||||
|
||||
$nonce = '00 00 00 00 00 01 02 03 04 05 06 07';
|
||||
$nonce = str_replace(' ', '', $nonce);
|
||||
$nonce = pack('H*', $nonce);
|
||||
|
||||
$expected = '8a d5 a0 8b 90 5f 81 cc 81 50 40 27 4a b2 94 71' .
|
||||
'a8 33 b6 37 e3 fd 0d a5 08 db b8 e2 fd d1 a6 46';
|
||||
$expected = str_replace(' ', '', $expected);
|
||||
$expected = pack('H*', $expected);
|
||||
|
||||
$engines = ['PHP', OpenSSL', 'libsodium'];
|
||||
for ($engines as $engine) {
|
||||
$c = new ChaCha20();
|
||||
$c->setKey($key);
|
||||
$c->setNonce($nonce);
|
||||
//$c->setCounter(0);
|
||||
$c->setPreferredEngine($engine);
|
||||
if ($c->getEngine() != $engine) {
|
||||
continue;
|
||||
}
|
||||
$result = $c->encrypt($plaintext);
|
||||
$this->assertSame($expected, $result, "Failed asserting that ciphertext matches expected value with $engine engine");
|
||||
}
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc8439#section-2.8.2
|
||||
public function test282()
|
||||
{
|
||||
$key = implode('', range("\80", "\x9f"));
|
||||
|
||||
$nonce = "\x07\0\0\0" . "\x40\x41\x42\x43\x44\x45\x46\x47";
|
||||
|
||||
$plaintext = 'Ladies and Gentlemen of the class of \'99: If I could offer you only one tip for the future,' .
|
||||
' sunscreen would be it.';
|
||||
|
||||
$aad = '50 51 52 53 c0 c1 c2 c3 c4 c5 c6 c7';
|
||||
$aad = str_replace(' ', '', $aad);
|
||||
$aad = pack('H*', $aad);
|
||||
|
||||
$expected = 'd3 1a 8d 34 64 8e 60 db 7b 86 af bc 53 ef 7e c2' .
|
||||
'a4 ad ed 51 29 6e 08 fe a9 e2 b5 a7 36 ee 62 d6' .
|
||||
'3d be a4 5e 8c a9 67 12 82 fa fb 69 da 92 72 8b' .
|
||||
'1a 71 de 0a 9e 06 0b 29 05 d6 a5 b6 7e cd 3b 36' .
|
||||
'92 dd bd 7f 2d 77 8b 8c 98 03 ae e3 28 09 1b 58' .
|
||||
'fa b3 24 e4 fa d6 75 94 55 85 80 8b 48 31 d7 bc' .
|
||||
'3f f4 de f0 8e 4b 7a 9d e5 76 d2 65 86 ce c6 4b' .
|
||||
'61 16';
|
||||
$expected = str_replace(' ', '', $expected);
|
||||
$expected = pack('H*', $expected);
|
||||
|
||||
$tag = '1a:e1:0b:59:4f:09:e2:6a:7e:90:2e:cb:d0:60:06:91';
|
||||
$tag = str_replace(' ', '', $tag);
|
||||
$tag = pack('H*', $tag);
|
||||
|
||||
$engines = ['PHP', OpenSSL', 'libsodium'];
|
||||
for ($engines as $engine) {
|
||||
$c = new ChaCha20();
|
||||
$c->enablePoly1305();
|
||||
$c->setKey($key);
|
||||
$c->setNonce($nonce);
|
||||
$c->setAAD($aad);
|
||||
$c->setPreferredEngine($engine);
|
||||
if ($c->getEngine() != $engine) {
|
||||
continue;
|
||||
}
|
||||
$result = $c->encrypt($plaintext);
|
||||
$this->assertSame($expected, $result, "Failed asserting that ciphertext matches expected value with $engine engine");
|
||||
$this->assertSame($tag, $c->getTag(), "Failed asserting that the tag matches the expected value with $engine engine");
|
||||
}
|
||||
}
|
||||
|
||||
public function testContinuousBuffer()
|
||||
{
|
||||
$key = str_repeat("\0", 16);
|
||||
$nonce = str_repeat("\0", 8);
|
||||
|
||||
$partitions = [1, 63, 70];
|
||||
|
||||
$plaintext = str_repeat("\0", array_sum($partitions));
|
||||
|
||||
$engines = ['PHP', OpenSSL', 'libsodium'];
|
||||
for ($engines as $engine) {
|
||||
$c = new ChaCha20();
|
||||
$c->setKey($key);
|
||||
$c->setNonce($nonce);
|
||||
$c->setPreferredEngine($engine);
|
||||
|
||||
$c2 = new ChaCha20();
|
||||
$c2->setKey($key);
|
||||
$c2->setNonce($nonce);
|
||||
$c2->setPreferredEngine($engine);
|
||||
$c2->enableContinuousBuffer();
|
||||
|
||||
if ($c2->getEngine() != $engine) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$p1 = $c->encrypt($plaintext);
|
||||
$p2 = '';
|
||||
foreach ($partitions as $partition) {
|
||||
$p2.= $c2->encrypt(str_repeat("\0", $partition));
|
||||
}
|
||||
|
||||
$this->assertSame($p1, $p2, "Failed asserting that ciphertext matches expected value with $engine engine");
|
||||
}
|
||||
}
|
||||
}
|
160
tests/Unit/Crypt/Salsa20.php
Normal file
160
tests/Unit/Crypt/Salsa20.php
Normal file
@ -0,0 +1,160 @@
|
||||
<?php
|
||||
/**
|
||||
* @author Jim Wigginton <terrafrost@php.net>
|
||||
* @copyright 2014 Jim Wigginton
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
*/
|
||||
|
||||
use phpseclib\Crypt\Salsa20;
|
||||
|
||||
class Unit_Crypt_Salsa20Test extends PhpseclibTestCase
|
||||
{
|
||||
public function engineVectors()
|
||||
{
|
||||
$engines = [
|
||||
'PHP',
|
||||
];
|
||||
// tests from http://www.ecrypt.eu.org/stream/svn/viewcvs.cgi/ecrypt/trunk/submissions/salsa20/full/verified.test-vectors?logsort=rev&rev=210&view=markup
|
||||
// more specifically, it's vector # 0 in each set
|
||||
$tests = [
|
||||
// key size: 128 bits
|
||||
// set 1
|
||||
[
|
||||
'key' => '80000000000000000000000000000000',
|
||||
'iv' => '0000000000000000',
|
||||
'result' => 'F7A274D268316790A67EC058F45C0F2A' .
|
||||
'067A99FCDE6236C0CEF8E056349FE54C' .
|
||||
'5F13AC74D2539570FD34FEAB06C57205' .
|
||||
'3949B59585742181A5A760223AFA22D4'
|
||||
],
|
||||
// set 2
|
||||
[
|
||||
'key' => '00000000000000000000000000000000',
|
||||
'iv' => '0000000000000000',
|
||||
'result' => '6D3937FFA13637648E477623277644AD' .
|
||||
'AD3854E6B2B3E4D68155356F68B30490' .
|
||||
'842B2AEA2E32239BE84E613C6CE1B9BD' .
|
||||
'026094962CB1A6757AF5A13DDAF8252C'
|
||||
],
|
||||
// set 3
|
||||
[
|
||||
'key' => '000102030405060708090A0B0C0D0E0F',
|
||||
'iv' => '0000000000000000',
|
||||
'result' => 'F3BCF4D6381742839C5627050D4B227F' .
|
||||
'EB1ECCC527BF605C4CB9D6FB0618F419' .
|
||||
'B51846707550BBEEE381E44A50A406D0' .
|
||||
'20C8433D08B19C98EFC867ED9897EDBB'
|
||||
],
|
||||
// set 4
|
||||
[
|
||||
'key' => '0053A6F94C9FF24598EB3E91E4378ADD',
|
||||
'iv' => '0000000000000000',
|
||||
'result' => '196D1A0977F0585B23367497D449E11D' .
|
||||
'E328ECD944BC133F786348C9591B35B7' .
|
||||
'189CDDD934757ED8F18FBC984DA377A8' .
|
||||
'07147F1A6A9A8759FD2A062FD76D275E'
|
||||
],
|
||||
// set 5
|
||||
[
|
||||
'key' => '00000000000000000000000000000000',
|
||||
'iv' => '8000000000000000',
|
||||
'result' => '104639D9F65C879F7DFF8A82A94C130C' .
|
||||
'D6C727B3BC8127943ACDF0AB7AD6D28B' .
|
||||
'F2ADF50D81F50C53D0FDFE15803854C7' .
|
||||
'D67F6C9B4752275696E370A467A4C1F8'
|
||||
],
|
||||
// set 6
|
||||
[
|
||||
'key' => '0053A6F94C9FF24598EB3E91E4378ADD',
|
||||
'iv' => '0D74DB42A91077DE',
|
||||
'result' => '620BB4C2ED20F4152F0F86053D3F5595' .
|
||||
'8E1FBA48F5D86B25C8F31559F3158072' .
|
||||
'6E7ED8525D0B9EA5264BF97750713476' .
|
||||
'1EF65FE195274AFBF000938C03BA59A7'
|
||||
],
|
||||
// key size: 256 bits
|
||||
// set 1
|
||||
[
|
||||
'key' => '8000000000000000000000000000000000000000000000000000000000000000',
|
||||
'iv' => '0000000000000000',
|
||||
'result' => '50EC2485637DB19C6E795E9C73938280' .
|
||||
'6F6DB320FE3D0444D56707D7B456457F' .
|
||||
'3DB3E8D7065AF375A225A70951C8AB74' .
|
||||
'4EC4D595E85225F08E2BC03FE1C42567'
|
||||
],
|
||||
// set 2
|
||||
[
|
||||
'key' => '0000000000000000000000000000000000000000000000000000000000000000',
|
||||
'iv' => '0000000000000000',
|
||||
'result' => '7C3A1499A63B507B0BC75824ABEEAA26' .
|
||||
'109101C5B915F0F554DD9950045D02FA' .
|
||||
'FF815CA8B2C7CFF3625765697B80B026' .
|
||||
'7EA87E25412564BD71DD05843A60465E'
|
||||
],
|
||||
// set 3
|
||||
[
|
||||
'key' => '000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F',
|
||||
'iv' => '0000000000000000',
|
||||
'result' => '8C03E9237FEE95D5041C753C204D2B35' .
|
||||
'764E4A53035A76F9EFBADD7E63E60B69' .
|
||||
'BF23F7C5FD39B2249B0C628FB654D521' .
|
||||
'4EB588371E5D2F34BF51396AF3ACB666'
|
||||
],
|
||||
// set 4
|
||||
[
|
||||
'key' => '0053A6F94C9FF24598EB3E91E4378ADD3083D6297CCF2275C81B6EC11467BA0D',
|
||||
'iv' => '0000000000000000',
|
||||
'result' => '2052F9A2853E989133D10938222AC76D' .
|
||||
'B8B4CBA135ACB59970DDF9C074C6271A' .
|
||||
'5C4E2A7A00D2D697EDFC9B1FF9B365C8' .
|
||||
'7347B23020663A30711A71E3A02AB00C'
|
||||
],
|
||||
// set 5
|
||||
[
|
||||
'key' => '0000000000000000000000000000000000000000000000000000000000000000',
|
||||
'iv' => '8000000000000000',
|
||||
'result' => 'FE40F57D1586D7664C2FCA5AB10BD7C7' .
|
||||
'9DE3234836E76949F9DC01CBFABC6D6C' .
|
||||
'42AB27DDC748B4DF7991092972AB4985' .
|
||||
'CEC19B3E7C2C85D6E25A338DEC288282'
|
||||
],
|
||||
// set 6
|
||||
[
|
||||
'key' => '0053A6F94C9FF24598EB3E91E4378ADD3083D6297CCF2275C81B6EC11467BA0D',
|
||||
'iv' => '0D74DB42A91077DE',
|
||||
'result' => 'C349B6A51A3EC9B712EAED3F90D8BCEE' .
|
||||
'69B7628645F251A996F55260C62EF31F' .
|
||||
'D6C6B0AEA94E136C9D984AD2DF3578F7' .
|
||||
'8E457527B03A0450580DD874F63B1AB9'
|
||||
],
|
||||
];
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($engines as $engine) {
|
||||
foreach ($tests as $test) {
|
||||
foreach ($test['output'] as $output) {
|
||||
$result[] = [$engine, $test['key'], $output['iv'], $output['result']];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider engineVectors
|
||||
*/
|
||||
public function testVectors($engine, $key, $iv, $expected)
|
||||
{
|
||||
$cipher = new Salsa();
|
||||
$cipher->setPreferredEngine($engine);
|
||||
$cipher->setKey(pack('H*', $key));
|
||||
$cipher->setNonce(pack('H*', $iv));
|
||||
if ($cipher->getEngine() != $engine) {
|
||||
self::markTestSkipped('Unable to initialize ' . $engine . ' engine for ' . (strlen($key) * 8) . '-bit key');
|
||||
}
|
||||
$result = $cipher->encrypt(str_repeat("\0", 64);
|
||||
$this->assertEquals(bin2hex($result), $expected, "Failed asserting that key $key / $iv yielded expected output in $engine engine");
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user