Subversion Repositories oidplus

Rev

Rev 874 | 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
 *
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
 
14
namespace phpseclib3\Crypt;
15
 
16
use phpseclib3\Common\Functions\Strings;
17
use phpseclib3\Crypt\Common\StreamCipher;
18
use phpseclib3\Exception\BadDecryptionException;
19
use phpseclib3\Exception\InsufficientSetupException;
20
 
21
/**
22
 * Pure-PHP implementation of Salsa20.
23
 *
24
 * @author  Jim Wigginton <terrafrost@php.net>
25
 */
26
class 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) {
1042 daniel-mar 408
            $r1 = $x << $n;
827 daniel-mar 409
            $r1 &= 0xFFFFFFFF;
410
            $r2 = ($x & 0xFFFFFFFF) >> (32 - $n);
411
        } else {
1042 daniel-mar 412
            $x = (int) $x;
413
            $r1 = $x << $n;
827 daniel-mar 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
}