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