These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
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()); |
||
0 ignored issues
–
show
|
|||
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()); |
||
0 ignored issues
–
show
It seems like
$signature defined by self::RSASP1($key, $message) on line 356 can also be of type false ; however, Jose\Util\RSA::convertIntegerToOctetString() does only seem to accept object<Jose\Util\BigInteger> , did you maybe forget to handle an error condition?
This check looks for type mismatches where the missing type is Consider the follow example <?php
function getDate($date)
{
if ($date !== null) {
return new DateTime($date);
}
return false;
}
This function either returns a new ![]() |
|||
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()); |
||
0 ignored issues
–
show
It seems like
$em defined by self::convertIntegerToOc...ing($m2, $modBits >> 3) on line 383 can also be of type false ; however, Jose\Util\RSA::verifyEMSAPSS() does only seem to accept string , did you maybe forget to handle an error condition?
This check looks for type mismatches where the missing type is Consider the follow example <?php
function getDate($date)
{
if ($date !== null) {
return new DateTime($date);
}
return false;
}
This function either returns a new ![]() |
|||
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
.