1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/* |
4
|
|
|
* The MIT License (MIT) |
5
|
|
|
* |
6
|
|
|
* Copyright (c) 2014-2016 Spomky-Labs |
7
|
|
|
* |
8
|
|
|
* This software may be modified and distributed under the terms |
9
|
|
|
* of the MIT license. See the LICENSE file for details. |
10
|
|
|
*/ |
11
|
|
|
|
12
|
|
|
namespace Jose\Util; |
13
|
|
|
|
14
|
|
|
final class GCM |
15
|
|
|
{ |
16
|
|
|
/** |
17
|
|
|
* @param string $K |
18
|
|
|
* @param string $IV |
19
|
|
|
* @param string $P |
20
|
|
|
* @param string $A |
21
|
|
|
* @param int $t |
22
|
|
|
* |
23
|
|
|
* @return array|null |
24
|
|
|
*/ |
25
|
|
|
public function gcm_encrypt($K, $IV, $P, $A, $t = 128) { |
26
|
|
|
|
27
|
|
|
$cipher = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_ECB, ''); |
28
|
|
|
if(!$cipher) |
29
|
|
|
return null; |
30
|
|
|
$key_length = StringUtil::getStringLength($K) * 8; |
31
|
|
|
if($key_length != 128 && $key_length != 192 && $key_length != 256) { |
32
|
|
|
die("encryp invalid key length {$key_length}\n"); |
|
|
|
|
33
|
|
|
} |
34
|
|
|
|
35
|
|
|
$iv_size = mcrypt_enc_get_iv_size($cipher); |
|
|
|
|
36
|
|
|
$iv = str_repeat(chr(0), 16); // initialize to 16 byte string of "0"s |
37
|
|
|
$s = mcrypt_generic_init($cipher, $K, $iv); |
38
|
|
|
if( ($s < 0) || ($s === false)) { |
39
|
|
|
die("encryp mcrypt init error $s"); |
|
|
|
|
40
|
|
|
} |
41
|
|
|
$H = mcrypt_generic($cipher, StringUtil::addPadding('', 16, "\0")); |
42
|
|
|
$iv_len = $this->gcm_len($IV); |
43
|
|
|
if($iv_len == 96) { |
44
|
|
|
$J0 = $IV . pack('H*', '00000001'); |
45
|
|
|
} else { |
46
|
|
|
$s = (128 * ceil($iv_len / 128)) - $iv_len; |
47
|
|
|
if(($s + 64) % 8) |
48
|
|
|
die("gcm_encrypt s {$s} + 64 not byte size"); |
|
|
|
|
49
|
|
|
$packed_iv_len = pack('N', $iv_len); |
50
|
|
|
$iv_len_padding = StringUtil::addPadding($packed_iv_len, 8, "\0", STR_PAD_LEFT); |
51
|
|
|
$hash_X = $IV . StringUtil::addPadding('', ($s + 64) / 8, "\0") . $iv_len_padding; |
52
|
|
|
$J0 = $this->gcm_hash($H, $hash_X); |
53
|
|
|
} |
54
|
|
|
$C = $this->gcm_gctr($K, $this->gcm_inc(32, $J0), $P); |
55
|
|
|
|
56
|
|
|
$u = (128 * ceil($this->gcm_len($C) / 128)) - $this->gcm_len($C); |
57
|
|
|
$v = (128 * ceil($this->gcm_len($A) / 128)) - $this->gcm_len($A); |
58
|
|
|
$a_len_padding = StringUtil::addPadding(pack('N', $this->gcm_len($A)), 8, "\0", STR_PAD_LEFT); |
59
|
|
|
$c_len_padding = StringUtil::addPadding(pack('N', $this->gcm_len($C)), 8, "\0", STR_PAD_LEFT); |
60
|
|
|
|
61
|
|
|
$S = $this->gcm_hash($H, $A . StringUtil::addPadding('', $v / 8, "\0") . $C . StringUtil::addPadding('', $u / 8, "\0") . $a_len_padding . $c_len_padding); |
62
|
|
|
$T = $this->gcm_MSB($t, $this->gcm_gctr($K, $J0, $S)); |
63
|
|
|
mcrypt_generic_deinit($cipher); |
64
|
|
|
mcrypt_module_close($cipher); |
65
|
|
|
return array($C, $T); |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* @param string $K |
70
|
|
|
* @param string $IV |
71
|
|
|
* @param string $C |
72
|
|
|
* @param string $A |
73
|
|
|
* @param string $T |
74
|
|
|
* |
75
|
|
|
* @return array|null |
76
|
|
|
*/ |
77
|
|
|
public function gcm_decrypt($K, $IV, $C, $A, $T) { |
78
|
|
|
|
79
|
|
|
$cipher = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_ECB, ''); |
80
|
|
|
if(!$cipher) |
81
|
|
|
return NULL; |
82
|
|
|
$key_length = StringUtil::getStringLength($K) * 8; |
83
|
|
|
|
84
|
|
|
if($key_length != 128 && $key_length != 192 && $key_length != 256) { |
85
|
|
|
die("encryp invalid key length\n"); |
|
|
|
|
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
$iv_size = mcrypt_enc_get_iv_size($cipher); |
|
|
|
|
89
|
|
|
$iv = str_repeat(chr(0), 16); // initialize to 16 byte string of "0"s |
90
|
|
|
$s = mcrypt_generic_init($cipher, $K, $iv); |
91
|
|
|
if( ($s < 0) || ($s === false)) { |
92
|
|
|
die("encryp mcrypt init error $s"); |
|
|
|
|
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
$H = mcrypt_generic($cipher, StringUtil::addPadding('', 16, "\0")); |
96
|
|
|
|
97
|
|
|
$iv_len = $this->gcm_len($IV); |
98
|
|
|
if($iv_len == 96) { |
99
|
|
|
$J0 = $IV . pack('H*', '00000001'); |
100
|
|
|
} else { |
101
|
|
|
$s = (128 * ceil($iv_len / 128)) - $iv_len; |
102
|
|
|
if(($s + 64) % 8) |
103
|
|
|
die("gcm_encrypt s {$s} + 64 not byte size"); |
|
|
|
|
104
|
|
|
$packed_iv_len = pack('N', $iv_len); |
105
|
|
|
$iv_len_padding = StringUtil::addPadding($packed_iv_len, 8, "\0", STR_PAD_LEFT); |
106
|
|
|
$hash_X = $IV . StringUtil::addPadding('', ($s + 64) / 8, "\0") . $iv_len_padding; |
107
|
|
|
$J0 = $this->gcm_hash($H, $hash_X); |
108
|
|
|
} |
109
|
|
|
$P = $this->gcm_gctr($K, $this->gcm_inc(32, $J0), $C); |
110
|
|
|
|
111
|
|
|
$u = (128 * ceil($this->gcm_len($C) / 128)) - $this->gcm_len($C); |
112
|
|
|
$v = (128 * ceil($this->gcm_len($A) / 128)) - $this->gcm_len($A); |
113
|
|
|
$a_len_padding = StringUtil::addPadding(pack('N', $this->gcm_len($A)), 8, "\0", STR_PAD_LEFT); |
114
|
|
|
$c_len_padding = StringUtil::addPadding(pack('N', $this->gcm_len($C)), 8, "\0", STR_PAD_LEFT); |
115
|
|
|
|
116
|
|
|
$S = $this->gcm_hash($H, $A . StringUtil::addPadding('', $v / 8, "\0") . $C . StringUtil::addPadding('', $u / 8, "\0") . $a_len_padding . $c_len_padding); |
117
|
|
|
$T1 = $this->gcm_MSB($this->gcm_len($T), $this->gcm_gctr($K, $J0, $S)); |
118
|
|
|
$result = strcmp($T, $T1); |
119
|
|
|
if($result) |
120
|
|
|
return null; |
121
|
|
|
mcrypt_generic_deinit($cipher); |
122
|
|
|
mcrypt_module_close($cipher); |
123
|
|
|
return $P; |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
/** |
127
|
|
|
* @param string $x |
128
|
|
|
* |
129
|
|
|
* @return int |
130
|
|
|
*/ |
131
|
|
|
private function gcm_len($x) |
132
|
|
|
{ |
133
|
|
|
return StringUtil::getStringLength($x) * 8; |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
/** |
137
|
|
|
* @param int $num_bits |
138
|
|
|
* @param int $x |
139
|
|
|
* |
140
|
|
|
* @return string |
141
|
|
|
*/ |
142
|
|
|
private function gcm_MSB($num_bits, $x) { |
143
|
|
|
if(!$num_bits || !$x) |
144
|
|
|
die('gcm_MSB invalid params'); |
|
|
|
|
145
|
|
|
if($num_bits % 8) |
146
|
|
|
die('gcm_MSB num_bits is not byte size'); |
|
|
|
|
147
|
|
|
$num_bytes = $num_bits / 8; |
148
|
|
|
$len_x = StringUtil::getStringLength($x); |
149
|
|
|
if($num_bytes > StringUtil::getStringLength($x)) |
150
|
|
|
die("gcm_MSB num_bits {$num_bits} bytes({$num_bytes}) > x {$len_x}"); |
|
|
|
|
151
|
|
|
return substr($x, 0, $num_bytes); |
152
|
|
|
|
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
/** |
156
|
|
|
* @param int $num_bits |
157
|
|
|
* @param int $x |
158
|
|
|
* |
159
|
|
|
* @return string |
160
|
|
|
*/ |
161
|
|
|
private function gcm_LSB($num_bits, $x) { |
162
|
|
|
if(!$num_bits || !$x) |
163
|
|
|
die('gcm_LSB invalid params'); |
|
|
|
|
164
|
|
|
if($num_bits % 8) |
165
|
|
|
die('gcm_LSB num_bits is not byte size'); |
|
|
|
|
166
|
|
|
$num_bytes = ($num_bits / 8); |
167
|
|
|
if($num_bytes > StringUtil::getStringLength($x)) |
168
|
|
|
die("gcm_LSB num_bits {$num_bits} > x {$x}"); |
|
|
|
|
169
|
|
|
return substr($x, $num_bytes * -1); |
170
|
|
|
|
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
/** |
174
|
|
|
* @param int $s_bits |
175
|
|
|
* @param int $x |
176
|
|
|
* |
177
|
|
|
* @return string |
178
|
|
|
*/ |
179
|
|
|
private function gcm_inc($s_bits, $x) { |
180
|
|
|
if(!$s_bits || $s_bits != 32) |
181
|
|
|
die("gcm_inc invalid s_bits"); |
|
|
|
|
182
|
|
|
if(!$x) |
183
|
|
|
die("gcm_inc invalid x"); |
|
|
|
|
184
|
|
|
if($s_bits % 8) |
185
|
|
|
die('gcm_inc s_bits is not byte size'); |
|
|
|
|
186
|
|
|
$lsb = $this->gcm_LSB($s_bits, $x); |
187
|
|
|
$X = ($this->_uint32be($lsb) + 1); |
188
|
|
|
$res = $this->gcm_MSB($this->gcm_len($x) - $s_bits, $x) . pack('N', $X); |
189
|
|
|
return $res; |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
/** |
193
|
|
|
* @param string $bin |
194
|
|
|
* |
195
|
|
|
* @return mixed |
196
|
|
|
*/ |
197
|
|
|
private function _uint32be($bin) |
198
|
|
|
{ |
199
|
|
|
// $bin is the binary 32-bit BE string that represents the integer |
200
|
|
|
// $int_size = PHP_INT_SIZE; |
201
|
|
|
$int_size = 4; |
202
|
|
|
if ($int_size <= 4){ |
203
|
|
|
list(,$h,$l) = unpack('n*', $bin); |
204
|
|
|
return ($l + ($h*0x010000)); |
205
|
|
|
} |
206
|
|
|
else{ |
207
|
|
|
list(,$int) = unpack('N', $bin); |
208
|
|
|
return $int; |
209
|
|
|
} |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
/** |
213
|
|
|
* @param $X |
214
|
|
|
* @param $Y |
215
|
|
|
* |
216
|
|
|
* @return string |
217
|
|
|
*/ |
218
|
|
|
private function gcm_product($X, $Y) { |
219
|
|
|
$R = pack('H*', 'E1') . StringUtil::addPadding('', 15, "\0"); |
220
|
|
|
$Z = StringUtil::addPadding('', 16, "\0"); |
221
|
|
|
$V = $Y; |
222
|
|
|
if(StringUtil::getStringLength($X) != 16) |
223
|
|
|
die('Invalid length for X'); |
|
|
|
|
224
|
|
|
$parts = str_split($X, 4); |
225
|
|
|
$x = sprintf("%032b%032b%032b%032b", $this->_uint32be($parts[0]), $this->_uint32be($parts[1]), $this->_uint32be($parts[2]), $this->_uint32be($parts[3])); |
226
|
|
|
$lsb_mask = "\1"; |
227
|
|
|
for($i = 0; $i < 128; $i++) { |
228
|
|
|
if($x[$i]) |
229
|
|
|
$Z = $this->bitxor($Z, $V); |
230
|
|
|
$lsb_8 = substr($V, -1); |
231
|
|
|
if(ord($lsb_8 & $lsb_mask)) |
232
|
|
|
$V = $this->bitxor($this->str_right_shift($V), $R); |
233
|
|
|
else |
234
|
|
|
$V = $this->str_right_shift($V); |
235
|
|
|
} |
236
|
|
|
return $Z; |
237
|
|
|
} |
238
|
|
|
|
239
|
|
|
/** |
240
|
|
|
* @param string $input |
241
|
|
|
* |
242
|
|
|
* @return string |
243
|
|
|
*/ |
244
|
|
|
private function str_right_shift($input) { |
245
|
|
|
// $width = PHP_INT_SIZE; // doesn't work well on 64-bit systems |
246
|
|
|
$width = 4; |
247
|
|
|
$parts = array_map([$this, '_uint32be'], str_split($input, $width)); |
248
|
|
|
$runs = count($parts); |
249
|
|
|
$len = StringUtil::getStringLength($input) / 4; |
250
|
|
|
if(!is_int($len)) |
251
|
|
|
die('not int len'); |
|
|
|
|
252
|
|
|
for($i=$runs - 1; $i >= 0; $i--) { |
253
|
|
|
if($i) { |
254
|
|
|
$lsb1 = $parts[$i - 1] & 0x00000001; |
255
|
|
|
if($lsb1) { |
256
|
|
|
$parts[$i] = ($parts[$i] >> 1) | 0x80000000; |
257
|
|
|
$parts[$i] = pack('N', $parts[$i]); |
258
|
|
|
continue; |
259
|
|
|
} |
260
|
|
|
} |
261
|
|
|
$parts[$i] = ($parts[$i] >> 1) & 0x7FFFFFFF; // get rid of sign bit |
262
|
|
|
$parts[$i] = pack('N', $parts[$i]); |
263
|
|
|
} |
264
|
|
|
$res = implode('', $parts); |
265
|
|
|
return $res; |
266
|
|
|
} |
267
|
|
|
|
268
|
|
|
/** |
269
|
|
|
* @param string $H |
270
|
|
|
* @param string $X |
271
|
|
|
* |
272
|
|
|
* @return mixed |
273
|
|
|
*/ |
274
|
|
|
private function gcm_hash($H, $X) { |
275
|
|
|
if(!$H or !$X) |
276
|
|
|
die("gcm_hash invalid params"); |
|
|
|
|
277
|
|
|
if(StringUtil::getStringLength($X) % 16) |
278
|
|
|
die("gcm_hash X is not multiple of 16 bytes"); |
|
|
|
|
279
|
|
|
$Y = array(); |
280
|
|
|
$Y[0] = StringUtil::addPadding('', 16, "\0"); |
281
|
|
|
$num_blocks = StringUtil::getStringLength($X) / 16; |
282
|
|
|
for($i = 1; $i <= $num_blocks; $i++) { |
283
|
|
|
$Y[$i] = $this->gcm_product($this->bitxor($Y[$i - 1], substr($X, ($i - 1) * 16, 16)), $H); |
284
|
|
|
} |
285
|
|
|
return $Y[$num_blocks]; |
286
|
|
|
} |
287
|
|
|
|
288
|
|
|
/** |
289
|
|
|
* @param string $K |
290
|
|
|
* @param string $ICB |
291
|
|
|
* @param string $X |
292
|
|
|
* |
293
|
|
|
* @return null|string |
294
|
|
|
*/ |
295
|
|
|
private function gcm_gctr($K, $ICB, $X) { |
296
|
|
|
if($X == '') |
297
|
|
|
return ''; |
298
|
|
|
|
299
|
|
|
$cipher = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_ECB, ''); |
300
|
|
|
if(!$cipher) |
301
|
|
|
return NULL; |
302
|
|
|
$key_length = StringUtil::getStringLength($K) * 8; |
303
|
|
|
|
304
|
|
|
if($key_length != 128 && $key_length != 192 && $key_length != 256) { |
305
|
|
|
die("gcm_gctr invalid key length\n"); |
|
|
|
|
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
$iv_size = mcrypt_enc_get_iv_size($cipher); |
|
|
|
|
309
|
|
|
$iv = str_repeat(chr(0), 16); // initialize to 16 byte string of "0"s |
310
|
|
|
$s = mcrypt_generic_init($cipher, $K, $iv); |
311
|
|
|
if( ($s < 0) || ($s === false)) { |
312
|
|
|
die("gcm_gctr mcrypt init error $s"); |
|
|
|
|
313
|
|
|
} |
314
|
|
|
|
315
|
|
|
$n = ceil($this->gcm_len($X) / 128); |
316
|
|
|
$CB = array(); |
317
|
|
|
$Y = array(); |
318
|
|
|
$CB[1] = $ICB; |
319
|
|
|
for($i = 2; $i <= $n; $i++) { |
320
|
|
|
$CB[$i] = $this->gcm_inc(32, $CB[$i - 1]); |
321
|
|
|
} |
322
|
|
|
for($i = 1; $i < $n; $i++) { |
323
|
|
|
$C = mcrypt_generic($cipher, $CB[$i]); |
324
|
|
|
$Y[$i] = $this->bitxor(substr($X, ($i - 1) * 16, 16), $C); |
325
|
|
|
} |
326
|
|
|
|
327
|
|
|
$Xn = substr($X, ($n - 1) * 16); |
328
|
|
|
$C = mcrypt_generic($cipher, $CB[$n]); |
329
|
|
|
$Y[$n] = $this->bitxor($Xn, $this->gcm_MSB($this->gcm_len($Xn), $C)); |
330
|
|
|
mcrypt_generic_deinit($cipher); |
331
|
|
|
mcrypt_module_close($cipher); |
332
|
|
|
return implode('', $Y); |
333
|
|
|
} |
334
|
|
|
|
335
|
|
|
/** |
336
|
|
|
* @param string $o1 |
337
|
|
|
* @param string $o2 |
338
|
|
|
* |
339
|
|
|
* @return string |
340
|
|
|
*/ |
341
|
|
|
private function bitxor($o1, $o2) { |
342
|
|
|
$xorWidth = PHP_INT_SIZE; |
343
|
|
|
$o1 = str_split($o1, $xorWidth); |
344
|
|
|
$o2 = str_split($o2, $xorWidth); |
345
|
|
|
$res = ''; |
346
|
|
|
$runs = count($o1); |
347
|
|
|
for($i=0;$i<$runs;$i++) |
348
|
|
|
$res .= $o1[$i] ^ $o2[$i]; |
349
|
|
|
return $res; |
350
|
|
|
} |
351
|
|
|
} |
352
|
|
|
|
An exit expression should only be used in rare cases. For example, if you write a short command line script.
In most cases however, using an
exit
expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.