* @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 * @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); } }