1<?php
2
3/**
4 * Pure-PHP implementation of Salsa20.
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\Common\Functions\Strings;
17use phpseclib3\Crypt\Common\StreamCipher;
18use phpseclib3\Exception\BadDecryptionException;
19use phpseclib3\Exception\InsufficientSetupException;
20
21/**
22 * Pure-PHP implementation of Salsa20.
23 *
24 * @author  Jim Wigginton <terrafrost@php.net>
25 */
26class Salsa20 extends StreamCipher
27{
28    /**
29     * Part 1 of the state
30     *
31     * @var string|false
32     */
33    protected $p1 = false;
34
35    /**
36     * Part 2 of the state
37     *
38     * @var string|false
39     */
40    protected $p2 = false;
41
42    /**
43     * Key Length (in bytes)
44     *
45     * @var int
46     */
47    protected $key_length = 32; // = 256 bits
48
49    /**
50     * @see \phpseclib3\Crypt\Salsa20::crypt()
51     */
52    const ENCRYPT = 0;
53
54    /**
55     * @see \phpseclib3\Crypt\Salsa20::crypt()
56     */
57    const DECRYPT = 1;
58
59    /**
60     * Encryption buffer for continuous mode
61     *
62     * @var array
63     */
64    protected $enbuffer;
65
66    /**
67     * Decryption buffer for continuous mode
68     *
69     * @var array
70     */
71    protected $debuffer;
72
73    /**
74     * Counter
75     *
76     * @var int
77     */
78    protected $counter = 0;
79
80    /**
81     * Using Generated Poly1305 Key
82     *
83     * @var boolean
84     */
85    protected $usingGeneratedPoly1305Key = false;
86
87    /**
88     * Salsa20 uses a nonce
89     *
90     * @return bool
91     */
92    public function usesNonce()
93    {
94        return true;
95    }
96
97    /**
98     * Sets the key.
99     *
100     * @param string $key
101     * @throws \LengthException if the key length isn't supported
102     */
103    public function setKey($key)
104    {
105        switch (strlen($key)) {
106            case 16:
107            case 32:
108                break;
109            default:
110                throw new \LengthException('Key of size ' . strlen($key) . ' not supported by this algorithm. Only keys of sizes 16 or 32 are supported');
111        }
112
113        parent::setKey($key);
114    }
115
116    /**
117     * Sets the nonce.
118     *
119     * @param string $nonce
120     */
121    public function setNonce($nonce)
122    {
123        if (strlen($nonce) != 8) {
124            throw new \LengthException('Nonce of size ' . strlen($key) . ' not supported by this algorithm. Only an 64-bit nonce is supported');
125        }
126
127        $this->nonce = $nonce;
128        $this->changed = true;
129        $this->setEngine();
130    }
131
132    /**
133     * Sets the counter.
134     *
135     * @param int $counter
136     */
137    public function setCounter($counter)
138    {
139        $this->counter = $counter;
140        $this->setEngine();
141    }
142
143    /**
144     * Creates a Poly1305 key using the method discussed in RFC8439
145     *
146     * See https://tools.ietf.org/html/rfc8439#section-2.6.1
147     */
148    protected function createPoly1305Key()
149    {
150        if ($this->nonce === false) {
151            throw new InsufficientSetupException('No nonce has been defined');
152        }
153
154        if ($this->key === false) {
155            throw new InsufficientSetupException('No key has been defined');
156        }
157
158        $c = clone $this;
159        $c->setCounter(0);
160        $c->usePoly1305 = false;
161        $block = $c->encrypt(str_repeat("\0", 256));
162        $this->setPoly1305Key(substr($block, 0, 32));
163
164        if ($this->counter == 0) {
165            $this->counter++;
166        }
167    }
168
169    /**
170     * Setup the self::ENGINE_INTERNAL $engine
171     *
172     * (re)init, if necessary, the internal cipher $engine
173     *
174     * _setup() will be called each time if $changed === true
175     * typically this happens when using one or more of following public methods:
176     *
177     * - setKey()
178     *
179     * - setNonce()
180     *
181     * - First run of encrypt() / decrypt() with no init-settings
182     *
183     * @see self::setKey()
184     * @see self::setNonce()
185     * @see self::disableContinuousBuffer()
186     */
187    protected function setup()
188    {
189        if (!$this->changed) {
190            return;
191        }
192
193        $this->enbuffer = $this->debuffer = ['ciphertext' => '', 'counter' => $this->counter];
194
195        $this->changed = $this->nonIVChanged = false;
196
197        if ($this->nonce === false) {
198            throw new InsufficientSetupException('No nonce has been defined');
199        }
200
201        if ($this->key === false) {
202            throw new InsufficientSetupException('No key has been defined');
203        }
204
205        if ($this->usePoly1305 && !isset($this->poly1305Key)) {
206            $this->usingGeneratedPoly1305Key = true;
207            $this->createPoly1305Key();
208        }
209
210        $key = $this->key;
211        if (strlen($key) == 16) {
212            $constant = 'expand 16-byte k';
213            $key .= $key;
214        } else {
215            $constant = 'expand 32-byte k';
216        }
217
218        $this->p1 = substr($constant, 0, 4) .
219                    substr($key, 0, 16) .
220                    substr($constant, 4, 4) .
221                    $this->nonce .
222                    "\0\0\0\0";
223        $this->p2 = substr($constant, 8, 4) .
224                    substr($key, 16, 16) .
225                    substr($constant, 12, 4);
226    }
227
228    /**
229     * Setup the key (expansion)
230     */
231    protected function setupKey()
232    {
233        // Salsa20 does not utilize this method
234    }
235
236    /**
237     * Encrypts a message.
238     *
239     * @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt()
240     * @see self::crypt()
241     * @param string $plaintext
242     * @return string $ciphertext
243     */
244    public function encrypt($plaintext)
245    {
246        $ciphertext = $this->crypt($plaintext, self::ENCRYPT);
247        if (isset($this->poly1305Key)) {
248            $this->newtag = $this->poly1305($ciphertext);
249        }
250        return $ciphertext;
251    }
252
253    /**
254     * Decrypts a message.
255     *
256     * $this->decrypt($this->encrypt($plaintext)) == $this->encrypt($this->encrypt($plaintext)).
257     * At least if the continuous buffer is disabled.
258     *
259     * @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt()
260     * @see self::crypt()
261     * @param string $ciphertext
262     * @return string $plaintext
263     */
264    public function decrypt($ciphertext)
265    {
266        if (isset($this->poly1305Key)) {
267            if ($this->oldtag === false) {
268                throw new InsufficientSetupException('Authentication Tag has not been set');
269            }
270            $newtag = $this->poly1305($ciphertext);
271            if ($this->oldtag != substr($newtag, 0, strlen($this->oldtag))) {
272                $this->oldtag = false;
273                throw new BadDecryptionException('Derived authentication tag and supplied authentication tag do not match');
274            }
275            $this->oldtag = false;
276        }
277
278        return $this->crypt($ciphertext, self::DECRYPT);
279    }
280
281    /**
282     * Encrypts a block
283     *
284     * @param string $in
285     */
286    protected function encryptBlock($in)
287    {
288        // Salsa20 does not utilize this method
289    }
290
291    /**
292     * Decrypts a block
293     *
294     * @param string $in
295     */
296    protected function decryptBlock($in)
297    {
298        // Salsa20 does not utilize this method
299    }
300
301    /**
302     * Encrypts or decrypts a message.
303     *
304     * @see self::encrypt()
305     * @see self::decrypt()
306     * @param string $text
307     * @param int $mode
308     * @return string $text
309     */
310    private function crypt($text, $mode)
311    {
312        $this->setup();
313        if (!$this->continuousBuffer) {
314            if ($this->engine == self::ENGINE_OPENSSL) {
315                $iv = pack('V', $this->counter) . $this->p2;
316                return openssl_encrypt(
317                    $text,
318                    $this->cipher_name_openssl,
319                    $this->key,
320                    OPENSSL_RAW_DATA,
321                    $iv
322                );
323            }
324            $i = $this->counter;
325            $blocks = str_split($text, 64);
326            foreach ($blocks as &$block) {
327                $block ^= static::salsa20($this->p1 . pack('V', $i++) . $this->p2);
328            }
329            unset($block);
330            return implode('', $blocks);
331        }
332
333        if ($mode == self::ENCRYPT) {
334            $buffer = &$this->enbuffer;
335        } else {
336            $buffer = &$this->debuffer;
337        }
338        if (!strlen($buffer['ciphertext'])) {
339            $ciphertext = '';
340        } else {
341            $ciphertext = $text ^ Strings::shift($buffer['ciphertext'], strlen($text));
342            $text = substr($text, strlen($ciphertext));
343            if (!strlen($text)) {
344                return $ciphertext;
345            }
346        }
347
348        $overflow = strlen($text) % 64; // & 0x3F
349        if ($overflow) {
350            $text2 = Strings::pop($text, $overflow);
351            if ($this->engine == self::ENGINE_OPENSSL) {
352                $iv = pack('V', $buffer['counter']) . $this->p2;
353                // at this point $text should be a multiple of 64
354                $buffer['counter'] += (strlen($text) >> 6) + 1; // ie. divide by 64
355                $encrypted = openssl_encrypt(
356                    $text . str_repeat("\0", 64),
357                    $this->cipher_name_openssl,
358                    $this->key,
359                    OPENSSL_RAW_DATA,
360                    $iv
361                );
362                $temp = Strings::pop($encrypted, 64);
363            } else {
364                $blocks = str_split($text, 64);
365                if (strlen($text)) {
366                    foreach ($blocks as &$block) {
367                        $block ^= static::salsa20($this->p1 . pack('V', $buffer['counter']++) . $this->p2);
368                    }
369                    unset($block);
370                }
371                $encrypted = implode('', $blocks);
372                $temp = static::salsa20($this->p1 . pack('V', $buffer['counter']++) . $this->p2);
373            }
374            $ciphertext .= $encrypted . ($text2 ^ $temp);
375            $buffer['ciphertext'] = substr($temp, $overflow);
376        } elseif (!strlen($buffer['ciphertext'])) {
377            if ($this->engine == self::ENGINE_OPENSSL) {
378                $iv = pack('V', $buffer['counter']) . $this->p2;
379                $buffer['counter'] += (strlen($text) >> 6);
380                $ciphertext .= openssl_encrypt(
381                    $text,
382                    $this->cipher_name_openssl,
383                    $this->key,
384                    OPENSSL_RAW_DATA,
385                    $iv
386                );
387            } else {
388                $blocks = str_split($text, 64);
389                foreach ($blocks as &$block) {
390                    $block ^= static::salsa20($this->p1 . pack('V', $buffer['counter']++) . $this->p2);
391                }
392                unset($block);
393                $ciphertext .= implode('', $blocks);
394            }
395        }
396
397        return $ciphertext;
398    }
399
400    /**
401     * Left Rotate
402     *
403     * @param int $x
404     * @param int $n
405     * @return int
406     */
407    protected static function leftRotate($x, $n)
408    {
409        if (PHP_INT_SIZE == 8) {
410            $r1 = $x << $n;
411            $r1 &= 0xFFFFFFFF;
412            $r2 = ($x & 0xFFFFFFFF) >> (32 - $n);
413        } else {
414            $x = (int) $x;
415            $r1 = $x << $n;
416            $r2 = $x >> (32 - $n);
417            $r2 &= (1 << $n) - 1;
418        }
419        return $r1 | $r2;
420    }
421
422    /**
423     * The quarterround function
424     *
425     * @param int $a
426     * @param int $b
427     * @param int $c
428     * @param int $d
429     */
430    protected static function quarterRound(&$a, &$b, &$c, &$d)
431    {
432        $b ^= self::leftRotate($a + $d, 7);
433        $c ^= self::leftRotate($b + $a, 9);
434        $d ^= self::leftRotate($c + $b, 13);
435        $a ^= self::leftRotate($d + $c, 18);
436    }
437
438    /**
439     * The doubleround function
440     *
441     * @param int $x0 (by reference)
442     * @param int $x1 (by reference)
443     * @param int $x2 (by reference)
444     * @param int $x3 (by reference)
445     * @param int $x4 (by reference)
446     * @param int $x5 (by reference)
447     * @param int $x6 (by reference)
448     * @param int $x7 (by reference)
449     * @param int $x8 (by reference)
450     * @param int $x9 (by reference)
451     * @param int $x10 (by reference)
452     * @param int $x11 (by reference)
453     * @param int $x12 (by reference)
454     * @param int $x13 (by reference)
455     * @param int $x14 (by reference)
456     * @param int $x15 (by reference)
457     */
458    protected static function doubleRound(&$x0, &$x1, &$x2, &$x3, &$x4, &$x5, &$x6, &$x7, &$x8, &$x9, &$x10, &$x11, &$x12, &$x13, &$x14, &$x15)
459    {
460        // columnRound
461        static::quarterRound($x0, $x4, $x8, $x12);
462        static::quarterRound($x5, $x9, $x13, $x1);
463        static::quarterRound($x10, $x14, $x2, $x6);
464        static::quarterRound($x15, $x3, $x7, $x11);
465        // rowRound
466        static::quarterRound($x0, $x1, $x2, $x3);
467        static::quarterRound($x5, $x6, $x7, $x4);
468        static::quarterRound($x10, $x11, $x8, $x9);
469        static::quarterRound($x15, $x12, $x13, $x14);
470    }
471
472    /**
473     * The Salsa20 hash function function
474     *
475     * @param string $x
476     */
477    protected static function salsa20($x)
478    {
479        $z = $x = unpack('V*', $x);
480        for ($i = 0; $i < 10; $i++) {
481            static::doubleRound($z[1], $z[2], $z[3], $z[4], $z[5], $z[6], $z[7], $z[8], $z[9], $z[10], $z[11], $z[12], $z[13], $z[14], $z[15], $z[16]);
482        }
483
484        for ($i = 1; $i <= 16; $i++) {
485            $x[$i] += $z[$i];
486        }
487
488        return pack('V*', ...$x);
489    }
490
491    /**
492     * Calculates Poly1305 MAC
493     *
494     * @see self::decrypt()
495     * @see self::encrypt()
496     * @param string $ciphertext
497     * @return string
498     */
499    protected function poly1305($ciphertext)
500    {
501        if (!$this->usingGeneratedPoly1305Key) {
502            return parent::poly1305($this->aad . $ciphertext);
503        } else {
504            /*
505            sodium_crypto_aead_chacha20poly1305_encrypt does not calculate the poly1305 tag
506            the same way sodium_crypto_aead_chacha20poly1305_ietf_encrypt does. you can see
507            how the latter encrypts it in Salsa20::encrypt(). here's how the former encrypts
508            it:
509
510            $this->newtag = $this->poly1305(
511                $this->aad .
512                pack('V', strlen($this->aad)) . "\0\0\0\0" .
513                $ciphertext .
514                pack('V', strlen($ciphertext)) . "\0\0\0\0"
515            );
516
517            phpseclib opts to use the IETF construction, even when the nonce is 64-bits
518            instead of 96-bits
519            */
520            return parent::poly1305(
521                self::nullPad128($this->aad) .
522                self::nullPad128($ciphertext) .
523                pack('V', strlen($this->aad)) . "\0\0\0\0" .
524                pack('V', strlen($ciphertext)) . "\0\0\0\0"
525            );
526        }
527    }
528}
529