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()); |
||
0 ignored issues
–
show
Bug
Compatibility
introduced
by
![]() |
|||
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))->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::createFromDecimalString('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::createFromDecimalString('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::createFromDecimalString('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::createFromDecimalString('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 substr($t, 0, $maskLen); |
||
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 _rsaes_oaep_encrypt(RSAKey $key, $m, Hash $hash) |
||
177 | { |
||
178 | $mLen = strlen($m); |
||
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 _rsaes_oaep_decrypt(RSAKey $key, $c, Hash $hash) |
||
206 | { |
||
207 | $c = self::convertOctetStringToInteger($c); |
||
208 | $m = self::_rsadp($key, $c); |
||
209 | |||
210 | Assertion::isInstanceOf($m, BigInteger::class); |
||
211 | |||
212 | $em = self::convertIntegerToOctetString($m, $key->getModulusLength()); |
||
213 | |||
214 | $lHash = $hash->hash(''); |
||
215 | $maskedSeed = substr($em, 1, $hash->getLength()); |
||
216 | $maskedDB = substr($em, $hash->getLength() + 1); |
||
217 | $seedMask = self::_mgf1($maskedDB, $hash->getLength(), $hash/*MGF*/); |
||
218 | $seed = $maskedSeed ^ $seedMask; |
||
219 | $dbMask = self::_mgf1($seed, $key->getModulusLength() - $hash->getLength() - 1, $hash/*MGF*/); |
||
220 | $db = $maskedDB ^ $dbMask; |
||
221 | $lHash2 = substr($db, 0, $hash->getLength()); |
||
222 | $m = substr($db, $hash->getLength()); |
||
223 | |||
224 | Assertion::eq($lHash, $lHash2); |
||
225 | |||
226 | $m = ltrim($m, chr(0)); |
||
227 | |||
228 | Assertion::eq(ord($m[0]), 1); |
||
229 | |||
230 | return substr($m, 1); |
||
231 | } |
||
232 | |||
233 | /** |
||
234 | * EMSA-PSS-ENCODE. |
||
235 | * |
||
236 | * @param string $m |
||
237 | * @param int $emBits |
||
238 | * @param \Jose\Util\Hash $hash |
||
239 | * |
||
240 | * @return string|bool |
||
241 | */ |
||
242 | private static function _emsa_pss_encode($m, $emBits, Hash $hash) |
||
243 | { |
||
244 | // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error |
||
245 | // be output. |
||
246 | |||
247 | $emLen = ($emBits + 1) >> 3; // ie. ceil($emBits / 8) |
||
248 | $sLen = $hash->getLength(); |
||
249 | |||
250 | $mHash = $hash->hash($m); |
||
251 | if ($emLen < $hash->getLength() + $sLen + 2) { |
||
252 | return false; |
||
253 | } |
||
254 | |||
255 | $salt = random_bytes($sLen); |
||
256 | $m2 = "\0\0\0\0\0\0\0\0".$mHash.$salt; |
||
257 | $h = $hash->hash($m2); |
||
258 | $ps = str_repeat(chr(0), $emLen - $sLen - $hash->getLength() - 2); |
||
259 | $db = $ps.chr(1).$salt; |
||
260 | $dbMask = self::_mgf1($h, $emLen - $hash->getLength() - 1, $hash/*MGF*/); |
||
261 | $maskedDB = $db ^ $dbMask; |
||
262 | $maskedDB[0] = ~chr(0xFF << ($emBits & 7)) & $maskedDB[0]; |
||
263 | $em = $maskedDB.$h.chr(0xBC); |
||
264 | |||
265 | return $em; |
||
266 | } |
||
267 | |||
268 | /** |
||
269 | * EMSA-PSS-VERIFY. |
||
270 | * |
||
271 | * @param string $m |
||
272 | * @param string $em |
||
273 | * @param int $emBits |
||
274 | * @param \Jose\Util\Hash $hash |
||
275 | * |
||
276 | * @return string |
||
277 | */ |
||
278 | private static function _emsa_pss_verify($m, $em, $emBits, Hash $hash) |
||
279 | { |
||
280 | // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error |
||
281 | // be output. |
||
282 | |||
283 | $emLen = ($emBits + 1) >> 3; // ie. ceil($emBits / 8); |
||
284 | $sLen = $hash->getLength(); |
||
285 | |||
286 | $mHash = $hash->hash($m); |
||
287 | if ($emLen < $hash->getLength() + $sLen + 2) { |
||
288 | return false; |
||
0 ignored issues
–
show
The return type of
return false; (false ) is incompatible with the return type documented by Jose\Util\RSA::_emsa_pss_verify of type string .
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design. Let’s take a look at an example: class Author {
private $name;
public function __construct($name) {
$this->name = $name;
}
public function getName() {
return $this->name;
}
}
abstract class Post {
public function getAuthor() {
return 'Johannes';
}
}
class BlogPost extends Post {
public function getAuthor() {
return new Author('Johannes');
}
}
class ForumPost extends Post { /* ... */ }
function my_function(Post $post) {
echo strtoupper($post->getAuthor());
}
Our function ![]() |
|||
289 | } |
||
290 | |||
291 | if ($em[strlen($em) - 1] != chr(0xBC)) { |
||
292 | return false; |
||
0 ignored issues
–
show
The return type of
return false; (false ) is incompatible with the return type documented by Jose\Util\RSA::_emsa_pss_verify of type string .
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design. Let’s take a look at an example: class Author {
private $name;
public function __construct($name) {
$this->name = $name;
}
public function getName() {
return $this->name;
}
}
abstract class Post {
public function getAuthor() {
return 'Johannes';
}
}
class BlogPost extends Post {
public function getAuthor() {
return new Author('Johannes');
}
}
class ForumPost extends Post { /* ... */ }
function my_function(Post $post) {
echo strtoupper($post->getAuthor());
}
Our function ![]() |
|||
293 | } |
||
294 | |||
295 | $maskedDB = substr($em, 0, -$hash->getLength() - 1); |
||
296 | $h = substr($em, -$hash->getLength() - 1, $hash->getLength()); |
||
297 | $temp = chr(0xFF << ($emBits & 7)); |
||
298 | if ((~$maskedDB[0] & $temp) != $temp) { |
||
299 | return false; |
||
0 ignored issues
–
show
The return type of
return false; (false ) is incompatible with the return type documented by Jose\Util\RSA::_emsa_pss_verify of type string .
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design. Let’s take a look at an example: class Author {
private $name;
public function __construct($name) {
$this->name = $name;
}
public function getName() {
return $this->name;
}
}
abstract class Post {
public function getAuthor() {
return 'Johannes';
}
}
class BlogPost extends Post {
public function getAuthor() {
return new Author('Johannes');
}
}
class ForumPost extends Post { /* ... */ }
function my_function(Post $post) {
echo strtoupper($post->getAuthor());
}
Our function ![]() |
|||
300 | } |
||
301 | $dbMask = self::_mgf1($h, $emLen - $hash->getLength() - 1, $hash/*MGF*/); |
||
302 | $db = $maskedDB ^ $dbMask; |
||
303 | $db[0] = ~chr(0xFF << ($emBits & 7)) & $db[0]; |
||
304 | $temp = $emLen - $hash->getLength() - $sLen - 2; |
||
305 | if (substr($db, 0, $temp) != str_repeat(chr(0), $temp) || ord($db[$temp]) != 1) { |
||
306 | return false; |
||
0 ignored issues
–
show
The return type of
return false; (false ) is incompatible with the return type documented by Jose\Util\RSA::_emsa_pss_verify of type string .
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design. Let’s take a look at an example: class Author {
private $name;
public function __construct($name) {
$this->name = $name;
}
public function getName() {
return $this->name;
}
}
abstract class Post {
public function getAuthor() {
return 'Johannes';
}
}
class BlogPost extends Post {
public function getAuthor() {
return new Author('Johannes');
}
}
class ForumPost extends Post { /* ... */ }
function my_function(Post $post) {
echo strtoupper($post->getAuthor());
}
Our function ![]() |
|||
307 | } |
||
308 | $salt = substr($db, $temp + 1); // should be $sLen long |
||
309 | $m2 = "\0\0\0\0\0\0\0\0".$mHash.$salt; |
||
310 | $h2 = $hash->hash($m2); |
||
311 | |||
312 | return hash_equals($h, $h2); |
||
313 | } |
||
314 | |||
315 | /** |
||
316 | * Encryption. |
||
317 | * |
||
318 | * @param \Jose\KeyConverter\RSAKey $key |
||
319 | * @param string $plaintext |
||
320 | * @param string $hash_algorithm |
||
321 | * |
||
322 | * @return string |
||
323 | */ |
||
324 | public static function encrypt(RSAKey $key, $plaintext, $hash_algorithm) |
||
325 | { |
||
326 | $hash = Hash::$hash_algorithm(); |
||
327 | $length = $key->getModulusLength() - 2 * $hash->getLength() - 2; |
||
328 | |||
329 | Assertion::greaterThan($length, 0); |
||
330 | |||
331 | $plaintext = str_split($plaintext, $length); |
||
332 | $ciphertext = ''; |
||
333 | foreach ($plaintext as $m) { |
||
334 | $ciphertext .= self::_rsaes_oaep_encrypt($key, $m, $hash); |
||
335 | } |
||
336 | |||
337 | return $ciphertext; |
||
338 | } |
||
339 | |||
340 | /** |
||
341 | * Decryption. |
||
342 | * |
||
343 | * @param \Jose\KeyConverter\RSAKey $key |
||
344 | * @param string $ciphertext |
||
345 | * @param string $hash_algorithm |
||
346 | * |
||
347 | * @return string |
||
348 | */ |
||
349 | public static function decrypt(RSAKey $key, $ciphertext, $hash_algorithm) |
||
350 | { |
||
351 | Assertion::greaterThan($key->getModulusLength(), 0); |
||
352 | |||
353 | $hash = Hash::$hash_algorithm(); |
||
354 | |||
355 | $ciphertext = str_split($ciphertext, $key->getModulusLength()); |
||
356 | $ciphertext[count($ciphertext) - 1] = str_pad($ciphertext[count($ciphertext) - 1], $key->getModulusLength(), chr(0), STR_PAD_LEFT); |
||
357 | |||
358 | $plaintext = ''; |
||
359 | |||
360 | foreach ($ciphertext as $c) { |
||
361 | $temp = self::_rsaes_oaep_decrypt($key, $c, $hash); |
||
362 | if ($temp === false) { |
||
363 | return false; |
||
364 | } |
||
365 | $plaintext .= $temp; |
||
366 | } |
||
367 | |||
368 | return $plaintext; |
||
369 | } |
||
370 | |||
371 | /** |
||
372 | * Create a signature. |
||
373 | * |
||
374 | * @param \Jose\KeyConverter\RSAKey $key |
||
375 | * @param string $message |
||
376 | * @param string $hash |
||
377 | * |
||
378 | * @return string |
||
379 | */ |
||
380 | public static function sign(RSAKey $key, $message, $hash) |
||
381 | { |
||
382 | Assertion::string($message); |
||
383 | Assertion::string($hash); |
||
384 | Assertion::inArray($hash, ['sha256', 'sha384', 'sha512']); |
||
385 | |||
386 | $em = self::_emsa_pss_encode($message, 8 * $key->getModulusLength() - 1, Hash::$hash()); |
||
387 | |||
388 | Assertion::string($em); |
||
389 | |||
390 | $message = self::convertOctetStringToInteger($em); |
||
391 | $signature = self::_rsasp1($key, $message); |
||
392 | |||
393 | Assertion::isInstanceOf($signature, BigInteger::class); |
||
394 | |||
395 | $signature = self::convertIntegerToOctetString($signature, $key->getModulusLength()); |
||
396 | |||
397 | return $signature; |
||
398 | } |
||
399 | |||
400 | /** |
||
401 | * Verifies a signature. |
||
402 | * |
||
403 | * @param \Jose\KeyConverter\RSAKey $key |
||
404 | * @param string $message |
||
405 | * @param string $signature |
||
406 | * @param string $hash |
||
407 | * |
||
408 | * @return bool |
||
409 | */ |
||
410 | public static function verify(RSAKey $key, $message, $signature, $hash) |
||
411 | { |
||
412 | Assertion::string($message); |
||
413 | Assertion::string($signature); |
||
414 | Assertion::string($hash); |
||
415 | Assertion::inArray($hash, ['sha256', 'sha384', 'sha512']); |
||
416 | Assertion::eq(strlen($signature), $key->getModulusLength()); |
||
417 | |||
418 | $modBits = 8 * $key->getModulusLength(); |
||
419 | |||
420 | $s2 = self::convertOctetStringToInteger($signature); |
||
421 | $m2 = self::_rsavp1($key, $s2); |
||
422 | |||
423 | Assertion::isInstanceOf($m2, BigInteger::class); |
||
424 | |||
425 | $em = self::convertIntegerToOctetString($m2, $modBits >> 3); |
||
426 | |||
427 | return self::_emsa_pss_verify($message, $em, $modBits - 1, Hash::$hash()); |
||
428 | } |
||
429 | } |
||
430 |