Passed
Push — master ( 01d638...0463d1 )
by Tony Karavasilev (Тони
05:33
created

NativeSha3::digest256()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 2
dl 0
loc 3
ccs 0
cts 3
cp 0
crap 2
rs 10
1
<?php
2
3
/**
4
 * The SHA-3 pure PHP implementation that is compatible with PHP versions before 7.1 and older `hash` extensions.
5
 */
6
7
namespace CryptoManana\Compatibility;
8
9
use \CryptoManana\Core\Abstractions\DesignPatterns\AbstractSingleton as SingletonPattern;
10
11
/**
12
 * Class NativeSha3 - Pure PHP implementation of the SHA-3 algorithm.
13
 *
14
 * @package CryptoManana\Compatibility
15
 */
16
class NativeSha3 extends SingletonPattern
17
{
18
    /**
19
     * Internal algorithm rounds count.
20
     */
21
    const KECCAK_ROUNDS = 24;
22
23
    /**
24
     * Internal algorithm suffix byte.
25
     */
26
    const KECCAK_SUFIX = 0x06;
27
28
    /**
29
     * Internal flag marking if the PHP version is x64 based.
30
     *
31
     * Note: `null` => auto-check on next call, `true` => x64, `false` => x32.
32
     *
33
     * @var null|bool Is it a x64 system.
34
     */
35
    protected static $isX64 = null;
36
37
    /**
38
     * Internal flag to enable or disable the `mbstring` extension usage.
39
     *
40
     * Note: `null` => auto-check on next call, `true` => available, `false` => not available.
41
     *
42
     * @var null|bool Is the `mbstring` extension supported.
43
     */
44
    protected static $mbString = null;
45
46
    /**
47
     * Internal algorithm hardcoded data.
48
     *
49
     * @var array Internal data for manipulations.
50
     */
51
    protected static $fKeccakRotc = [
52
        1,
53
        3,
54
        6,
55
        10,
56
        15,
57
        21,
58
        28,
59
        36,
60
        45,
61
        55,
62
        2,
63
        14,
64
        27,
65
        41,
66
        56,
67
        8,
68
        25,
69
        43,
70
        62,
71
        18,
72
        39,
73
        61,
74
        20,
75
        44,
76
    ];
77
78
    /**
79
     * Internal algorithm hardcoded data.
80
     *
81
     * @var array Internal data for manipulations.
82
     */
83
    protected static $fKeccakPiln = [
84
        10,
85
        7,
86
        11,
87
        17,
88
        18,
89
        3,
90
        5,
91
        16,
92
        8,
93
        21,
94
        24,
95
        4,
96
        15,
97
        23,
98
        19,
99
        13,
100
        12,
101
        2,
102
        20,
103
        14,
104
        22,
105
        9,
106
        6,
107
        1,
108
    ];
109
110
    /**
111
     * Get different hardcoded data for internal manipulations depending on the system word size.
112
     *
113
     * @return array Internal data for manipulations.
114
     */
115
    protected static function getRndcArray()
116
    {
117
        if (self::$isX64) {
118
            return [
119
                [0x00000000, 0x00000001],
120
                [0x00000000, 0x00008082],
121
                [0x80000000, 0x0000808a],
122
                [0x80000000, 0x80008000],
123
                [0x00000000, 0x0000808b],
124
                [0x00000000, 0x80000001],
125
                [0x80000000, 0x80008081],
126
                [0x80000000, 0x00008009],
127
                [0x00000000, 0x0000008a],
128
                [0x00000000, 0x00000088],
129
                [0x00000000, 0x80008009],
130
                [0x00000000, 0x8000000a],
131
                [0x00000000, 0x8000808b],
132
                [0x80000000, 0x0000008b],
133
                [0x80000000, 0x00008089],
134
                [0x80000000, 0x00008003],
135
                [0x80000000, 0x00008002],
136
                [0x80000000, 0x00000080],
137
                [0x00000000, 0x0000800a],
138
                [0x80000000, 0x8000000a],
139
                [0x80000000, 0x80008081],
140
                [0x80000000, 0x00008080],
141
                [0x00000000, 0x80000001],
142
                [0x80000000, 0x80008008],
143
            ];
144
        } else {
145
            return [
146
                [0x0000, 0x0000, 0x0000, 0x0001],
147
                [0x0000, 0x0000, 0x0000, 0x8082],
148
                [0x8000, 0x0000, 0x0000, 0x0808a],
149
                [0x8000, 0x0000, 0x8000, 0x8000],
150
                [0x0000, 0x0000, 0x0000, 0x808b],
151
                [0x0000, 0x0000, 0x8000, 0x0001],
152
                [0x8000, 0x0000, 0x8000, 0x08081],
153
                [0x8000, 0x0000, 0x0000, 0x8009],
154
                [0x0000, 0x0000, 0x0000, 0x008a],
155
                [0x0000, 0x0000, 0x0000, 0x0088],
156
                [0x0000, 0x0000, 0x8000, 0x08009],
157
                [0x0000, 0x0000, 0x8000, 0x000a],
158
                [0x0000, 0x0000, 0x8000, 0x808b],
159
                [0x8000, 0x0000, 0x0000, 0x008b],
160
                [0x8000, 0x0000, 0x0000, 0x08089],
161
                [0x8000, 0x0000, 0x0000, 0x8003],
162
                [0x8000, 0x0000, 0x0000, 0x8002],
163
                [0x8000, 0x0000, 0x0000, 0x0080],
164
                [0x0000, 0x0000, 0x0000, 0x0800a],
165
                [0x8000, 0x0000, 0x8000, 0x000a],
166
                [0x8000, 0x0000, 0x8000, 0x8081],
167
                [0x8000, 0x0000, 0x0000, 0x8080],
168
                [0x0000, 0x0000, 0x8000, 0x00001],
169
                [0x8000, 0x0000, 0x8000, 0x8008],
170
            ];
171
        }
172
    }
173
174
    /**
175
     * Internal data manipulation for the Keccak algorithm.
176
     *
177
     * @param array $state The state matrix.
178
     * @param int $rounds The rounds count.
179
     */
180
    protected static function fKeccakAlgorithm(&$state, $rounds)
181
    {
182
        $fKeccakRndc = self::getRndcArray();
183
184
        $bc = [];
185
186
        for ($round = 0; $round < $rounds; $round++) {
187
            // Theta
188
            for ($i = 0; $i < 5; $i++) {
189
                if (self::$isX64) {
190
                    $bc[$i] = [
191
                        $state[$i][0] ^ $state[$i + 5][0] ^ $state[$i + 10][0] ^
192
                        $state[$i + 15][0] ^ $state[$i + 20][0],
193
194
                        $state[$i][1] ^ $state[$i + 5][1] ^ $state[$i + 10][1] ^
195
                        $state[$i + 15][1] ^ $state[$i + 20][1],
196
                    ];
197
                } else {
198
                    $bc[$i] = [
199
                        $state[$i][0] ^ $state[$i + 5][0] ^ $state[$i + 10][0] ^
200
                        $state[$i + 15][0] ^ $state[$i + 20][0],
201
202
                        $state[$i][1] ^ $state[$i + 5][1] ^ $state[$i + 10][1] ^
203
                        $state[$i + 15][1] ^ $state[$i + 20][1],
204
205
                        $state[$i][2] ^ $state[$i + 5][2] ^ $state[$i + 10][2] ^
206
                        $state[$i + 15][2] ^ $state[$i + 20][2],
207
208
                        $state[$i][3] ^ $state[$i + 5][3] ^ $state[$i + 10][3] ^
209
                        $state[$i + 15][3] ^ $state[$i + 20][3],
210
                    ];
211
                }
212
            }
213
214
            for ($i = 0; $i < 5; $i++) {
215
                if (self::$isX64) {
216
                    $tmp = [
217
                        $bc[($i + 4) % 5][0] ^ (($bc[($i + 1) % 5][0] << 1) |
218
                            ($bc[($i + 1) % 5][1] >> 31)) & (0xFFFFFFFF),
219
220
                        $bc[($i + 4) % 5][1] ^ (($bc[($i + 1) % 5][1] << 1) |
221
                            ($bc[($i + 1) % 5][0] >> 31)) & (0xFFFFFFFF),
222
                    ];
223
                } else {
224
                    $tmp = [
225
                        $bc[($i + 4) % 5][0] ^ ((($bc[($i + 1) % 5][0] << 1) |
226
                                ($bc[($i + 1) % 5][1] >> 15)) & (0xFFFF)),
227
228
                        $bc[($i + 4) % 5][1] ^ ((($bc[($i + 1) % 5][1] << 1) |
229
                                ($bc[($i + 1) % 5][2] >> 15)) & (0xFFFF)),
230
231
                        $bc[($i + 4) % 5][2] ^ ((($bc[($i + 1) % 5][2] << 1) |
232
                                ($bc[($i + 1) % 5][3] >> 15)) & (0xFFFF)),
233
234
                        $bc[($i + 4) % 5][3] ^ ((($bc[($i + 1) % 5][3] << 1) |
235
                                ($bc[($i + 1) % 5][0] >> 15)) & (0xFFFF)),
236
                    ];
237
                }
238
239
                for ($j = 0; $j < 25; $j += 5) {
240
                    if (self::$isX64) {
241
                        $state[$j + $i] = [
242
                            $state[$j + $i][0] ^ $tmp[0],
243
                            $state[$j + $i][1] ^ $tmp[1],
244
                        ];
245
                    } else {
246
                        $state[$j + $i] = [
247
                            $state[$j + $i][0] ^ $tmp[0],
248
                            $state[$j + $i][1] ^ $tmp[1],
249
                            $state[$j + $i][2] ^ $tmp[2],
250
                            $state[$j + $i][3] ^ $tmp[3],
251
                        ];
252
                    }
253
                }
254
            }
255
256
            // Rho Pi
257
            $tmp = $state[1];
258
259
            for ($i = 0; $i < 24; $i++) {
260
                $j = self::$fKeccakPiln[$i];
261
                $bc[0] = $state[$j];
262
263
                if (self::$isX64) {
264
                    $n = self::$fKeccakRotc[$i];
265
                    $hi = $tmp[0];
266
                    $lo = $tmp[1];
267
268
                    if ($n >= 32) {
269
                        $n -= 32;
270
                        $hi = $tmp[1];
271
                        $lo = $tmp[0];
272
                    }
273
274
                    $state[$j] = [
275
                        (($hi << $n) | ($lo >> (32 - $n))) & (0xFFFFFFFF),
276
                        (($lo << $n) | ($hi >> (32 - $n))) & (0xFFFFFFFF),
277
                    ];
278
                } else {
279
                    $n = self::$fKeccakRotc[$i] >> 4;
280
                    $m = self::$fKeccakRotc[$i] % 16;
281
282
                    $state[$j] = [
283
                        ((($tmp[(0 + $n) % 4] << $m) | ($tmp[(1 + $n) % 4] >> (16 - $m))) & (0xFFFF)),
284
                        ((($tmp[(1 + $n) % 4] << $m) | ($tmp[(2 + $n) % 4] >> (16 - $m))) & (0xFFFF)),
285
                        ((($tmp[(2 + $n) % 4] << $m) | ($tmp[(3 + $n) % 4] >> (16 - $m))) & (0xFFFF)),
286
                        ((($tmp[(3 + $n) % 4] << $m) | ($tmp[(0 + $n) % 4] >> (16 - $m))) & (0xFFFF)),
287
                    ];
288
                }
289
290
                $tmp = $bc[0];
291
            }
292
293
            // Chi
294
            for ($j = 0; $j < 25; $j += 5) {
295
                for ($i = 0; $i < 5; $i++) {
296
                    $bc[$i] = $state[$j + $i];
297
                }
298
299
                for ($i = 0; $i < 5; $i++) {
300
                    if (self::$isX64) {
301
                        $state[$j + $i] = [
302
                            $state[$j + $i][0] ^ ~$bc[($i + 1) % 5][0] & $bc[($i + 2) % 5][0],
303
                            $state[$j + $i][1] ^ ~$bc[($i + 1) % 5][1] & $bc[($i + 2) % 5][1],
304
                        ];
305
                    } else {
306
                        $state[$j + $i] = [
307
                            $state[$j + $i][0] ^ ~$bc[($i + 1) % 5][0] & $bc[($i + 2) % 5][0],
308
                            $state[$j + $i][1] ^ ~$bc[($i + 1) % 5][1] & $bc[($i + 2) % 5][1],
309
                            $state[$j + $i][2] ^ ~$bc[($i + 1) % 5][2] & $bc[($i + 2) % 5][2],
310
                            $state[$j + $i][3] ^ ~$bc[($i + 1) % 5][3] & $bc[($i + 2) % 5][3],
311
                        ];
312
                    }
313
                }
314
            }
315
316
            // Iota
317
            if (self::$isX64) {
318
                $state[0] = [
319
                    $state[0][0] ^ $fKeccakRndc[$round][0],
320
                    $state[0][1] ^ $fKeccakRndc[$round][1],
321
                ];
322
            } else {
323
                $state[0] = [
324
                    $state[0][0] ^ $fKeccakRndc[$round][0],
325
                    $state[0][1] ^ $fKeccakRndc[$round][1],
326
                    $state[0][2] ^ $fKeccakRndc[$round][2],
327
                    $state[0][3] ^ $fKeccakRndc[$round][3],
328
                ];
329
            }
330
        }
331
    }
332
333
    /**
334
     * The internal Keccak native implementation.
335
     *
336
     * @param string|mixed $inputBytes The data for hashing.
337
     * @param int $outputLength The output length for the algorithm.
338
     * @param int $algorithmSuffix The used integer suffix for the algorithm.
339
     * @param bool|int|null $rawOutput Flag for using raw byte output instead of HEX.
340
     *
341
     * @return string The output digest.
342
     */
343
    protected static function keccakAlgorithm($inputBytes, $outputLength, $algorithmSuffix, $rawOutput)
344
    {
345
        $capacity = $outputLength;
346
347
        $capacity /= 8;
348
349
        $inputLength = self::binarySafeStrLength($inputBytes);
350
351
        $rSize = 200 - 2 * $capacity;
352
        $rSizeWidth = $rSize / 8;
353
354
        $state = [];
355
        for ($i = 0; $i < 25; $i++) {
356
            if (self::$isX64) {
357
                $state[] = [0, 0];
358
            } else {
359
                $state[] = [0, 0, 0, 0];
360
            }
361
        }
362
363
        for ($inputIterator = 0; $inputLength >= $rSize; $inputLength -= $rSize, $inputIterator += $rSize) {
364
            for ($i = 0; $i < $rSizeWidth; $i++) {
365
                $tmp = unpack(
366
                    self::$isX64 ? 'V*' : 'v*',
367
                    self::binarySafeSubStr($inputBytes, $i * 8 + $inputIterator, 8)
368
                );
369
370
                if (self::$isX64) {
371
                    $state[$i] = [
372
                        $state[$i][0] ^ $tmp[2],
373
                        $state[$i][1] ^ $tmp[1],
374
                    ];
375
                } else {
376
                    $state[$i] = [
377
                        $state[$i][0] ^ $tmp[4],
378
                        $state[$i][1] ^ $tmp[3],
379
                        $state[$i][2] ^ $tmp[2],
380
                        $state[$i][3] ^ $tmp[1],
381
                    ];
382
                }
383
            }
384
385
            self::fKeccakAlgorithm($state, self::KECCAK_ROUNDS);
386
        }
387
388
        $tempData = self::binarySafeSubStr($inputBytes, $inputIterator, $inputLength);
389
        $tempData = str_pad($tempData, $rSize, "\x0", STR_PAD_RIGHT);
390
391
        // Note: mb_chr() is available only in PHP >= 7.2, so using chr() in ASCII 8-bit codes
392
        $tempData[$inputLength] = chr($algorithmSuffix);
393
394
        // Note: mb_ord() is available only in PHP >= 7.2, so using ord() in ASCII 8-bit codes
395
        if (self::$isX64) {
396
            $tempData[$rSize - 1] = chr(ord($tempData[$rSize - 1]) | 0x80);
397
        } else {
398
            $tempData[$rSize - 1] = chr((int)$tempData[$rSize - 1] | 0x80);
399
        }
400
401
        for ($i = 0; $i < $rSizeWidth; $i++) {
402
            $tmp = unpack(
403
                self::$isX64 ? 'V*' : 'v*',
404
                self::binarySafeSubStr($tempData, $i * 8, 8)
405
            );
406
407
            if (self::$isX64) {
408
                $state[$i] = [
409
                    $state[$i][0] ^ $tmp[2],
410
                    $state[$i][1] ^ $tmp[1],
411
                ];
412
            } else {
413
                $state[$i] = [
414
                    $state[$i][0] ^ $tmp[4],
415
                    $state[$i][1] ^ $tmp[3],
416
                    $state[$i][2] ^ $tmp[2],
417
                    $state[$i][3] ^ $tmp[1],
418
                ];
419
            }
420
        }
421
422
        $tmp = null;
1 ignored issue
show
Unused Code introduced by
The assignment to $tmp is dead and can be removed.
Loading history...
423
424
        self::fKeccakAlgorithm($state, self::KECCAK_ROUNDS);
425
426
        $output = '';
427
428
        for ($i = 0; $i < 25; $i++) {
429
            if (self::$isX64) {
430
                $output .= pack('V*', $state[$i][1], $state[$i][0]);
431
            } else {
432
                $output .= pack('v*', $state[$i][3], $state[$i][2], $state[$i][1], $state[$i][0]);
433
            }
434
        }
435
436
        $output = self::binarySafeSubStr($output, 0, $outputLength / 8);
437
438
        return ($rawOutput) ? $output : bin2hex($output);
439
    }
440
441
    /**
442
     * Get the string's length in 8-bit representation of raw bytes.
443
     *
444
     * @param string $string The string for length measuring.
445
     *
446
     * @return int The string's length.
447
     */
448
    protected static function binarySafeStrLength($string)
449
    {
450
        return self::$mbString ? mb_strlen($string, '8bit') : strlen($string);
451
    }
452
453
    /**
454
     * Return a part of a string in length via the 8-bit representation of raw bytes.
455
     *
456
     * @param string $string The input string
457
     * @param int $start The starting position.
458
     * @param int|null $length The length to take.
459
     *
460
     * @return bool|string The extracted part of string or false on failure.
461
     */
462
    protected static function binarySafeSubStr($string, $start = 0, $length = null)
463
    {
464
        return self::$mbString ? mb_substr($string, $start, $length, '8bit') : substr($string, $start, $length);
465
    }
466
467
    /**
468
     * Internal static method for single point consumption of the Keccak implementation.
469
     *
470
     * @param string|mixed $inputData The data for hashing.
471
     * @param int $outputLength The output length for the algorithm.
472
     * @param bool|int|null $rawOutput Flag for using raw byte output instead of HEX.
473
     *
474
     * @return bool|string The output digest for the given input parameters.
475
     * @throws \Exception Validation errors.
476
     */
477
    protected static function calculateDigest($inputData, $outputLength, $rawOutput = false)
478
    {
479
        if (self::$isX64 === null) {
480
            self::$isX64 = (PHP_INT_SIZE === 8);
481
        }
482
483
        if (self::$mbString === null) {
484
            self::$mbString = extension_loaded('mbstring');
485
        }
486
487
        if (!is_string($inputData)) {
488
            throw new \InvalidArgumentException('The input data parameter must be of type string.');
489
        }
490
491
        return self::keccakAlgorithm($inputData, $outputLength, self::KECCAK_SUFIX, $rawOutput);
492
    }
493
494
    /**
495
     * Global method for resetting internal system check.
496
     */
497
    public static function resetSystemChecks()
498
    {
499
        self::$isX64 = null;
500
        self::$mbString = null;
501
    }
502
503
    /**
504
     * The SHA-3-224 hashing function.
505
     *
506
     * @param string|mixed $inputData The input message to be hashed.
507
     * @param bool|int|null $rawOutput When set to TRUE, outputs raw binary data. FALSE outputs lowercase hexits.
508
     *
509
     * @return string The output digest.
510
     * @throws \Exception Validation errors.
511
     */
512
    public static function digest224($inputData, $rawOutput = false)
513
    {
514
        return self::calculateDigest($inputData, 224, $rawOutput);
515
    }
516
517
    /**
518
     * The SHA-3-256 hashing function.
519
     *
520
     * @param string|mixed $inputData The input message to be hashed.
521
     * @param bool|int|null $rawOutput When set to TRUE, outputs raw binary data. FALSE outputs lowercase hexits.
522
     *
523
     * @return string The output digest.
524
     * @throws \Exception Validation errors.
525
     */
526
    public static function digest256($inputData, $rawOutput = false)
527
    {
528
        return self::calculateDigest($inputData, 256, $rawOutput);
529
    }
530
531
    /**
532
     * The SHA-3-384 hashing function.
533
     *
534
     * @param string|mixed $inputData The input message to be hashed.
535
     * @param bool|int|null $rawOutput When set to TRUE, outputs raw binary data. FALSE outputs lowercase hexits.
536
     *
537
     * @return string The output digest.
538
     * @throws \Exception Validation errors.
539
     */
540
    public static function digest384($inputData, $rawOutput = false)
541
    {
542
        return self::calculateDigest($inputData, 384, $rawOutput);
543
    }
544
545
    /**
546
     * The SHA-3-512 hashing function.
547
     *
548
     * @param string|mixed $inputData The input message to be hashed.
549
     * @param bool|int|null $rawOutput When set to TRUE, outputs raw binary data. FALSE outputs lowercase hexits.
550
     *
551
     * @return string The output digest.
552
     * @throws \Exception Validation errors.
553
     */
554
    public static function digest512($inputData, $rawOutput = false)
555
    {
556
        return self::calculateDigest($inputData, 512, $rawOutput);
557
    }
558
}
559