Subversion Repositories oidplus

Rev

Rev 846 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
827 daniel-mar 1
<?php
2
 
3
/**
4
 * Pure-PHP implementation of Salsa20.
5
 *
6
 * PHP version 5
7
 *
874 daniel-mar 8
 * @category  Crypt
9
 * @package   Salsa20
827 daniel-mar 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
 
16
namespace phpseclib3\Crypt;
17
 
18
use phpseclib3\Common\Functions\Strings;
19
use phpseclib3\Crypt\Common\StreamCipher;
20
use phpseclib3\Exception\BadDecryptionException;
21
use phpseclib3\Exception\InsufficientSetupException;
22
 
23
/**
24
 * Pure-PHP implementation of Salsa20.
25
 *
874 daniel-mar 26
 * @package Salsa20
827 daniel-mar 27
 * @author  Jim Wigginton <terrafrost@php.net>
874 daniel-mar 28
 * @access  public
827 daniel-mar 29
 */
30
class 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
    /**
874 daniel-mar 54
     * @access private
827 daniel-mar 55
     * @see \phpseclib3\Crypt\Salsa20::crypt()
56
     */
57
    const ENCRYPT = 0;
58
 
59
    /**
874 daniel-mar 60
     * @access private
827 daniel-mar 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()
874 daniel-mar 498
     * @access private
827 daniel-mar 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
}