1
0
mirror of https://github.com/danog/tgseclib.git synced 2024-11-27 04:34:45 +01:00
tgseclib/phpseclib/Crypt/ChaCha20.php
2019-03-22 15:45:36 -05:00

784 lines
31 KiB
PHP

<?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);
}
}