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
|
|
|
use Assert\Assertion; |
15
|
|
|
use Jose\KeyConverter\RSAKey; |
16
|
|
|
|
17
|
|
|
final class RSA |
18
|
|
|
{ |
19
|
|
|
/** |
20
|
|
|
* Integer-to-Octet-String primitive. |
21
|
|
|
* |
22
|
|
|
* @param \Jose\Util\BigInteger $x |
23
|
|
|
* @param int $xLen |
24
|
|
|
* |
25
|
|
|
* @return string |
26
|
|
|
*/ |
27
|
|
|
private static function convertIntegerToOctetString($x, $xLen) |
28
|
|
|
{ |
29
|
|
|
$x = $x->toBytes(); |
30
|
|
|
if (strlen($x) > $xLen) { |
31
|
|
|
return false; |
32
|
|
|
} |
33
|
|
|
|
34
|
|
|
return str_pad($x, $xLen, chr(0), STR_PAD_LEFT); |
35
|
|
|
} |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* Octet-String-to-Integer primitive. |
39
|
|
|
* |
40
|
|
|
* @param string $x |
41
|
|
|
* |
42
|
|
|
* @return \Jose\Util\BigInteger |
43
|
|
|
*/ |
44
|
|
|
private static function convertOctetStringToInteger($x) |
45
|
|
|
{ |
46
|
|
|
return BigInteger::createFromBinaryString($x); |
47
|
|
|
} |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* Exponentiate with or without Chinese Remainder Theorem. |
51
|
|
|
* Operation with primes 'p' and 'q' is appox. 2x faster. |
52
|
|
|
* |
53
|
|
|
* @param \Jose\KeyConverter\RSAKey $key |
54
|
|
|
* @param \Jose\Util\BigInteger $c |
55
|
|
|
* |
56
|
|
|
* @return \Jose\Util\BigInteger |
57
|
|
|
*/ |
58
|
|
|
private static function exponentiate(RSAKey $key, $c) |
59
|
|
|
{ |
60
|
|
|
if ($key->isPublic() || empty($key->getPrimes())) { |
61
|
|
|
return $c->modPow($key->getExponent(), $key->getModulus()); |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
$p = $key->getPrimes()[0]; |
65
|
|
|
$q = $key->getPrimes()[1]; |
66
|
|
|
$dP = $key->getExponents()[0]; |
67
|
|
|
$dQ = $key->getExponents()[1]; |
68
|
|
|
$qInv = $key->getCoefficient(); |
69
|
|
|
|
70
|
|
|
$m1 = $c->modPow($dP, $p); |
71
|
|
|
$m2 = $c->modPow($dQ, $q); |
72
|
|
|
$h = $qInv->multiply($m1->subtract($m2)->add($p))->mod($p); |
73
|
|
|
$m = $m2->add($h->multiply($q)); |
74
|
|
|
|
75
|
|
|
return $m; |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* RSAEP. |
80
|
|
|
* |
81
|
|
|
* @param \Jose\KeyConverter\RSAKey $key |
82
|
|
|
* @param \Jose\Util\BigInteger $m |
83
|
|
|
* |
84
|
|
|
* @return \Jose\Util\BigInteger|false |
85
|
|
|
*/ |
86
|
|
|
private static function RSAEP(RSAKey $key, BigInteger $m) |
87
|
|
|
{ |
88
|
|
|
if ($m->compare(BigInteger::createFromDecimal(0)) < 0 || $m->compare($key->getModulus()) > 0) { |
89
|
|
|
return false; |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
return self::exponentiate($key, $m); |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
/** |
96
|
|
|
* RSADP. |
97
|
|
|
* |
98
|
|
|
* @param \Jose\KeyConverter\RSAKey $key |
99
|
|
|
* @param \Jose\Util\BigInteger $c |
100
|
|
|
* |
101
|
|
|
* @return \Jose\Util\BigInteger|false |
102
|
|
|
*/ |
103
|
|
|
private static function RSADP(RSAKey $key, BigInteger $c) |
104
|
|
|
{ |
105
|
|
|
if ($c->compare(BigInteger::createFromDecimal(0)) < 0 || $c->compare($key->getModulus()) > 0) { |
106
|
|
|
return false; |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
return self::exponentiate($key, $c); |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
/** |
113
|
|
|
* RSASP1. |
114
|
|
|
* |
115
|
|
|
* @param \Jose\KeyConverter\RSAKey $key |
116
|
|
|
* @param \Jose\Util\BigInteger $m |
117
|
|
|
* |
118
|
|
|
* @return \Jose\Util\BigInteger|false |
119
|
|
|
*/ |
120
|
|
|
private static function RSASP1(RSAKey $key, BigInteger $m) |
121
|
|
|
{ |
122
|
|
|
if ($m->compare(BigInteger::createFromDecimal(0)) < 0 || $m->compare($key->getModulus()) > 0) { |
123
|
|
|
return false; |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
return self::exponentiate($key, $m); |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* RSAVP1. |
131
|
|
|
* |
132
|
|
|
* @param \Jose\KeyConverter\RSAKey $key |
133
|
|
|
* @param \Jose\Util\BigInteger $s |
134
|
|
|
* |
135
|
|
|
* @return \Jose\Util\BigInteger|false |
136
|
|
|
*/ |
137
|
|
|
private static function _rsavp1(RSAKey $key, BigInteger $s) |
138
|
|
|
{ |
139
|
|
|
if ($s->compare(BigInteger::createFromDecimal(0)) < 0 || $s->compare($key->getModulus()) > 0) { |
140
|
|
|
return false; |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
return self::exponentiate($key, $s); |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
/** |
147
|
|
|
* MGF1. |
148
|
|
|
* |
149
|
|
|
* @param string $mgfSeed |
150
|
|
|
* @param int $maskLen |
151
|
|
|
* @param \Jose\Util\Hash $mgfHash |
152
|
|
|
* |
153
|
|
|
* @return string |
154
|
|
|
*/ |
155
|
|
|
private static function _mgf1($mgfSeed, $maskLen, Hash $mgfHash) |
156
|
|
|
{ |
157
|
|
|
$t = ''; |
158
|
|
|
$count = ceil($maskLen / $mgfHash->getLength()); |
159
|
|
|
for ($i = 0; $i < $count; $i++) { |
160
|
|
|
$c = pack('N', $i); |
161
|
|
|
$t .= $mgfHash->hash($mgfSeed.$c); |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
return mb_substr($t, 0, $maskLen, '8bit'); |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
/** |
168
|
|
|
* RSAES-OAEP-ENCRYPT. |
169
|
|
|
* |
170
|
|
|
* @param \Jose\KeyConverter\RSAKey $key |
171
|
|
|
* @param string $m |
172
|
|
|
* @param \Jose\Util\Hash $hash |
173
|
|
|
* |
174
|
|
|
* @return string |
175
|
|
|
*/ |
176
|
|
|
private static function RSAESOAEPEncrypt(RSAKey $key, $m, Hash $hash) |
177
|
|
|
{ |
178
|
|
|
$mLen = mb_strlen($m, '8bit'); |
179
|
|
|
$lHash = $hash->hash(''); |
180
|
|
|
$ps = str_repeat(chr(0), $key->getModulusLength() - $mLen - 2 * $hash->getLength() - 2); |
181
|
|
|
$db = $lHash.$ps.chr(1).$m; |
182
|
|
|
$seed = random_bytes($hash->getLength()); |
183
|
|
|
$dbMask = self::_mgf1($seed, $key->getModulusLength() - $hash->getLength() - 1, $hash/*MGF*/); |
184
|
|
|
$maskedDB = $db ^ $dbMask; |
185
|
|
|
$seedMask = self::_mgf1($maskedDB, $hash->getLength(), $hash/*MGF*/); |
186
|
|
|
$maskedSeed = $seed ^ $seedMask; |
187
|
|
|
$em = chr(0).$maskedSeed.$maskedDB; |
188
|
|
|
|
189
|
|
|
$m = self::convertOctetStringToInteger($em); |
190
|
|
|
$c = self::RSAEP($key, $m); |
191
|
|
|
$c = self::convertIntegerToOctetString($c, $key->getModulusLength()); |
|
|
|
|
192
|
|
|
|
193
|
|
|
return $c; |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
/** |
197
|
|
|
* RSAES-OAEP-DECRYPT. |
198
|
|
|
* |
199
|
|
|
* @param \Jose\KeyConverter\RSAKey $key |
200
|
|
|
* @param string $c |
201
|
|
|
* @param \Jose\Util\Hash $hash |
202
|
|
|
* |
203
|
|
|
* @return string |
204
|
|
|
*/ |
205
|
|
|
private static function RSAESOAEPDecrypt(RSAKey $key, $c, Hash $hash) |
206
|
|
|
{ |
207
|
|
|
$c = self::convertOctetStringToInteger($c); |
208
|
|
|
$m = self::RSADP($key, $c); |
209
|
|
|
Assertion::isInstanceOf($m, BigInteger::class); |
210
|
|
|
$em = self::convertIntegerToOctetString($m, $key->getModulusLength()); |
|
|
|
|
211
|
|
|
$lHash = $hash->hash(''); |
212
|
|
|
$maskedSeed = mb_substr($em, 1, $hash->getLength(), '8bit'); |
213
|
|
|
$maskedDB = mb_substr($em, $hash->getLength() + 1, null, '8bit'); |
214
|
|
|
$seedMask = self::_mgf1($maskedDB, $hash->getLength(), $hash/*MGF*/); |
215
|
|
|
$seed = $maskedSeed ^ $seedMask; |
216
|
|
|
$dbMask = self::_mgf1($seed, $key->getModulusLength() - $hash->getLength() - 1, $hash/*MGF*/); |
217
|
|
|
$db = $maskedDB ^ $dbMask; |
218
|
|
|
$lHash2 = mb_substr($db, 0, $hash->getLength(), '8bit'); |
219
|
|
|
$m = mb_substr($db, $hash->getLength(), null, '8bit'); |
220
|
|
|
Assertion::eq($lHash, $lHash2); |
221
|
|
|
$m = ltrim($m, chr(0)); |
222
|
|
|
Assertion::eq(ord($m[0]), 1); |
223
|
|
|
|
224
|
|
|
return mb_substr($m, 1, null, '8bit'); |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
/** |
228
|
|
|
* EMSA-PSS-ENCODE. |
229
|
|
|
* |
230
|
|
|
* @param string $m |
231
|
|
|
* @param int $emBits |
232
|
|
|
* @param \Jose\Util\Hash $hash |
233
|
|
|
* |
234
|
|
|
* @return string|bool |
235
|
|
|
*/ |
236
|
|
|
private static function encodeEMSAPSS($m, $emBits, Hash $hash) |
237
|
|
|
{ |
238
|
|
|
$emLen = ($emBits + 1) >> 3; |
239
|
|
|
$sLen = $hash->getLength(); |
240
|
|
|
$mHash = $hash->hash($m); |
241
|
|
|
Assertion::greaterThan($emLen , $hash->getLength() + $sLen + 2); |
242
|
|
|
$salt = random_bytes($sLen); |
243
|
|
|
$m2 = "\0\0\0\0\0\0\0\0".$mHash.$salt; |
244
|
|
|
$h = $hash->hash($m2); |
245
|
|
|
$ps = str_repeat(chr(0), $emLen - $sLen - $hash->getLength() - 2); |
246
|
|
|
$db = $ps.chr(1).$salt; |
247
|
|
|
$dbMask = self::_mgf1($h, $emLen - $hash->getLength() - 1, $hash/*MGF*/); |
248
|
|
|
$maskedDB = $db ^ $dbMask; |
249
|
|
|
$maskedDB[0] = ~chr(0xFF << ($emBits & 7)) & $maskedDB[0]; |
250
|
|
|
$em = $maskedDB.$h.chr(0xBC); |
251
|
|
|
|
252
|
|
|
return $em; |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
/** |
256
|
|
|
* EMSA-PSS-VERIFY. |
257
|
|
|
* |
258
|
|
|
* @param string $m |
259
|
|
|
* @param string $em |
260
|
|
|
* @param int $emBits |
261
|
|
|
* @param \Jose\Util\Hash $hash |
262
|
|
|
* |
263
|
|
|
* @return string |
264
|
|
|
*/ |
265
|
|
|
private static function verifyEMSAPSS($m, $em, $emBits, Hash $hash) |
266
|
|
|
{ |
267
|
|
|
$emLen = ($emBits + 1) >> 3; |
268
|
|
|
$sLen = $hash->getLength(); |
269
|
|
|
$mHash = $hash->hash($m); |
270
|
|
|
Assertion::greaterThan($emLen, $hash->getLength() + $sLen + 2); |
271
|
|
|
Assertion::eq($em[mb_strlen($em, '8bit') - 1], chr(0xBC)); |
272
|
|
|
$maskedDB = mb_substr($em, 0, -$hash->getLength() - 1, '8bit'); |
273
|
|
|
$h = mb_substr($em, -$hash->getLength() - 1, $hash->getLength(), '8bit'); |
274
|
|
|
$temp = chr(0xFF << ($emBits & 7)); |
275
|
|
|
Assertion::eq(~$maskedDB[0] & $temp, $temp); |
276
|
|
|
$dbMask = self::_mgf1($h, $emLen - $hash->getLength() - 1, $hash/*MGF*/); |
277
|
|
|
$db = $maskedDB ^ $dbMask; |
278
|
|
|
$db[0] = ~chr(0xFF << ($emBits & 7)) & $db[0]; |
279
|
|
|
$temp = $emLen - $hash->getLength() - $sLen - 2; |
280
|
|
|
Assertion::eq(mb_substr($db, 0, $temp, '8bit'), str_repeat(chr(0), $temp)); |
281
|
|
|
Assertion::eq(ord($db[$temp]), 1); |
282
|
|
|
$salt = mb_substr($db, $temp + 1, null, '8bit'); // should be $sLen long |
283
|
|
|
$m2 = "\0\0\0\0\0\0\0\0".$mHash.$salt; |
284
|
|
|
$h2 = $hash->hash($m2); |
285
|
|
|
|
286
|
|
|
return hash_equals($h, $h2); |
287
|
|
|
} |
288
|
|
|
|
289
|
|
|
/** |
290
|
|
|
* Encryption. |
291
|
|
|
* |
292
|
|
|
* @param \Jose\KeyConverter\RSAKey $key |
293
|
|
|
* @param string $plaintext |
294
|
|
|
* @param string $hash_algorithm |
295
|
|
|
* |
296
|
|
|
* @return string |
297
|
|
|
*/ |
298
|
|
|
public static function encrypt(RSAKey $key, $plaintext, $hash_algorithm) |
299
|
|
|
{ |
300
|
|
|
/** |
301
|
|
|
* @var $hash Hash |
302
|
|
|
*/ |
303
|
|
|
$hash = Hash::$hash_algorithm(); |
304
|
|
|
$length = $key->getModulusLength() - 2 * $hash->getLength() - 2; |
305
|
|
|
Assertion::greaterThan($length, 0); |
306
|
|
|
$plaintext = str_split($plaintext, $length); |
307
|
|
|
$ciphertext = ''; |
308
|
|
|
foreach ($plaintext as $m) { |
309
|
|
|
$ciphertext .= self::RSAESOAEPEncrypt($key, $m, $hash); |
310
|
|
|
} |
311
|
|
|
|
312
|
|
|
return $ciphertext; |
313
|
|
|
} |
314
|
|
|
|
315
|
|
|
/** |
316
|
|
|
* Decryption. |
317
|
|
|
* |
318
|
|
|
* @param \Jose\KeyConverter\RSAKey $key |
319
|
|
|
* @param string $ciphertext |
320
|
|
|
* @param string $hash_algorithm |
321
|
|
|
* |
322
|
|
|
* @return string |
323
|
|
|
*/ |
324
|
|
|
public static function decrypt(RSAKey $key, $ciphertext, $hash_algorithm) |
325
|
|
|
{ |
326
|
|
|
Assertion::greaterThan($key->getModulusLength(), 0); |
327
|
|
|
$hash = Hash::$hash_algorithm(); |
328
|
|
|
$ciphertext = str_split($ciphertext, $key->getModulusLength()); |
329
|
|
|
$ciphertext[count($ciphertext) - 1] = str_pad($ciphertext[count($ciphertext) - 1], $key->getModulusLength(), chr(0), STR_PAD_LEFT); |
330
|
|
|
$plaintext = ''; |
331
|
|
|
foreach ($ciphertext as $c) { |
332
|
|
|
$temp = self::RSAESOAEPDecrypt($key, $c, $hash); |
333
|
|
|
$plaintext .= $temp; |
334
|
|
|
} |
335
|
|
|
|
336
|
|
|
return $plaintext; |
337
|
|
|
} |
338
|
|
|
|
339
|
|
|
/** |
340
|
|
|
* Create a signature. |
341
|
|
|
* |
342
|
|
|
* @param \Jose\KeyConverter\RSAKey $key |
343
|
|
|
* @param string $message |
344
|
|
|
* @param string $hash |
345
|
|
|
* |
346
|
|
|
* @return string |
347
|
|
|
*/ |
348
|
|
|
public static function sign(RSAKey $key, $message, $hash) |
349
|
|
|
{ |
350
|
|
|
Assertion::string($message); |
351
|
|
|
Assertion::string($hash); |
352
|
|
|
Assertion::inArray($hash, ['sha256', 'sha384', 'sha512']); |
353
|
|
|
$em = self::encodeEMSAPSS($message, 8 * $key->getModulusLength() - 1, Hash::$hash()); |
354
|
|
|
Assertion::string($em); |
355
|
|
|
$message = self::convertOctetStringToInteger($em); |
|
|
|
|
356
|
|
|
$signature = self::RSASP1($key, $message); |
357
|
|
|
Assertion::isInstanceOf($signature, BigInteger::class); |
358
|
|
|
|
359
|
|
|
return self::convertIntegerToOctetString($signature, $key->getModulusLength()); |
|
|
|
|
360
|
|
|
} |
361
|
|
|
|
362
|
|
|
/** |
363
|
|
|
* Verifies a signature. |
364
|
|
|
* |
365
|
|
|
* @param \Jose\KeyConverter\RSAKey $key |
366
|
|
|
* @param string $message |
367
|
|
|
* @param string $signature |
368
|
|
|
* @param string $hash |
369
|
|
|
* |
370
|
|
|
* @return bool |
371
|
|
|
*/ |
372
|
|
|
public static function verify(RSAKey $key, $message, $signature, $hash) |
373
|
|
|
{ |
374
|
|
|
Assertion::string($message); |
375
|
|
|
Assertion::string($signature); |
376
|
|
|
Assertion::string($hash); |
377
|
|
|
Assertion::inArray($hash, ['sha256', 'sha384', 'sha512']); |
378
|
|
|
Assertion::eq(strlen($signature), $key->getModulusLength()); |
379
|
|
|
$modBits = 8 * $key->getModulusLength(); |
380
|
|
|
$s2 = self::convertOctetStringToInteger($signature); |
381
|
|
|
$m2 = self::_rsavp1($key, $s2); |
382
|
|
|
Assertion::isInstanceOf($m2, BigInteger::class); |
383
|
|
|
$em = self::convertIntegerToOctetString($m2, $modBits >> 3); |
|
|
|
|
384
|
|
|
|
385
|
|
|
return self::verifyEMSAPSS($message, $em, $modBits - 1, Hash::$hash()); |
|
|
|
|
386
|
|
|
} |
387
|
|
|
} |
388
|
|
|
|
This check looks for type mismatches where the missing type is
false
. This is usually indicative of an error condtion.Consider the follow example
This function either returns a new
DateTime
object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returnedfalse
before passing on the value to another function or method that may not be able to handle afalse
.