1<?php
2
3/**
4 * Pure-PHP implementation of ChaCha20.
5 *
6 * PHP version 5
7 *
8 * @author    Jim Wigginton <terrafrost@php.net>
9 * @copyright 2019 Jim Wigginton
10 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
11 * @link      http://phpseclib.sourceforge.net
12 */
13
14namespace phpseclib3\Crypt;
15
16use phpseclib3\Exception\BadDecryptionException;
17use phpseclib3\Exception\InsufficientSetupException;
18
19/**
20 * Pure-PHP implementation of ChaCha20.
21 *
22 * @author  Jim Wigginton <terrafrost@php.net>
23 */
24class ChaCha20 extends Salsa20
25{
26    /**
27     * The OpenSSL specific name of the cipher
28     *
29     * @var string
30     */
31    protected $cipher_name_openssl = 'chacha20';
32
33    /**
34     * Test for engine validity
35     *
36     * This is mainly just a wrapper to set things up for \phpseclib3\Crypt\Common\SymmetricKey::isValidEngine()
37     *
38     * @see \phpseclib3\Crypt\Common\SymmetricKey::__construct()
39     * @param int $engine
40     * @return bool
41     */
42    protected function isValidEngineHelper($engine)
43    {
44        switch ($engine) {
45            case self::ENGINE_LIBSODIUM:
46                // PHP 7.2.0 (30 Nov 2017) added support for libsodium
47
48                // we could probably make it so that if $this->counter == 0 then the first block would be done with either OpenSSL
49                // or PHP and then subsequent blocks would then be done with libsodium but idk - it's not a high priority atm
50
51                // we could also make it so that if $this->counter == 0 and $this->continuousBuffer then do the first string
52                // with libsodium and subsequent strings with openssl or pure-PHP but again not a high priority
53                return function_exists('sodium_crypto_aead_chacha20poly1305_ietf_encrypt') &&
54                       $this->key_length == 32 &&
55                       (($this->usePoly1305 && !isset($this->poly1305Key) && $this->counter == 0) || $this->counter == 1) &&
56                       !$this->continuousBuffer;
57            case self::ENGINE_OPENSSL:
58                // OpenSSL 1.1.0 (released 25 Aug 2016) added support for chacha20.
59                // PHP didn't support OpenSSL 1.1.0 until 7.0.19 (11 May 2017)
60
61                // if you attempt to provide openssl with a 128 bit key (as opposed to a 256 bit key) openssl will null
62                // pad the key to 256 bits and still use the expansion constant for 256-bit keys. the fact that
63                // openssl treats the IV as both the counter and nonce, however, let's us use openssl in continuous mode
64                // whereas libsodium does not
65                if ($this->key_length != 32) {
66                    return false;
67                }
68        }
69
70        return parent::isValidEngineHelper($engine);
71    }
72
73    /**
74     * Encrypts a message.
75     *
76     * @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt()
77     * @see self::crypt()
78     * @param string $plaintext
79     * @return string $ciphertext
80     */
81    public function encrypt($plaintext)
82    {
83        $this->setup();
84
85        if ($this->engine == self::ENGINE_LIBSODIUM) {
86            return $this->encrypt_with_libsodium($plaintext);
87        }
88
89        return parent::encrypt($plaintext);
90    }
91
92    /**
93     * Decrypts a message.
94     *
95     * $this->decrypt($this->encrypt($plaintext)) == $this->encrypt($this->encrypt($plaintext)).
96     * At least if the continuous buffer is disabled.
97     *
98     * @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt()
99     * @see self::crypt()
100     * @param string $ciphertext
101     * @return string $plaintext
102     */
103    public function decrypt($ciphertext)
104    {
105        $this->setup();
106
107        if ($this->engine == self::ENGINE_LIBSODIUM) {
108            return $this->decrypt_with_libsodium($ciphertext);
109        }
110
111        return parent::decrypt($ciphertext);
112    }
113
114    /**
115     * Encrypts a message with libsodium
116     *
117     * @see self::encrypt()
118     * @param string $plaintext
119     * @return string $text
120     */
121    private function encrypt_with_libsodium($plaintext)
122    {
123        $params = [$plaintext, $this->aad, $this->nonce, $this->key];
124        $ciphertext = strlen($this->nonce) == 8 ?
125            sodium_crypto_aead_chacha20poly1305_encrypt(...$params) :
126            sodium_crypto_aead_chacha20poly1305_ietf_encrypt(...$params);
127        if (!$this->usePoly1305) {
128            return substr($ciphertext, 0, strlen($plaintext));
129        }
130
131        $newciphertext = substr($ciphertext, 0, strlen($plaintext));
132
133        $this->newtag = $this->usingGeneratedPoly1305Key && strlen($this->nonce) == 12 ?
134            substr($ciphertext, strlen($plaintext)) :
135            $this->poly1305($newciphertext);
136
137        return $newciphertext;
138    }
139
140    /**
141     * Decrypts a message with libsodium
142     *
143     * @see self::decrypt()
144     * @param string $ciphertext
145     * @return string $text
146     */
147    private function decrypt_with_libsodium($ciphertext)
148    {
149        $params = [$ciphertext, $this->aad, $this->nonce, $this->key];
150
151        if (isset($this->poly1305Key)) {
152            if ($this->oldtag === false) {
153                throw new InsufficientSetupException('Authentication Tag has not been set');
154            }
155            if ($this->usingGeneratedPoly1305Key && strlen($this->nonce) == 12) {
156                $plaintext = sodium_crypto_aead_chacha20poly1305_ietf_decrypt(...$params);
157                $this->oldtag = false;
158                if ($plaintext === false) {
159                    throw new BadDecryptionException('Derived authentication tag and supplied authentication tag do not match');
160                }
161                return $plaintext;
162            }
163            $newtag = $this->poly1305($ciphertext);
164            if ($this->oldtag != substr($newtag, 0, strlen($this->oldtag))) {
165                $this->oldtag = false;
166                throw new BadDecryptionException('Derived authentication tag and supplied authentication tag do not match');
167            }
168            $this->oldtag = false;
169        }
170
171        $plaintext = strlen($this->nonce) == 8 ?
172            sodium_crypto_aead_chacha20poly1305_encrypt(...$params) :
173            sodium_crypto_aead_chacha20poly1305_ietf_encrypt(...$params);
174
175        return substr($plaintext, 0, strlen($ciphertext));
176    }
177
178    /**
179     * Sets the nonce.
180     *
181     * @param string $nonce
182     */
183    public function setNonce($nonce)
184    {
185        if (!is_string($nonce)) {
186            throw new \UnexpectedValueException('The nonce should be a string');
187        }
188
189        /*
190          from https://tools.ietf.org/html/rfc7539#page-7
191
192          "Note also that the original ChaCha had a 64-bit nonce and 64-bit
193           block count.  We have modified this here to be more consistent with
194           recommendations in Section 3.2 of [RFC5116]."
195         */
196        switch (strlen($nonce)) {
197            case 8:  // 64 bits
198            case 12: // 96 bits
199                break;
200            default:
201                throw new \LengthException('Nonce of size ' . strlen($nonce) . ' not supported by this algorithm. Only 64-bit nonces or 96-bit nonces are supported');
202        }
203
204        $this->nonce = $nonce;
205        $this->changed = true;
206        $this->setEngine();
207    }
208
209    /**
210     * Setup the self::ENGINE_INTERNAL $engine
211     *
212     * (re)init, if necessary, the internal cipher $engine
213     *
214     * _setup() will be called each time if $changed === true
215     * typically this happens when using one or more of following public methods:
216     *
217     * - setKey()
218     *
219     * - setNonce()
220     *
221     * - First run of encrypt() / decrypt() with no init-settings
222     *
223     * @see self::setKey()
224     * @see self::setNonce()
225     * @see self::disableContinuousBuffer()
226     */
227    protected function setup()
228    {
229        if (!$this->changed) {
230            return;
231        }
232
233        $this->enbuffer = $this->debuffer = ['ciphertext' => '', 'counter' => $this->counter];
234
235        $this->changed = $this->nonIVChanged = false;
236
237        if ($this->nonce === false) {
238            throw new InsufficientSetupException('No nonce has been defined');
239        }
240
241        if ($this->key === false) {
242            throw new InsufficientSetupException('No key has been defined');
243        }
244
245        if ($this->usePoly1305 && !isset($this->poly1305Key)) {
246            $this->usingGeneratedPoly1305Key = true;
247            if ($this->engine == self::ENGINE_LIBSODIUM) {
248                return;
249            }
250            $this->createPoly1305Key();
251        }
252
253        $key = $this->key;
254        if (strlen($key) == 16) {
255            $constant = 'expand 16-byte k';
256            $key .= $key;
257        } else {
258            $constant = 'expand 32-byte k';
259        }
260
261        $this->p1 = $constant . $key;
262        $this->p2 = $this->nonce;
263        if (strlen($this->nonce) == 8) {
264            $this->p2 = "\0\0\0\0" . $this->p2;
265        }
266    }
267
268    /**
269     * The quarterround function
270     *
271     * @param int $a
272     * @param int $b
273     * @param int $c
274     * @param int $d
275     */
276    protected static function quarterRound(&$a, &$b, &$c, &$d)
277    {
278        // in https://datatracker.ietf.org/doc/html/rfc7539#section-2.1 the addition,
279        // xor'ing and rotation are all on the same line so i'm keeping it on the same
280        // line here as well
281        // @codingStandardsIgnoreStart
282        $a+= $b; $d = self::leftRotate(intval($d) ^ intval($a), 16);
283        $c+= $d; $b = self::leftRotate(intval($b) ^ intval($c), 12);
284        $a+= $b; $d = self::leftRotate(intval($d) ^ intval($a), 8);
285        $c+= $d; $b = self::leftRotate(intval($b) ^ intval($c), 7);
286        // @codingStandardsIgnoreEnd
287    }
288
289    /**
290     * The doubleround function
291     *
292     * @param int $x0 (by reference)
293     * @param int $x1 (by reference)
294     * @param int $x2 (by reference)
295     * @param int $x3 (by reference)
296     * @param int $x4 (by reference)
297     * @param int $x5 (by reference)
298     * @param int $x6 (by reference)
299     * @param int $x7 (by reference)
300     * @param int $x8 (by reference)
301     * @param int $x9 (by reference)
302     * @param int $x10 (by reference)
303     * @param int $x11 (by reference)
304     * @param int $x12 (by reference)
305     * @param int $x13 (by reference)
306     * @param int $x14 (by reference)
307     * @param int $x15 (by reference)
308     */
309    protected static function doubleRound(&$x0, &$x1, &$x2, &$x3, &$x4, &$x5, &$x6, &$x7, &$x8, &$x9, &$x10, &$x11, &$x12, &$x13, &$x14, &$x15)
310    {
311        // columnRound
312        static::quarterRound($x0, $x4, $x8, $x12);
313        static::quarterRound($x1, $x5, $x9, $x13);
314        static::quarterRound($x2, $x6, $x10, $x14);
315        static::quarterRound($x3, $x7, $x11, $x15);
316        // rowRound
317        static::quarterRound($x0, $x5, $x10, $x15);
318        static::quarterRound($x1, $x6, $x11, $x12);
319        static::quarterRound($x2, $x7, $x8, $x13);
320        static::quarterRound($x3, $x4, $x9, $x14);
321    }
322
323    /**
324     * The Salsa20 hash function function
325     *
326     * On my laptop this loop unrolled / function dereferenced version of parent::salsa20 encrypts 1mb of text in
327     * 0.65s vs the 0.85s that it takes with the parent method.
328     *
329     * If we were free to assume that the host OS would always be 64-bits then the if condition in leftRotate could
330     * be eliminated and we could knock this done to 0.60s.
331     *
332     * For comparison purposes, RC4 takes 0.16s and AES in CTR mode with the Eval engine takes 0.48s.
333     * AES in CTR mode with the PHP engine takes 1.19s. Salsa20 / ChaCha20 do not benefit as much from the Eval
334     * approach due to the fact that there are a lot less variables to de-reference, fewer loops to unroll, etc
335     *
336     * @param string $x
337     */
338    protected static function salsa20($x)
339    {
340        list(, $x0, $x1, $x2, $x3, $x4, $x5, $x6, $x7, $x8, $x9, $x10, $x11, $x12, $x13, $x14, $x15) = unpack('V*', $x);
341        $z0 = $x0;
342        $z1 = $x1;
343        $z2 = $x2;
344        $z3 = $x3;
345        $z4 = $x4;
346        $z5 = $x5;
347        $z6 = $x6;
348        $z7 = $x7;
349        $z8 = $x8;
350        $z9 = $x9;
351        $z10 = $x10;
352        $z11 = $x11;
353        $z12 = $x12;
354        $z13 = $x13;
355        $z14 = $x14;
356        $z15 = $x15;
357
358        // @codingStandardsIgnoreStart
359        // columnRound
360        $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
361        $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
362        $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
363        $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
364
365        $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
366        $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
367        $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
368        $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
369
370        $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
371        $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
372        $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
373        $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
374
375        $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
376        $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
377        $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
378        $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
379
380        // rowRound
381        $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
382        $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
383        $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
384        $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
385
386        $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
387        $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
388        $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
389        $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
390
391        $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
392        $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
393        $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
394        $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
395
396        $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
397        $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
398        $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
399        $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
400
401        // columnRound
402        $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
403        $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
404        $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
405        $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
406
407        $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
408        $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
409        $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
410        $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
411
412        $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
413        $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
414        $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
415        $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
416
417        $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
418        $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
419        $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
420        $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
421
422        // rowRound
423        $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
424        $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
425        $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
426        $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
427
428        $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
429        $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
430        $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
431        $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
432
433        $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
434        $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
435        $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
436        $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
437
438        $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
439        $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
440        $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
441        $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
442
443        // columnRound
444        $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
445        $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
446        $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
447        $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
448
449        $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
450        $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
451        $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
452        $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
453
454        $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
455        $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
456        $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
457        $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
458
459        $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
460        $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
461        $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
462        $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
463
464        // rowRound
465        $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
466        $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
467        $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
468        $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
469
470        $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
471        $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
472        $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
473        $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
474
475        $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
476        $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
477        $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
478        $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
479
480        $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
481        $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
482        $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
483        $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
484
485        // columnRound
486        $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
487        $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
488        $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
489        $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
490
491        $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
492        $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
493        $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
494        $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
495
496        $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
497        $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
498        $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
499        $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
500
501        $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
502        $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
503        $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
504        $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
505
506        // rowRound
507        $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
508        $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
509        $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
510        $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
511
512        $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
513        $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
514        $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
515        $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
516
517        $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
518        $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
519        $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
520        $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
521
522        $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
523        $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
524        $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
525        $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
526
527        // columnRound
528        $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
529        $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
530        $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
531        $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
532
533        $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
534        $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
535        $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
536        $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
537
538        $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
539        $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
540        $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
541        $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
542
543        $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
544        $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
545        $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
546        $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
547
548        // rowRound
549        $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
550        $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
551        $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
552        $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
553
554        $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
555        $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
556        $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
557        $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
558
559        $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
560        $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
561        $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
562        $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
563
564        $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
565        $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
566        $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
567        $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
568
569        // columnRound
570        $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
571        $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
572        $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
573        $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
574
575        $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
576        $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
577        $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
578        $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
579
580        $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
581        $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
582        $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
583        $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
584
585        $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
586        $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
587        $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
588        $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
589
590        // rowRound
591        $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
592        $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
593        $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
594        $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
595
596        $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
597        $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
598        $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
599        $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
600
601        $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
602        $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
603        $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
604        $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
605
606        $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
607        $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
608        $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
609        $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
610
611        // columnRound
612        $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
613        $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
614        $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
615        $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
616
617        $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
618        $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
619        $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
620        $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
621
622        $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
623        $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
624        $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
625        $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
626
627        $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
628        $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
629        $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
630        $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
631
632        // rowRound
633        $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
634        $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
635        $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
636        $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
637
638        $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
639        $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
640        $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
641        $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
642
643        $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
644        $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
645        $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
646        $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
647
648        $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
649        $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
650        $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
651        $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
652
653        // columnRound
654        $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
655        $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
656        $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
657        $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
658
659        $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
660        $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
661        $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
662        $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
663
664        $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
665        $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
666        $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
667        $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
668
669        $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
670        $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
671        $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
672        $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
673
674        // rowRound
675        $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
676        $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
677        $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
678        $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
679
680        $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
681        $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
682        $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
683        $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
684
685        $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
686        $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
687        $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
688        $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
689
690        $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
691        $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
692        $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
693        $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
694
695        // columnRound
696        $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
697        $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
698        $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
699        $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
700
701        $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
702        $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
703        $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
704        $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
705
706        $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
707        $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
708        $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
709        $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
710
711        $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
712        $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
713        $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
714        $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
715
716        // rowRound
717        $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
718        $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
719        $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
720        $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
721
722        $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
723        $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
724        $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
725        $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
726
727        $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
728        $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
729        $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
730        $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
731
732        $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
733        $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
734        $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
735        $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
736
737        // columnRound
738        $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
739        $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
740        $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
741        $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
742
743        $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
744        $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
745        $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
746        $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
747
748        $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
749        $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
750        $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
751        $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
752
753        $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
754        $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
755        $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
756        $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
757
758        // rowRound
759        $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
760        $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
761        $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
762        $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
763
764        $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
765        $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
766        $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
767        $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
768
769        $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
770        $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
771        $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
772        $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
773
774        $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
775        $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
776        $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
777        $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
778        // @codingStandardsIgnoreEnd
779
780        $x0 += $z0;
781        $x1 += $z1;
782        $x2 += $z2;
783        $x3 += $z3;
784        $x4 += $z4;
785        $x5 += $z5;
786        $x6 += $z6;
787        $x7 += $z7;
788        $x8 += $z8;
789        $x9 += $z9;
790        $x10 += $z10;
791        $x11 += $z11;
792        $x12 += $z12;
793        $x13 += $z13;
794        $x14 += $z14;
795        $x15 += $z15;
796
797        return pack('V*', $x0, $x1, $x2, $x3, $x4, $x5, $x6, $x7, $x8, $x9, $x10, $x11, $x12, $x13, $x14, $x15);
798    }
799}
800