Rev 846 | Rev 1042 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
827 | daniel-mar | 1 | <?php |
2 | |||
3 | /** |
||
4 | * RSA Private Key |
||
5 | * |
||
874 | daniel-mar | 6 | * @category Crypt |
7 | * @package RSA |
||
827 | daniel-mar | 8 | * @author Jim Wigginton <terrafrost@php.net> |
9 | * @copyright 2015 Jim Wigginton |
||
10 | * @license http://www.opensource.org/licenses/mit-license.html MIT License |
||
11 | * @link http://phpseclib.sourceforge.net |
||
12 | */ |
||
13 | |||
14 | namespace phpseclib3\Crypt\RSA; |
||
15 | |||
16 | use phpseclib3\Crypt\Common; |
||
17 | use phpseclib3\Crypt\Random; |
||
18 | use phpseclib3\Crypt\RSA; |
||
19 | use phpseclib3\Crypt\RSA\Formats\Keys\PSS; |
||
20 | use phpseclib3\Exception\UnsupportedFormatException; |
||
21 | use phpseclib3\Math\BigInteger; |
||
22 | |||
23 | /** |
||
24 | * Raw RSA Key Handler |
||
25 | * |
||
874 | daniel-mar | 26 | * @package RSA |
827 | daniel-mar | 27 | * @author Jim Wigginton <terrafrost@php.net> |
874 | daniel-mar | 28 | * @access public |
827 | daniel-mar | 29 | */ |
30 | class PrivateKey extends RSA implements Common\PrivateKey |
||
31 | { |
||
32 | use Common\Traits\PasswordProtected; |
||
33 | |||
34 | /** |
||
35 | * Primes for Chinese Remainder Theorem (ie. p and q) |
||
36 | * |
||
37 | * @var array |
||
874 | daniel-mar | 38 | * @access private |
827 | daniel-mar | 39 | */ |
40 | protected $primes; |
||
41 | |||
42 | /** |
||
43 | * Exponents for Chinese Remainder Theorem (ie. dP and dQ) |
||
44 | * |
||
45 | * @var array |
||
874 | daniel-mar | 46 | * @access private |
827 | daniel-mar | 47 | */ |
48 | protected $exponents; |
||
49 | |||
50 | /** |
||
51 | * Coefficients for Chinese Remainder Theorem (ie. qInv) |
||
52 | * |
||
53 | * @var array |
||
874 | daniel-mar | 54 | * @access private |
827 | daniel-mar | 55 | */ |
56 | protected $coefficients; |
||
57 | |||
58 | /** |
||
59 | * Public Exponent |
||
60 | * |
||
61 | * @var mixed |
||
874 | daniel-mar | 62 | * @access private |
827 | daniel-mar | 63 | */ |
64 | protected $publicExponent = false; |
||
65 | |||
66 | /** |
||
67 | * RSADP |
||
68 | * |
||
69 | * See {@link http://tools.ietf.org/html/rfc3447#section-5.1.2 RFC3447#section-5.1.2}. |
||
70 | * |
||
874 | daniel-mar | 71 | * @access private |
827 | daniel-mar | 72 | * @param \phpseclib3\Math\BigInteger $c |
73 | * @return bool|\phpseclib3\Math\BigInteger |
||
74 | */ |
||
75 | private function rsadp($c) |
||
76 | { |
||
77 | if ($c->compare(self::$zero) < 0 || $c->compare($this->modulus) > 0) { |
||
78 | throw new \OutOfRangeException('Ciphertext representative out of range'); |
||
79 | } |
||
80 | return $this->exponentiate($c); |
||
81 | } |
||
82 | |||
83 | /** |
||
84 | * RSASP1 |
||
85 | * |
||
86 | * See {@link http://tools.ietf.org/html/rfc3447#section-5.2.1 RFC3447#section-5.2.1}. |
||
87 | * |
||
874 | daniel-mar | 88 | * @access private |
827 | daniel-mar | 89 | * @param \phpseclib3\Math\BigInteger $m |
90 | * @return bool|\phpseclib3\Math\BigInteger |
||
91 | */ |
||
92 | private function rsasp1($m) |
||
93 | { |
||
94 | if ($m->compare(self::$zero) < 0 || $m->compare($this->modulus) > 0) { |
||
95 | throw new \OutOfRangeException('Signature representative out of range'); |
||
96 | } |
||
97 | return $this->exponentiate($m); |
||
98 | } |
||
99 | |||
100 | /** |
||
101 | * Exponentiate |
||
102 | * |
||
103 | * @param \phpseclib3\Math\BigInteger $x |
||
104 | * @return \phpseclib3\Math\BigInteger |
||
105 | */ |
||
106 | protected function exponentiate(BigInteger $x) |
||
107 | { |
||
108 | switch (true) { |
||
109 | case empty($this->primes): |
||
110 | case $this->primes[1]->equals(self::$zero): |
||
111 | case empty($this->coefficients): |
||
112 | case $this->coefficients[2]->equals(self::$zero): |
||
113 | case empty($this->exponents): |
||
114 | case $this->exponents[1]->equals(self::$zero): |
||
115 | return $x->modPow($this->exponent, $this->modulus); |
||
116 | } |
||
117 | |||
118 | $num_primes = count($this->primes); |
||
119 | |||
120 | if (!static::$enableBlinding) { |
||
121 | $m_i = [ |
||
122 | 1 => $x->modPow($this->exponents[1], $this->primes[1]), |
||
123 | 2 => $x->modPow($this->exponents[2], $this->primes[2]) |
||
124 | ]; |
||
125 | $h = $m_i[1]->subtract($m_i[2]); |
||
126 | $h = $h->multiply($this->coefficients[2]); |
||
127 | list(, $h) = $h->divide($this->primes[1]); |
||
128 | $m = $m_i[2]->add($h->multiply($this->primes[2])); |
||
129 | |||
130 | $r = $this->primes[1]; |
||
131 | for ($i = 3; $i <= $num_primes; $i++) { |
||
132 | $m_i = $x->modPow($this->exponents[$i], $this->primes[$i]); |
||
133 | |||
134 | $r = $r->multiply($this->primes[$i - 1]); |
||
135 | |||
136 | $h = $m_i->subtract($m); |
||
137 | $h = $h->multiply($this->coefficients[$i]); |
||
138 | list(, $h) = $h->divide($this->primes[$i]); |
||
139 | |||
140 | $m = $m->add($r->multiply($h)); |
||
141 | } |
||
142 | } else { |
||
143 | $smallest = $this->primes[1]; |
||
144 | for ($i = 2; $i <= $num_primes; $i++) { |
||
145 | if ($smallest->compare($this->primes[$i]) > 0) { |
||
146 | $smallest = $this->primes[$i]; |
||
147 | } |
||
148 | } |
||
149 | |||
150 | $r = BigInteger::randomRange(self::$one, $smallest->subtract(self::$one)); |
||
151 | |||
152 | $m_i = [ |
||
153 | 1 => $this->blind($x, $r, 1), |
||
154 | 2 => $this->blind($x, $r, 2) |
||
155 | ]; |
||
156 | $h = $m_i[1]->subtract($m_i[2]); |
||
157 | $h = $h->multiply($this->coefficients[2]); |
||
158 | list(, $h) = $h->divide($this->primes[1]); |
||
159 | $m = $m_i[2]->add($h->multiply($this->primes[2])); |
||
160 | |||
161 | $r = $this->primes[1]; |
||
162 | for ($i = 3; $i <= $num_primes; $i++) { |
||
163 | $m_i = $this->blind($x, $r, $i); |
||
164 | |||
165 | $r = $r->multiply($this->primes[$i - 1]); |
||
166 | |||
167 | $h = $m_i->subtract($m); |
||
168 | $h = $h->multiply($this->coefficients[$i]); |
||
169 | list(, $h) = $h->divide($this->primes[$i]); |
||
170 | |||
171 | $m = $m->add($r->multiply($h)); |
||
172 | } |
||
173 | } |
||
174 | |||
175 | return $m; |
||
176 | } |
||
177 | |||
178 | /** |
||
179 | * Performs RSA Blinding |
||
180 | * |
||
181 | * Protects against timing attacks by employing RSA Blinding. |
||
182 | * Returns $x->modPow($this->exponents[$i], $this->primes[$i]) |
||
183 | * |
||
874 | daniel-mar | 184 | * @access private |
827 | daniel-mar | 185 | * @param \phpseclib3\Math\BigInteger $x |
186 | * @param \phpseclib3\Math\BigInteger $r |
||
187 | * @param int $i |
||
188 | * @return \phpseclib3\Math\BigInteger |
||
189 | */ |
||
190 | private function blind($x, $r, $i) |
||
191 | { |
||
192 | $x = $x->multiply($r->modPow($this->publicExponent, $this->primes[$i])); |
||
193 | $x = $x->modPow($this->exponents[$i], $this->primes[$i]); |
||
194 | |||
195 | $r = $r->modInverse($this->primes[$i]); |
||
196 | $x = $x->multiply($r); |
||
197 | list(, $x) = $x->divide($this->primes[$i]); |
||
198 | |||
199 | return $x; |
||
200 | } |
||
201 | |||
202 | /** |
||
203 | * EMSA-PSS-ENCODE |
||
204 | * |
||
205 | * See {@link http://tools.ietf.org/html/rfc3447#section-9.1.1 RFC3447#section-9.1.1}. |
||
206 | * |
||
207 | * @return string |
||
874 | daniel-mar | 208 | * @access private |
827 | daniel-mar | 209 | * @param string $m |
210 | * @throws \RuntimeException on encoding error |
||
211 | * @param int $emBits |
||
212 | */ |
||
213 | private function emsa_pss_encode($m, $emBits) |
||
214 | { |
||
215 | // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error |
||
216 | // be output. |
||
217 | |||
218 | $emLen = ($emBits + 1) >> 3; // ie. ceil($emBits / 8) |
||
219 | $sLen = $this->sLen !== null ? $this->sLen : $this->hLen; |
||
220 | |||
221 | $mHash = $this->hash->hash($m); |
||
222 | if ($emLen < $this->hLen + $sLen + 2) { |
||
223 | throw new \LengthException('RSA modulus too short'); |
||
224 | } |
||
225 | |||
226 | $salt = Random::string($sLen); |
||
227 | $m2 = "\0\0\0\0\0\0\0\0" . $mHash . $salt; |
||
228 | $h = $this->hash->hash($m2); |
||
229 | $ps = str_repeat(chr(0), $emLen - $sLen - $this->hLen - 2); |
||
230 | $db = $ps . chr(1) . $salt; |
||
231 | $dbMask = $this->mgf1($h, $emLen - $this->hLen - 1); // ie. stlren($db) |
||
232 | $maskedDB = $db ^ $dbMask; |
||
233 | $maskedDB[0] = ~chr(0xFF << ($emBits & 7)) & $maskedDB[0]; |
||
234 | $em = $maskedDB . $h . chr(0xBC); |
||
235 | |||
236 | return $em; |
||
237 | } |
||
238 | |||
239 | /** |
||
240 | * RSASSA-PSS-SIGN |
||
241 | * |
||
242 | * See {@link http://tools.ietf.org/html/rfc3447#section-8.1.1 RFC3447#section-8.1.1}. |
||
243 | * |
||
874 | daniel-mar | 244 | * @access private |
827 | daniel-mar | 245 | * @param string $m |
246 | * @return bool|string |
||
247 | */ |
||
248 | private function rsassa_pss_sign($m) |
||
249 | { |
||
250 | // EMSA-PSS encoding |
||
251 | |||
252 | $em = $this->emsa_pss_encode($m, 8 * $this->k - 1); |
||
253 | |||
254 | // RSA signature |
||
255 | |||
256 | $m = $this->os2ip($em); |
||
257 | $s = $this->rsasp1($m); |
||
258 | $s = $this->i2osp($s, $this->k); |
||
259 | |||
260 | // Output the signature S |
||
261 | |||
262 | return $s; |
||
263 | } |
||
264 | |||
265 | /** |
||
266 | * RSASSA-PKCS1-V1_5-SIGN |
||
267 | * |
||
268 | * See {@link http://tools.ietf.org/html/rfc3447#section-8.2.1 RFC3447#section-8.2.1}. |
||
269 | * |
||
874 | daniel-mar | 270 | * @access private |
827 | daniel-mar | 271 | * @param string $m |
272 | * @throws \LengthException if the RSA modulus is too short |
||
273 | * @return bool|string |
||
274 | */ |
||
275 | private function rsassa_pkcs1_v1_5_sign($m) |
||
276 | { |
||
277 | // EMSA-PKCS1-v1_5 encoding |
||
278 | |||
279 | // If the encoding operation outputs "intended encoded message length too short," output "RSA modulus |
||
280 | // too short" and stop. |
||
281 | try { |
||
282 | $em = $this->emsa_pkcs1_v1_5_encode($m, $this->k); |
||
283 | } catch (\LengthException $e) { |
||
284 | throw new \LengthException('RSA modulus too short'); |
||
285 | } |
||
286 | |||
287 | // RSA signature |
||
288 | |||
289 | $m = $this->os2ip($em); |
||
290 | $s = $this->rsasp1($m); |
||
291 | $s = $this->i2osp($s, $this->k); |
||
292 | |||
293 | // Output the signature S |
||
294 | |||
295 | return $s; |
||
296 | } |
||
297 | |||
298 | /** |
||
299 | * Create a signature |
||
300 | * |
||
301 | * @see self::verify() |
||
874 | daniel-mar | 302 | * @access public |
827 | daniel-mar | 303 | * @param string $message |
304 | * @return string |
||
305 | */ |
||
306 | public function sign($message) |
||
307 | { |
||
308 | switch ($this->signaturePadding) { |
||
309 | case self::SIGNATURE_PKCS1: |
||
310 | case self::SIGNATURE_RELAXED_PKCS1: |
||
311 | return $this->rsassa_pkcs1_v1_5_sign($message); |
||
312 | //case self::SIGNATURE_PSS: |
||
313 | default: |
||
314 | return $this->rsassa_pss_sign($message); |
||
315 | } |
||
316 | } |
||
317 | |||
318 | /** |
||
319 | * RSAES-PKCS1-V1_5-DECRYPT |
||
320 | * |
||
321 | * See {@link http://tools.ietf.org/html/rfc3447#section-7.2.2 RFC3447#section-7.2.2}. |
||
322 | * |
||
874 | daniel-mar | 323 | * @access private |
827 | daniel-mar | 324 | * @param string $c |
325 | * @return bool|string |
||
326 | */ |
||
327 | private function rsaes_pkcs1_v1_5_decrypt($c) |
||
328 | { |
||
329 | // Length checking |
||
330 | |||
331 | if (strlen($c) != $this->k) { // or if k < 11 |
||
332 | throw new \LengthException('Ciphertext representative too long'); |
||
333 | } |
||
334 | |||
335 | // RSA decryption |
||
336 | |||
337 | $c = $this->os2ip($c); |
||
338 | $m = $this->rsadp($c); |
||
339 | $em = $this->i2osp($m, $this->k); |
||
340 | |||
341 | // EME-PKCS1-v1_5 decoding |
||
342 | |||
343 | if (ord($em[0]) != 0 || ord($em[1]) > 2) { |
||
344 | throw new \RuntimeException('Decryption error'); |
||
345 | } |
||
346 | |||
347 | $ps = substr($em, 2, strpos($em, chr(0), 2) - 2); |
||
348 | $m = substr($em, strlen($ps) + 3); |
||
349 | |||
350 | if (strlen($ps) < 8) { |
||
351 | throw new \RuntimeException('Decryption error'); |
||
352 | } |
||
353 | |||
354 | // Output M |
||
355 | |||
356 | return $m; |
||
357 | } |
||
358 | |||
359 | /** |
||
360 | * RSAES-OAEP-DECRYPT |
||
361 | * |
||
362 | * See {@link http://tools.ietf.org/html/rfc3447#section-7.1.2 RFC3447#section-7.1.2}. The fact that the error |
||
363 | * messages aren't distinguishable from one another hinders debugging, but, to quote from RFC3447#section-7.1.2: |
||
364 | * |
||
365 | * Note. Care must be taken to ensure that an opponent cannot |
||
366 | * distinguish the different error conditions in Step 3.g, whether by |
||
367 | * error message or timing, or, more generally, learn partial |
||
368 | * information about the encoded message EM. Otherwise an opponent may |
||
369 | * be able to obtain useful information about the decryption of the |
||
370 | * ciphertext C, leading to a chosen-ciphertext attack such as the one |
||
371 | * observed by Manger [36]. |
||
372 | * |
||
874 | daniel-mar | 373 | * @access private |
827 | daniel-mar | 374 | * @param string $c |
375 | * @return bool|string |
||
376 | */ |
||
377 | private function rsaes_oaep_decrypt($c) |
||
378 | { |
||
379 | // Length checking |
||
380 | |||
381 | // if $l is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error |
||
382 | // be output. |
||
383 | |||
384 | if (strlen($c) != $this->k || $this->k < 2 * $this->hLen + 2) { |
||
385 | throw new \LengthException('Ciphertext representative too long'); |
||
386 | } |
||
387 | |||
388 | // RSA decryption |
||
389 | |||
390 | $c = $this->os2ip($c); |
||
391 | $m = $this->rsadp($c); |
||
392 | $em = $this->i2osp($m, $this->k); |
||
393 | |||
394 | // EME-OAEP decoding |
||
395 | |||
396 | $lHash = $this->hash->hash($this->label); |
||
397 | $y = ord($em[0]); |
||
398 | $maskedSeed = substr($em, 1, $this->hLen); |
||
399 | $maskedDB = substr($em, $this->hLen + 1); |
||
400 | $seedMask = $this->mgf1($maskedDB, $this->hLen); |
||
401 | $seed = $maskedSeed ^ $seedMask; |
||
402 | $dbMask = $this->mgf1($seed, $this->k - $this->hLen - 1); |
||
403 | $db = $maskedDB ^ $dbMask; |
||
404 | $lHash2 = substr($db, 0, $this->hLen); |
||
405 | $m = substr($db, $this->hLen); |
||
406 | $hashesMatch = hash_equals($lHash, $lHash2); |
||
407 | $leadingZeros = 1; |
||
408 | $patternMatch = 0; |
||
409 | $offset = 0; |
||
410 | for ($i = 0; $i < strlen($m); $i++) { |
||
411 | $patternMatch |= $leadingZeros & ($m[$i] === "\1"); |
||
412 | $leadingZeros &= $m[$i] === "\0"; |
||
413 | $offset += $patternMatch ? 0 : 1; |
||
414 | } |
||
415 | |||
416 | // we do | instead of || to avoid https://en.wikipedia.org/wiki/Short-circuit_evaluation |
||
417 | // to protect against timing attacks |
||
418 | if (!$hashesMatch | !$patternMatch) { |
||
419 | throw new \RuntimeException('Decryption error'); |
||
420 | } |
||
421 | |||
422 | // Output the message M |
||
423 | |||
424 | return substr($m, $offset + 1); |
||
425 | } |
||
426 | |||
427 | /** |
||
428 | * Raw Encryption / Decryption |
||
429 | * |
||
430 | * Doesn't use padding and is not recommended. |
||
431 | * |
||
874 | daniel-mar | 432 | * @access private |
827 | daniel-mar | 433 | * @param string $m |
434 | * @return bool|string |
||
435 | * @throws \LengthException if strlen($m) > $this->k |
||
436 | */ |
||
437 | private function raw_encrypt($m) |
||
438 | { |
||
439 | if (strlen($m) > $this->k) { |
||
440 | throw new \LengthException('Ciphertext representative too long'); |
||
441 | } |
||
442 | |||
443 | $temp = $this->os2ip($m); |
||
444 | $temp = $this->rsadp($temp); |
||
445 | return $this->i2osp($temp, $this->k); |
||
446 | } |
||
447 | |||
448 | /** |
||
449 | * Decryption |
||
450 | * |
||
451 | * @see self::encrypt() |
||
874 | daniel-mar | 452 | * @access public |
827 | daniel-mar | 453 | * @param string $ciphertext |
454 | * @return bool|string |
||
455 | */ |
||
456 | public function decrypt($ciphertext) |
||
457 | { |
||
458 | switch ($this->encryptionPadding) { |
||
459 | case self::ENCRYPTION_NONE: |
||
460 | return $this->raw_encrypt($ciphertext); |
||
461 | case self::ENCRYPTION_PKCS1: |
||
462 | return $this->rsaes_pkcs1_v1_5_decrypt($ciphertext); |
||
463 | //case self::ENCRYPTION_OAEP: |
||
464 | default: |
||
465 | return $this->rsaes_oaep_decrypt($ciphertext); |
||
466 | } |
||
467 | } |
||
468 | |||
469 | /** |
||
470 | * Returns the public key |
||
471 | * |
||
874 | daniel-mar | 472 | * @access public |
827 | daniel-mar | 473 | * @return mixed |
474 | */ |
||
475 | public function getPublicKey() |
||
476 | { |
||
477 | $type = self::validatePlugin('Keys', 'PKCS8', 'savePublicKey'); |
||
478 | if (empty($this->modulus) || empty($this->publicExponent)) { |
||
479 | throw new \RuntimeException('Public key components not found'); |
||
480 | } |
||
481 | |||
482 | $key = $type::savePublicKey($this->modulus, $this->publicExponent); |
||
483 | return RSA::loadFormat('PKCS8', $key) |
||
484 | ->withHash($this->hash->getHash()) |
||
485 | ->withMGFHash($this->mgfHash->getHash()) |
||
486 | ->withSaltLength($this->sLen) |
||
487 | ->withLabel($this->label) |
||
488 | ->withPadding($this->signaturePadding | $this->encryptionPadding); |
||
489 | } |
||
490 | |||
491 | /** |
||
492 | * Returns the private key |
||
493 | * |
||
494 | * @param string $type |
||
495 | * @param array $options optional |
||
496 | * @return string |
||
497 | */ |
||
498 | public function toString($type, array $options = []) |
||
499 | { |
||
500 | $type = self::validatePlugin( |
||
501 | 'Keys', |
||
502 | $type, |
||
503 | empty($this->primes) ? 'savePublicKey' : 'savePrivateKey' |
||
504 | ); |
||
505 | |||
506 | if ($type == PSS::class) { |
||
507 | if ($this->signaturePadding == self::SIGNATURE_PSS) { |
||
508 | $options += [ |
||
509 | 'hash' => $this->hash->getHash(), |
||
510 | 'MGFHash' => $this->mgfHash->getHash(), |
||
511 | 'saltLength' => $this->getSaltLength() |
||
512 | ]; |
||
513 | } else { |
||
514 | throw new UnsupportedFormatException('The PSS format can only be used when the signature method has been explicitly set to PSS'); |
||
515 | } |
||
516 | } |
||
517 | |||
518 | if (empty($this->primes)) { |
||
519 | return $type::savePublicKey($this->modulus, $this->exponent, $options); |
||
520 | } |
||
521 | |||
522 | return $type::savePrivateKey($this->modulus, $this->publicExponent, $this->exponent, $this->primes, $this->exponents, $this->coefficients, $this->password, $options); |
||
523 | |||
524 | /* |
||
525 | $key = $type::savePrivateKey($this->modulus, $this->publicExponent, $this->exponent, $this->primes, $this->exponents, $this->coefficients, $this->password, $options); |
||
526 | if ($key !== false || count($this->primes) == 2) { |
||
527 | return $key; |
||
528 | } |
||
529 | |||
530 | $nSize = $this->getSize() >> 1; |
||
531 | |||
532 | $primes = [1 => clone self::$one, clone self::$one]; |
||
533 | $i = 1; |
||
534 | foreach ($this->primes as $prime) { |
||
535 | $primes[$i] = $primes[$i]->multiply($prime); |
||
536 | if ($primes[$i]->getLength() >= $nSize) { |
||
537 | $i++; |
||
538 | } |
||
539 | } |
||
540 | |||
541 | $exponents = []; |
||
542 | $coefficients = [2 => $primes[2]->modInverse($primes[1])]; |
||
543 | |||
544 | foreach ($primes as $i => $prime) { |
||
545 | $temp = $prime->subtract(self::$one); |
||
546 | $exponents[$i] = $this->modulus->modInverse($temp); |
||
547 | } |
||
548 | |||
549 | return $type::savePrivateKey($this->modulus, $this->publicExponent, $this->exponent, $primes, $exponents, $coefficients, $this->password, $options); |
||
550 | */ |
||
551 | } |
||
552 | } |