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
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                }
370                $encrypted = implode('', $blocks);
371                $temp = static::salsa20($this->p1 . pack('V', $buffer['counter']++) . $this->p2);
372            }
373            $ciphertext .= $encrypted . ($text2 ^ $temp);
374            $buffer['ciphertext'] = substr($temp, $overflow);
375        } elseif (!strlen($buffer['ciphertext'])) {
376            if ($this->engine == self::ENGINE_OPENSSL) {
377                $iv = pack('V', $buffer['counter']) . $this->p2;
378                $buffer['counter'] += (strlen($text) >> 6);
379                $ciphertext .= openssl_encrypt(
380                    $text,
381                    $this->cipher_name_openssl,
382                    $this->key,
383                    OPENSSL_RAW_DATA,
384                    $iv
385                );
386            } else {
387                $blocks = str_split($text, 64);
388                foreach ($blocks as &$block) {
389                    $block ^= static::salsa20($this->p1 . pack('V', $buffer['counter']++) . $this->p2);
390                }
391                $ciphertext .= implode('', $blocks);
392            }
393        }
394
395        return $ciphertext;
396    }
397
398    /**
399     * Left Rotate
400     *
401     * @param int $x
402     * @param int $n
403     * @return int
404     */
405    protected static function leftRotate($x, $n)
406    {
407        if (PHP_INT_SIZE == 8) {
408            $r1 = $x << $n;
409            $r1 &= 0xFFFFFFFF;
410            $r2 = ($x & 0xFFFFFFFF) >> (32 - $n);
411        } else {
412            $x = (int) $x;
413            $r1 = $x << $n;
414            $r2 = $x >> (32 - $n);
415            $r2 &= (1 << $n) - 1;
416        }
417        return $r1 | $r2;
418    }
419
420    /**
421     * The quarterround function
422     *
423     * @param int $a
424     * @param int $b
425     * @param int $c
426     * @param int $d
427     */
428    protected static function quarterRound(&$a, &$b, &$c, &$d)
429    {
430        $b ^= self::leftRotate($a + $d, 7);
431        $c ^= self::leftRotate($b + $a, 9);
432        $d ^= self::leftRotate($c + $b, 13);
433        $a ^= self::leftRotate($d + $c, 18);
434    }
435
436    /**
437     * The doubleround function
438     *
439     * @param int $x0 (by reference)
440     * @param int $x1 (by reference)
441     * @param int $x2 (by reference)
442     * @param int $x3 (by reference)
443     * @param int $x4 (by reference)
444     * @param int $x5 (by reference)
445     * @param int $x6 (by reference)
446     * @param int $x7 (by reference)
447     * @param int $x8 (by reference)
448     * @param int $x9 (by reference)
449     * @param int $x10 (by reference)
450     * @param int $x11 (by reference)
451     * @param int $x12 (by reference)
452     * @param int $x13 (by reference)
453     * @param int $x14 (by reference)
454     * @param int $x15 (by reference)
455     */
456    protected static function doubleRound(&$x0, &$x1, &$x2, &$x3, &$x4, &$x5, &$x6, &$x7, &$x8, &$x9, &$x10, &$x11, &$x12, &$x13, &$x14, &$x15)
457    {
458        // columnRound
459        static::quarterRound($x0, $x4, $x8, $x12);
460        static::quarterRound($x5, $x9, $x13, $x1);
461        static::quarterRound($x10, $x14, $x2, $x6);
462        static::quarterRound($x15, $x3, $x7, $x11);
463        // rowRound
464        static::quarterRound($x0, $x1, $x2, $x3);
465        static::quarterRound($x5, $x6, $x7, $x4);
466        static::quarterRound($x10, $x11, $x8, $x9);
467        static::quarterRound($x15, $x12, $x13, $x14);
468    }
469
470    /**
471     * The Salsa20 hash function function
472     *
473     * @param string $x
474     */
475    protected static function salsa20($x)
476    {
477        $z = $x = unpack('V*', $x);
478        for ($i = 0; $i < 10; $i++) {
479            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]);
480        }
481
482        for ($i = 1; $i <= 16; $i++) {
483            $x[$i] += $z[$i];
484        }
485
486        return pack('V*', ...$x);
487    }
488
489    /**
490     * Calculates Poly1305 MAC
491     *
492     * @see self::decrypt()
493     * @see self::encrypt()
494     * @param string $ciphertext
495     * @return string
496     */
497    protected function poly1305($ciphertext)
498    {
499        if (!$this->usingGeneratedPoly1305Key) {
500            return parent::poly1305($this->aad . $ciphertext);
501        } else {
502            /*
503            sodium_crypto_aead_chacha20poly1305_encrypt does not calculate the poly1305 tag
504            the same way sodium_crypto_aead_chacha20poly1305_ietf_encrypt does. you can see
505            how the latter encrypts it in Salsa20::encrypt(). here's how the former encrypts
506            it:
507
508            $this->newtag = $this->poly1305(
509                $this->aad .
510                pack('V', strlen($this->aad)) . "\0\0\0\0" .
511                $ciphertext .
512                pack('V', strlen($ciphertext)) . "\0\0\0\0"
513            );
514
515            phpseclib opts to use the IETF construction, even when the nonce is 64-bits
516            instead of 96-bits
517            */
518            return parent::poly1305(
519                self::nullPad128($this->aad) .
520                self::nullPad128($ciphertext) .
521                pack('V', strlen($this->aad)) . "\0\0\0\0" .
522                pack('V', strlen($ciphertext)) . "\0\0\0\0"
523            );
524        }
525    }
526}
527