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 RangeException; |
6 | use TypeError; |
||
7 | |||
827 | daniel-mar | 8 | /** |
874 | daniel-mar | 9 | * Copyright (c) 2016 - 2022 Paragon Initiative Enterprises. |
827 | daniel-mar | 10 | * Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com) |
11 | * |
||
12 | * Permission is hereby granted, free of charge, to any person obtaining a copy |
||
13 | * of this software and associated documentation files (the "Software"), to deal |
||
14 | * in the Software without restriction, including without limitation the rights |
||
15 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||
16 | * copies of the Software, and to permit persons to whom the Software is |
||
17 | * furnished to do so, subject to the following conditions: |
||
18 | * |
||
19 | * The above copyright notice and this permission notice shall be included in all |
||
20 | * copies or substantial portions of the Software. |
||
21 | * |
||
22 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||
23 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||
24 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||
25 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||
26 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||
27 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||
28 | * SOFTWARE. |
||
29 | */ |
||
30 | |||
31 | /** |
||
32 | * Class Hex |
||
33 | * @package ParagonIE\ConstantTime |
||
34 | */ |
||
35 | abstract class Hex implements EncoderInterface |
||
36 | { |
||
37 | /** |
||
38 | * Convert a binary string into a hexadecimal string without cache-timing |
||
39 | * leaks |
||
40 | * |
||
41 | * @param string $binString (raw binary) |
||
42 | * @return string |
||
874 | daniel-mar | 43 | * @throws TypeError |
827 | daniel-mar | 44 | */ |
45 | public static function encode(string $binString): string |
||
46 | { |
||
47 | $hex = ''; |
||
48 | $len = Binary::safeStrlen($binString); |
||
49 | for ($i = 0; $i < $len; ++$i) { |
||
50 | /** @var array<int, int> $chunk */ |
||
874 | daniel-mar | 51 | $chunk = \unpack('C', $binString[$i]); |
827 | daniel-mar | 52 | $c = $chunk[1] & 0xf; |
53 | $b = $chunk[1] >> 4; |
||
54 | |||
874 | daniel-mar | 55 | $hex .= \pack( |
827 | daniel-mar | 56 | 'CC', |
57 | (87 + $b + ((($b - 10) >> 8) & ~38)), |
||
58 | (87 + $c + ((($c - 10) >> 8) & ~38)) |
||
59 | ); |
||
60 | } |
||
61 | return $hex; |
||
62 | } |
||
63 | |||
64 | /** |
||
65 | * Convert a binary string into a hexadecimal string without cache-timing |
||
66 | * leaks, returning uppercase letters (as per RFC 4648) |
||
67 | * |
||
68 | * @param string $binString (raw binary) |
||
69 | * @return string |
||
874 | daniel-mar | 70 | * @throws TypeError |
827 | daniel-mar | 71 | */ |
72 | public static function encodeUpper(string $binString): string |
||
73 | { |
||
74 | $hex = ''; |
||
75 | $len = Binary::safeStrlen($binString); |
||
76 | |||
77 | for ($i = 0; $i < $len; ++$i) { |
||
78 | /** @var array<int, int> $chunk */ |
||
874 | daniel-mar | 79 | $chunk = \unpack('C', $binString[$i]); |
827 | daniel-mar | 80 | $c = $chunk[1] & 0xf; |
81 | $b = $chunk[1] >> 4; |
||
82 | |||
874 | daniel-mar | 83 | $hex .= \pack( |
827 | daniel-mar | 84 | 'CC', |
85 | (55 + $b + ((($b - 10) >> 8) & ~6)), |
||
86 | (55 + $c + ((($c - 10) >> 8) & ~6)) |
||
87 | ); |
||
88 | } |
||
89 | return $hex; |
||
90 | } |
||
91 | |||
92 | /** |
||
93 | * Convert a hexadecimal string into a binary string without cache-timing |
||
94 | * leaks |
||
95 | * |
||
96 | * @param string $encodedString |
||
97 | * @param bool $strictPadding |
||
98 | * @return string (raw binary) |
||
874 | daniel-mar | 99 | * @throws RangeException |
827 | daniel-mar | 100 | */ |
874 | daniel-mar | 101 | public static function decode( |
102 | string $encodedString, |
||
103 | bool $strictPadding = false |
||
104 | ): string { |
||
827 | daniel-mar | 105 | $hex_pos = 0; |
106 | $bin = ''; |
||
107 | $c_acc = 0; |
||
108 | $hex_len = Binary::safeStrlen($encodedString); |
||
109 | $state = 0; |
||
110 | if (($hex_len & 1) !== 0) { |
||
111 | if ($strictPadding) { |
||
874 | daniel-mar | 112 | throw new RangeException( |
827 | daniel-mar | 113 | 'Expected an even number of hexadecimal characters' |
114 | ); |
||
115 | } else { |
||
116 | $encodedString = '0' . $encodedString; |
||
117 | ++$hex_len; |
||
118 | } |
||
119 | } |
||
120 | |||
121 | /** @var array<int, int> $chunk */ |
||
122 | $chunk = \unpack('C*', $encodedString); |
||
123 | while ($hex_pos < $hex_len) { |
||
124 | ++$hex_pos; |
||
125 | $c = $chunk[$hex_pos]; |
||
126 | $c_num = $c ^ 48; |
||
127 | $c_num0 = ($c_num - 10) >> 8; |
||
128 | $c_alpha = ($c & ~32) - 55; |
||
129 | $c_alpha0 = (($c_alpha - 10) ^ ($c_alpha - 16)) >> 8; |
||
130 | |||
131 | if (($c_num0 | $c_alpha0) === 0) { |
||
874 | daniel-mar | 132 | throw new RangeException( |
827 | daniel-mar | 133 | 'Expected hexadecimal character' |
134 | ); |
||
135 | } |
||
136 | $c_val = ($c_num0 & $c_num) | ($c_alpha & $c_alpha0); |
||
137 | if ($state === 0) { |
||
138 | $c_acc = $c_val * 16; |
||
139 | } else { |
||
140 | $bin .= \pack('C', $c_acc | $c_val); |
||
141 | } |
||
142 | $state ^= 1; |
||
143 | } |
||
144 | return $bin; |
||
145 | } |
||
146 | } |