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, BigInteger $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 | * RSA EP. |
||
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 getRSAEP(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 | * RSA DP. |
||
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 getRSADP(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 | * RSA SP1. |
||
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 getRSASP1(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 getRSAVP1(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 getMGF1($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 encryptRSAESOAEP(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::getMGF1($seed, $key->getModulusLength() - $hash->getLength() - 1, $hash/*MGF*/); |
||
184 | $maskedDB = $db ^ $dbMask; |
||
185 | $seedMask = self::getMGF1($maskedDB, $hash->getLength(), $hash/*MGF*/); |
||
186 | $maskedSeed = $seed ^ $seedMask; |
||
187 | $em = chr(0).$maskedSeed.$maskedDB; |
||
188 | |||
189 | $m = self::convertOctetStringToInteger($em); |
||
190 | $c = self::getRSAEP($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 getRSAESOAEP(RSAKey $key, $c, Hash $hash) |
||
206 | { |
||
207 | $c = self::convertOctetStringToInteger($c); |
||
208 | $m = self::getRSADP($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::getMGF1($maskedDB, $hash->getLength(), $hash/*MGF*/); |
||
215 | $seed = $maskedSeed ^ $seedMask; |
||
216 | $dbMask = self::getMGF1($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::getMGF1($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::getMGF1($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 |
||
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::encryptRSAESOAEP($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::getRSAESOAEP($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::getRSASP1($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::getRSASP1($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::getRSAVP1($key, $s2); |
||
382 | Assertion::isInstanceOf($m2, BigInteger::class); |
||
383 | $em = self::convertIntegerToOctetString($m2, $modBits >> 3); |
||
0 ignored issues
–
show
It seems like
$m2 defined by self::getRSAVP1($key, $s2) on line 381 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 ![]() |
|||
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
.