Subversion Repositories oidplus

Rev

Rev 827 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
827 daniel-mar 1
<?php
2
declare(strict_types=1);
3
namespace ParagonIE\ConstantTime;
4
 
874 daniel-mar 5
use InvalidArgumentException;
6
use RangeException;
7
use TypeError;
8
 
827 daniel-mar 9
/**
874 daniel-mar 10
 *  Copyright (c) 2016 - 2022 Paragon Initiative Enterprises.
827 daniel-mar 11
 *  Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com)
12
 *
13
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
14
 *  of this software and associated documentation files (the "Software"), to deal
15
 *  in the Software without restriction, including without limitation the rights
16
 *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
 *  copies of the Software, and to permit persons to whom the Software is
18
 *  furnished to do so, subject to the following conditions:
19
 *
20
 *  The above copyright notice and this permission notice shall be included in all
21
 *  copies or substantial portions of the Software.
22
 *
23
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
 *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
 *  SOFTWARE.
30
 */
31
 
32
/**
33
 * Class Base32
34
 * [A-Z][2-7]
35
 *
36
 * @package ParagonIE\ConstantTime
37
 */
38
abstract class Base32 implements EncoderInterface
39
{
40
    /**
41
     * Decode a Base32-encoded string into raw binary
42
     *
43
     * @param string $encodedString
44
     * @param bool $strictPadding
45
     * @return string
46
     */
47
    public static function decode(string $encodedString, bool $strictPadding = false): string
48
    {
49
        return static::doDecode($encodedString, false, $strictPadding);
50
    }
51
 
52
    /**
53
     * Decode an uppercase Base32-encoded string into raw binary
54
     *
55
     * @param string $src
56
     * @param bool $strictPadding
57
     * @return string
58
     */
59
    public static function decodeUpper(string $src, bool $strictPadding = false): string
60
    {
61
        return static::doDecode($src, true, $strictPadding);
62
    }
63
 
64
    /**
65
     * Encode into Base32 (RFC 4648)
66
     *
67
     * @param string $binString
68
     * @return string
874 daniel-mar 69
     * @throws TypeError
827 daniel-mar 70
     */
71
    public static function encode(string $binString): string
72
    {
73
        return static::doEncode($binString, false, true);
74
    }
75
    /**
76
     * Encode into Base32 (RFC 4648)
77
     *
78
     * @param string $src
79
     * @return string
874 daniel-mar 80
     * @throws TypeError
827 daniel-mar 81
     */
82
    public static function encodeUnpadded(string $src): string
83
    {
84
        return static::doEncode($src, false, false);
85
    }
86
 
87
    /**
88
     * Encode into uppercase Base32 (RFC 4648)
89
     *
90
     * @param string $src
91
     * @return string
874 daniel-mar 92
     * @throws TypeError
827 daniel-mar 93
     */
94
    public static function encodeUpper(string $src): string
95
    {
96
        return static::doEncode($src, true, true);
97
    }
98
 
99
    /**
100
     * Encode into uppercase Base32 (RFC 4648)
101
     *
102
     * @param string $src
103
     * @return string
874 daniel-mar 104
     * @throws TypeError
827 daniel-mar 105
     */
106
    public static function encodeUpperUnpadded(string $src): string
107
    {
108
        return static::doEncode($src, true, false);
109
    }
110
 
111
    /**
112
     * Uses bitwise operators instead of table-lookups to turn 5-bit integers
113
     * into 8-bit integers.
114
     *
115
     * @param int $src
116
     * @return int
117
     */
118
    protected static function decode5Bits(int $src): int
119
    {
120
        $ret = -1;
121
 
122
        // if ($src > 96 && $src < 123) $ret += $src - 97 + 1; // -64
123
        $ret += (((0x60 - $src) & ($src - 0x7b)) >> 8) & ($src - 96);
124
 
125
        // if ($src > 0x31 && $src < 0x38) $ret += $src - 24 + 1; // -23
126
        $ret += (((0x31 - $src) & ($src - 0x38)) >> 8) & ($src - 23);
127
 
128
        return $ret;
129
    }
130
 
131
    /**
132
     * Uses bitwise operators instead of table-lookups to turn 5-bit integers
133
     * into 8-bit integers.
134
     *
135
     * Uppercase variant.
136
     *
137
     * @param int $src
138
     * @return int
139
     */
140
    protected static function decode5BitsUpper(int $src): int
141
    {
142
        $ret = -1;
143
 
144
        // if ($src > 64 && $src < 91) $ret += $src - 65 + 1; // -64
145
        $ret += (((0x40 - $src) & ($src - 0x5b)) >> 8) & ($src - 64);
146
 
147
        // if ($src > 0x31 && $src < 0x38) $ret += $src - 24 + 1; // -23
148
        $ret += (((0x31 - $src) & ($src - 0x38)) >> 8) & ($src - 23);
149
 
150
        return $ret;
151
    }
152
 
153
    /**
154
     * Uses bitwise operators instead of table-lookups to turn 8-bit integers
155
     * into 5-bit integers.
156
     *
157
     * @param int $src
158
     * @return string
159
     */
160
    protected static function encode5Bits(int $src): string
161
    {
162
        $diff = 0x61;
163
 
164
        // if ($src > 25) $ret -= 72;
165
        $diff -= ((25 - $src) >> 8) & 73;
166
 
167
        return \pack('C', $src + $diff);
168
    }
169
 
170
    /**
171
     * Uses bitwise operators instead of table-lookups to turn 8-bit integers
172
     * into 5-bit integers.
173
     *
174
     * Uppercase variant.
175
     *
176
     * @param int $src
177
     * @return string
178
     */
179
    protected static function encode5BitsUpper(int $src): string
180
    {
181
        $diff = 0x41;
182
 
183
        // if ($src > 25) $ret -= 40;
184
        $diff -= ((25 - $src) >> 8) & 41;
185
 
186
        return \pack('C', $src + $diff);
187
    }
188
 
874 daniel-mar 189
    /**
190
     * @param string $encodedString
191
     * @param bool $upper
192
     * @return string
193
     */
194
    public static function decodeNoPadding(string $encodedString, bool $upper = false): string
195
    {
196
        $srcLen = Binary::safeStrlen($encodedString);
197
        if ($srcLen === 0) {
198
            return '';
199
        }
200
        if (($srcLen & 7) === 0) {
201
            for ($j = 0; $j < 7 && $j < $srcLen; ++$j) {
202
                if ($encodedString[$srcLen - $j - 1] === '=') {
203
                    throw new InvalidArgumentException(
204
                        "decodeNoPadding() doesn't tolerate padding"
205
                    );
206
                }
207
            }
208
        }
209
        return static::doDecode(
210
            $encodedString,
211
            $upper,
212
            true
213
        );
214
    }
827 daniel-mar 215
 
216
    /**
217
     * Base32 decoding
218
     *
219
     * @param string $src
220
     * @param bool $upper
221
     * @param bool $strictPadding
222
     * @return string
874 daniel-mar 223
     *
224
     * @throws TypeError
827 daniel-mar 225
     * @psalm-suppress RedundantCondition
226
     */
874 daniel-mar 227
    protected static function doDecode(
228
        string $src,
229
        bool $upper = false,
230
        bool $strictPadding = false
231
    ): string {
827 daniel-mar 232
        // We do this to reduce code duplication:
233
        $method = $upper
234
            ? 'decode5BitsUpper'
235
            : 'decode5Bits';
236
 
237
        // Remove padding
238
        $srcLen = Binary::safeStrlen($src);
239
        if ($srcLen === 0) {
240
            return '';
241
        }
242
        if ($strictPadding) {
243
            if (($srcLen & 7) === 0) {
244
                for ($j = 0; $j < 7; ++$j) {
245
                    if ($src[$srcLen - 1] === '=') {
246
                        $srcLen--;
247
                    } else {
248
                        break;
249
                    }
250
                }
251
            }
252
            if (($srcLen & 7) === 1) {
874 daniel-mar 253
                throw new RangeException(
827 daniel-mar 254
                    'Incorrect padding'
255
                );
256
            }
257
        } else {
258
            $src = \rtrim($src, '=');
259
            $srcLen = Binary::safeStrlen($src);
260
        }
261
 
262
        $err = 0;
263
        $dest = '';
264
        // Main loop (no padding):
265
        for ($i = 0; $i + 8 <= $srcLen; $i += 8) {
266
            /** @var array<int, int> $chunk */
267
            $chunk = \unpack('C*', Binary::safeSubstr($src, $i, 8));
268
            /** @var int $c0 */
269
            $c0 = static::$method($chunk[1]);
270
            /** @var int $c1 */
271
            $c1 = static::$method($chunk[2]);
272
            /** @var int $c2 */
273
            $c2 = static::$method($chunk[3]);
274
            /** @var int $c3 */
275
            $c3 = static::$method($chunk[4]);
276
            /** @var int $c4 */
277
            $c4 = static::$method($chunk[5]);
278
            /** @var int $c5 */
279
            $c5 = static::$method($chunk[6]);
280
            /** @var int $c6 */
281
            $c6 = static::$method($chunk[7]);
282
            /** @var int $c7 */
283
            $c7 = static::$method($chunk[8]);
284
 
285
            $dest .= \pack(
286
                'CCCCC',
287
                (($c0 << 3) | ($c1 >> 2)             ) & 0xff,
288
                (($c1 << 6) | ($c2 << 1) | ($c3 >> 4)) & 0xff,
289
                (($c3 << 4) | ($c4 >> 1)             ) & 0xff,
290
                (($c4 << 7) | ($c5 << 2) | ($c6 >> 3)) & 0xff,
291
                (($c6 << 5) | ($c7     )             ) & 0xff
292
            );
293
            $err |= ($c0 | $c1 | $c2 | $c3 | $c4 | $c5 | $c6 | $c7) >> 8;
294
        }
295
        // The last chunk, which may have padding:
296
        if ($i < $srcLen) {
297
            /** @var array<int, int> $chunk */
298
            $chunk = \unpack('C*', Binary::safeSubstr($src, $i, $srcLen - $i));
299
            /** @var int $c0 */
300
            $c0 = static::$method($chunk[1]);
301
 
302
            if ($i + 6 < $srcLen) {
303
                /** @var int $c1 */
304
                $c1 = static::$method($chunk[2]);
305
                /** @var int $c2 */
306
                $c2 = static::$method($chunk[3]);
307
                /** @var int $c3 */
308
                $c3 = static::$method($chunk[4]);
309
                /** @var int $c4 */
310
                $c4 = static::$method($chunk[5]);
311
                /** @var int $c5 */
312
                $c5 = static::$method($chunk[6]);
313
                /** @var int $c6 */
314
                $c6 = static::$method($chunk[7]);
315
 
316
                $dest .= \pack(
317
                    'CCCC',
318
                    (($c0 << 3) | ($c1 >> 2)             ) & 0xff,
319
                    (($c1 << 6) | ($c2 << 1) | ($c3 >> 4)) & 0xff,
320
                    (($c3 << 4) | ($c4 >> 1)             ) & 0xff,
321
                    (($c4 << 7) | ($c5 << 2) | ($c6 >> 3)) & 0xff
322
                );
323
                $err |= ($c0 | $c1 | $c2 | $c3 | $c4 | $c5 | $c6) >> 8;
874 daniel-mar 324
                if ($strictPadding) {
325
                    $err |= ($c6 << 5) & 0xff;
326
                }
827 daniel-mar 327
            } elseif ($i + 5 < $srcLen) {
328
                /** @var int $c1 */
329
                $c1 = static::$method($chunk[2]);
330
                /** @var int $c2 */
331
                $c2 = static::$method($chunk[3]);
332
                /** @var int $c3 */
333
                $c3 = static::$method($chunk[4]);
334
                /** @var int $c4 */
335
                $c4 = static::$method($chunk[5]);
336
                /** @var int $c5 */
337
                $c5 = static::$method($chunk[6]);
338
 
339
                $dest .= \pack(
340
                    'CCCC',
341
                    (($c0 << 3) | ($c1 >> 2)             ) & 0xff,
342
                    (($c1 << 6) | ($c2 << 1) | ($c3 >> 4)) & 0xff,
343
                    (($c3 << 4) | ($c4 >> 1)             ) & 0xff,
344
                    (($c4 << 7) | ($c5 << 2)             ) & 0xff
345
                );
346
                $err |= ($c0 | $c1 | $c2 | $c3 | $c4 | $c5) >> 8;
347
            } elseif ($i + 4 < $srcLen) {
348
                /** @var int $c1 */
349
                $c1 = static::$method($chunk[2]);
350
                /** @var int $c2 */
351
                $c2 = static::$method($chunk[3]);
352
                /** @var int $c3 */
353
                $c3 = static::$method($chunk[4]);
354
                /** @var int $c4 */
355
                $c4 = static::$method($chunk[5]);
356
 
357
                $dest .= \pack(
358
                    'CCC',
359
                    (($c0 << 3) | ($c1 >> 2)             ) & 0xff,
360
                    (($c1 << 6) | ($c2 << 1) | ($c3 >> 4)) & 0xff,
361
                    (($c3 << 4) | ($c4 >> 1)             ) & 0xff
362
                );
363
                $err |= ($c0 | $c1 | $c2 | $c3 | $c4) >> 8;
874 daniel-mar 364
                if ($strictPadding) {
365
                    $err |= ($c4 << 7) & 0xff;
366
                }
827 daniel-mar 367
            } elseif ($i + 3 < $srcLen) {
368
                /** @var int $c1 */
369
                $c1 = static::$method($chunk[2]);
370
                /** @var int $c2 */
371
                $c2 = static::$method($chunk[3]);
372
                /** @var int $c3 */
373
                $c3 = static::$method($chunk[4]);
374
 
375
                $dest .= \pack(
376
                    'CC',
377
                    (($c0 << 3) | ($c1 >> 2)             ) & 0xff,
378
                    (($c1 << 6) | ($c2 << 1) | ($c3 >> 4)) & 0xff
379
                );
380
                $err |= ($c0 | $c1 | $c2 | $c3) >> 8;
874 daniel-mar 381
                if ($strictPadding) {
382
                    $err |= ($c3 << 4) & 0xff;
383
                }
827 daniel-mar 384
            } elseif ($i + 2 < $srcLen) {
385
                /** @var int $c1 */
386
                $c1 = static::$method($chunk[2]);
387
                /** @var int $c2 */
388
                $c2 = static::$method($chunk[3]);
389
 
390
                $dest .= \pack(
391
                    'CC',
392
                    (($c0 << 3) | ($c1 >> 2)             ) & 0xff,
393
                    (($c1 << 6) | ($c2 << 1)             ) & 0xff
394
                );
395
                $err |= ($c0 | $c1 | $c2) >> 8;
874 daniel-mar 396
                if ($strictPadding) {
397
                    $err |= ($c2 << 6) & 0xff;
398
                }
827 daniel-mar 399
            } elseif ($i + 1 < $srcLen) {
400
                /** @var int $c1 */
401
                $c1 = static::$method($chunk[2]);
402
 
403
                $dest .= \pack(
404
                    'C',
405
                    (($c0 << 3) | ($c1 >> 2)             ) & 0xff
406
                );
407
                $err |= ($c0 | $c1) >> 8;
874 daniel-mar 408
                if ($strictPadding) {
409
                    $err |= ($c1 << 6) & 0xff;
410
                }
827 daniel-mar 411
            } else {
412
                $dest .= \pack(
413
                    'C',
414
                    (($c0 << 3)                          ) & 0xff
415
                );
416
                $err |= ($c0) >> 8;
417
            }
418
        }
419
        $check = ($err === 0);
420
        if (!$check) {
874 daniel-mar 421
            throw new RangeException(
827 daniel-mar 422
                'Base32::doDecode() only expects characters in the correct base32 alphabet'
423
            );
424
        }
425
        return $dest;
426
    }
427
 
428
    /**
429
     * Base32 Encoding
430
     *
431
     * @param string $src
432
     * @param bool $upper
433
     * @param bool $pad
434
     * @return string
874 daniel-mar 435
     * @throws TypeError
827 daniel-mar 436
     */
437
    protected static function doEncode(string $src, bool $upper = false, $pad = true): string
438
    {
439
        // We do this to reduce code duplication:
440
        $method = $upper
441
            ? 'encode5BitsUpper'
442
            : 'encode5Bits';
443
 
444
        $dest = '';
445
        $srcLen = Binary::safeStrlen($src);
446
 
447
        // Main loop (no padding):
448
        for ($i = 0; $i + 5 <= $srcLen; $i += 5) {
449
            /** @var array<int, int> $chunk */
450
            $chunk = \unpack('C*', Binary::safeSubstr($src, $i, 5));
451
            $b0 = $chunk[1];
452
            $b1 = $chunk[2];
453
            $b2 = $chunk[3];
454
            $b3 = $chunk[4];
455
            $b4 = $chunk[5];
456
            $dest .=
457
                static::$method(              ($b0 >> 3)  & 31) .
458
                static::$method((($b0 << 2) | ($b1 >> 6)) & 31) .
459
                static::$method((($b1 >> 1)             ) & 31) .
460
                static::$method((($b1 << 4) | ($b2 >> 4)) & 31) .
461
                static::$method((($b2 << 1) | ($b3 >> 7)) & 31) .
462
                static::$method((($b3 >> 2)             ) & 31) .
463
                static::$method((($b3 << 3) | ($b4 >> 5)) & 31) .
464
                static::$method(  $b4                     & 31);
465
        }
466
        // The last chunk, which may have padding:
467
        if ($i < $srcLen) {
468
            /** @var array<int, int> $chunk */
469
            $chunk = \unpack('C*', Binary::safeSubstr($src, $i, $srcLen - $i));
470
            $b0 = $chunk[1];
471
            if ($i + 3 < $srcLen) {
472
                $b1 = $chunk[2];
473
                $b2 = $chunk[3];
474
                $b3 = $chunk[4];
475
                $dest .=
476
                    static::$method(              ($b0 >> 3)  & 31) .
477
                    static::$method((($b0 << 2) | ($b1 >> 6)) & 31) .
478
                    static::$method((($b1 >> 1)             ) & 31) .
479
                    static::$method((($b1 << 4) | ($b2 >> 4)) & 31) .
480
                    static::$method((($b2 << 1) | ($b3 >> 7)) & 31) .
481
                    static::$method((($b3 >> 2)             ) & 31) .
482
                    static::$method((($b3 << 3)             ) & 31);
483
                if ($pad) {
484
                    $dest .= '=';
485
                }
486
            } elseif ($i + 2 < $srcLen) {
487
                $b1 = $chunk[2];
488
                $b2 = $chunk[3];
489
                $dest .=
490
                    static::$method(              ($b0 >> 3)  & 31) .
491
                    static::$method((($b0 << 2) | ($b1 >> 6)) & 31) .
492
                    static::$method((($b1 >> 1)             ) & 31) .
493
                    static::$method((($b1 << 4) | ($b2 >> 4)) & 31) .
494
                    static::$method((($b2 << 1)             ) & 31);
495
                if ($pad) {
496
                    $dest .= '===';
497
                }
498
            } elseif ($i + 1 < $srcLen) {
499
                $b1 = $chunk[2];
500
                $dest .=
501
                    static::$method(              ($b0 >> 3)  & 31) .
502
                    static::$method((($b0 << 2) | ($b1 >> 6)) & 31) .
503
                    static::$method((($b1 >> 1)             ) & 31) .
504
                    static::$method((($b1 << 4)             ) & 31);
505
                if ($pad) {
506
                    $dest .= '====';
507
                }
508
            } else {
509
                $dest .=
510
                    static::$method(              ($b0 >> 3)  & 31) .
511
                    static::$method( ($b0 << 2)               & 31);
512
                if ($pad) {
513
                    $dest .= '======';
514
                }
515
            }
516
        }
517
        return $dest;
518
    }
519
}