[ Index ] |
PHP Cross Reference of DokuWiki |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * RSA Public Key 5 * 6 * @author Jim Wigginton <terrafrost@php.net> 7 * @copyright 2015 Jim Wigginton 8 * @license http://www.opensource.org/licenses/mit-license.html MIT License 9 * @link http://phpseclib.sourceforge.net 10 */ 11 12 namespace phpseclib3\Crypt\RSA; 13 14 use phpseclib3\Common\Functions\Strings; 15 use phpseclib3\Crypt\Common; 16 use phpseclib3\Crypt\Hash; 17 use phpseclib3\Crypt\Random; 18 use phpseclib3\Crypt\RSA; 19 use phpseclib3\Crypt\RSA\Formats\Keys\PSS; 20 use phpseclib3\Exception\UnsupportedAlgorithmException; 21 use phpseclib3\Exception\UnsupportedFormatException; 22 use phpseclib3\File\ASN1; 23 use phpseclib3\File\ASN1\Maps\DigestInfo; 24 use phpseclib3\Math\BigInteger; 25 26 /** 27 * Raw RSA Key Handler 28 * 29 * @author Jim Wigginton <terrafrost@php.net> 30 */ 31 final class PublicKey extends RSA implements Common\PublicKey 32 { 33 use Common\Traits\Fingerprint; 34 35 /** 36 * Exponentiate 37 * 38 * @param \phpseclib3\Math\BigInteger $x 39 * @return \phpseclib3\Math\BigInteger 40 */ 41 private function exponentiate(BigInteger $x) 42 { 43 return $x->modPow($this->exponent, $this->modulus); 44 } 45 46 /** 47 * RSAVP1 48 * 49 * See {@link http://tools.ietf.org/html/rfc3447#section-5.2.2 RFC3447#section-5.2.2}. 50 * 51 * @param \phpseclib3\Math\BigInteger $s 52 * @return bool|\phpseclib3\Math\BigInteger 53 */ 54 private function rsavp1($s) 55 { 56 if ($s->compare(self::$zero) < 0 || $s->compare($this->modulus) > 0) { 57 return false; 58 } 59 return $this->exponentiate($s); 60 } 61 62 /** 63 * RSASSA-PKCS1-V1_5-VERIFY 64 * 65 * See {@link http://tools.ietf.org/html/rfc3447#section-8.2.2 RFC3447#section-8.2.2}. 66 * 67 * @param string $m 68 * @param string $s 69 * @throws \LengthException if the RSA modulus is too short 70 * @return bool 71 */ 72 private function rsassa_pkcs1_v1_5_verify($m, $s) 73 { 74 // Length checking 75 76 if (strlen($s) != $this->k) { 77 return false; 78 } 79 80 // RSA verification 81 82 $s = $this->os2ip($s); 83 $m2 = $this->rsavp1($s); 84 if ($m2 === false) { 85 return false; 86 } 87 $em = $this->i2osp($m2, $this->k); 88 if ($em === false) { 89 return false; 90 } 91 92 // EMSA-PKCS1-v1_5 encoding 93 94 $exception = false; 95 96 // If the encoding operation outputs "intended encoded message length too short," output "RSA modulus 97 // too short" and stop. 98 try { 99 $em2 = $this->emsa_pkcs1_v1_5_encode($m, $this->k); 100 $r1 = hash_equals($em, $em2); 101 } catch (\LengthException $e) { 102 $exception = true; 103 } 104 105 try { 106 $em3 = $this->emsa_pkcs1_v1_5_encode_without_null($m, $this->k); 107 $r2 = hash_equals($em, $em3); 108 } catch (\LengthException $e) { 109 $exception = true; 110 } catch (UnsupportedAlgorithmException $e) { 111 $r2 = false; 112 } 113 114 if ($exception) { 115 throw new \LengthException('RSA modulus too short'); 116 } 117 118 // Compare 119 return $r1 || $r2; 120 } 121 122 /** 123 * RSASSA-PKCS1-V1_5-VERIFY (relaxed matching) 124 * 125 * Per {@link http://tools.ietf.org/html/rfc3447#page-43 RFC3447#page-43} PKCS1 v1.5 126 * specified the use BER encoding rather than DER encoding that PKCS1 v2.0 specified. 127 * This means that under rare conditions you can have a perfectly valid v1.5 signature 128 * that fails to validate with _rsassa_pkcs1_v1_5_verify(). PKCS1 v2.1 also recommends 129 * that if you're going to validate these types of signatures you "should indicate 130 * whether the underlying BER encoding is a DER encoding and hence whether the signature 131 * is valid with respect to the specification given in [PKCS1 v2.0+]". so if you do 132 * $rsa->getLastPadding() and get RSA::PADDING_RELAXED_PKCS1 back instead of 133 * RSA::PADDING_PKCS1... that means BER encoding was used. 134 * 135 * @param string $m 136 * @param string $s 137 * @return bool 138 */ 139 private function rsassa_pkcs1_v1_5_relaxed_verify($m, $s) 140 { 141 // Length checking 142 143 if (strlen($s) != $this->k) { 144 return false; 145 } 146 147 // RSA verification 148 149 $s = $this->os2ip($s); 150 $m2 = $this->rsavp1($s); 151 if ($m2 === false) { 152 return false; 153 } 154 $em = $this->i2osp($m2, $this->k); 155 if ($em === false) { 156 return false; 157 } 158 159 if (Strings::shift($em, 2) != "\0\1") { 160 return false; 161 } 162 163 $em = ltrim($em, "\xFF"); 164 if (Strings::shift($em) != "\0") { 165 return false; 166 } 167 168 $decoded = ASN1::decodeBER($em); 169 if (!is_array($decoded) || empty($decoded[0]) || strlen($em) > $decoded[0]['length']) { 170 return false; 171 } 172 173 static $oids; 174 if (!isset($oids)) { 175 $oids = [ 176 'md2' => '1.2.840.113549.2.2', 177 'md4' => '1.2.840.113549.2.4', // from PKCS1 v1.5 178 'md5' => '1.2.840.113549.2.5', 179 'id-sha1' => '1.3.14.3.2.26', 180 'id-sha256' => '2.16.840.1.101.3.4.2.1', 181 'id-sha384' => '2.16.840.1.101.3.4.2.2', 182 'id-sha512' => '2.16.840.1.101.3.4.2.3', 183 // from PKCS1 v2.2 184 'id-sha224' => '2.16.840.1.101.3.4.2.4', 185 'id-sha512/224' => '2.16.840.1.101.3.4.2.5', 186 'id-sha512/256' => '2.16.840.1.101.3.4.2.6', 187 ]; 188 ASN1::loadOIDs($oids); 189 } 190 191 $decoded = ASN1::asn1map($decoded[0], DigestInfo::MAP); 192 if (!isset($decoded) || $decoded === false) { 193 return false; 194 } 195 196 if (!isset($oids[$decoded['digestAlgorithm']['algorithm']])) { 197 return false; 198 } 199 200 if (isset($decoded['digestAlgorithm']['parameters']) && $decoded['digestAlgorithm']['parameters'] !== ['null' => '']) { 201 return false; 202 } 203 204 $hash = $decoded['digestAlgorithm']['algorithm']; 205 $hash = substr($hash, 0, 3) == 'id-' ? 206 substr($hash, 3) : 207 $hash; 208 $hash = new Hash($hash); 209 $em = $hash->hash($m); 210 $em2 = $decoded['digest']; 211 212 return hash_equals($em, $em2); 213 } 214 215 /** 216 * EMSA-PSS-VERIFY 217 * 218 * See {@link http://tools.ietf.org/html/rfc3447#section-9.1.2 RFC3447#section-9.1.2}. 219 * 220 * @param string $m 221 * @param string $em 222 * @param int $emBits 223 * @return string 224 */ 225 private function emsa_pss_verify($m, $em, $emBits) 226 { 227 // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error 228 // be output. 229 230 $emLen = ($emBits + 7) >> 3; // ie. ceil($emBits / 8); 231 $sLen = $this->sLen !== null ? $this->sLen : $this->hLen; 232 233 $mHash = $this->hash->hash($m); 234 if ($emLen < $this->hLen + $sLen + 2) { 235 return false; 236 } 237 238 if ($em[strlen($em) - 1] != chr(0xBC)) { 239 return false; 240 } 241 242 $maskedDB = substr($em, 0, -$this->hLen - 1); 243 $h = substr($em, -$this->hLen - 1, $this->hLen); 244 $temp = chr(0xFF << ($emBits & 7)); 245 if ((~$maskedDB[0] & $temp) != $temp) { 246 return false; 247 } 248 $dbMask = $this->mgf1($h, $emLen - $this->hLen - 1); 249 $db = $maskedDB ^ $dbMask; 250 $db[0] = ~chr(0xFF << ($emBits & 7)) & $db[0]; 251 $temp = $emLen - $this->hLen - $sLen - 2; 252 if (substr($db, 0, $temp) != str_repeat(chr(0), $temp) || ord($db[$temp]) != 1) { 253 return false; 254 } 255 $salt = substr($db, $temp + 1); // should be $sLen long 256 $m2 = "\0\0\0\0\0\0\0\0" . $mHash . $salt; 257 $h2 = $this->hash->hash($m2); 258 return hash_equals($h, $h2); 259 } 260 261 /** 262 * RSASSA-PSS-VERIFY 263 * 264 * See {@link http://tools.ietf.org/html/rfc3447#section-8.1.2 RFC3447#section-8.1.2}. 265 * 266 * @param string $m 267 * @param string $s 268 * @return bool|string 269 */ 270 private function rsassa_pss_verify($m, $s) 271 { 272 // Length checking 273 274 if (strlen($s) != $this->k) { 275 return false; 276 } 277 278 // RSA verification 279 280 $modBits = strlen($this->modulus->toBits()); 281 282 $s2 = $this->os2ip($s); 283 $m2 = $this->rsavp1($s2); 284 $em = $this->i2osp($m2, $this->k); 285 if ($em === false) { 286 return false; 287 } 288 289 // EMSA-PSS verification 290 291 return $this->emsa_pss_verify($m, $em, $modBits - 1); 292 } 293 294 /** 295 * Verifies a signature 296 * 297 * @see self::sign() 298 * @param string $message 299 * @param string $signature 300 * @return bool 301 */ 302 public function verify($message, $signature) 303 { 304 switch ($this->signaturePadding) { 305 case self::SIGNATURE_RELAXED_PKCS1: 306 return $this->rsassa_pkcs1_v1_5_relaxed_verify($message, $signature); 307 case self::SIGNATURE_PKCS1: 308 return $this->rsassa_pkcs1_v1_5_verify($message, $signature); 309 //case self::SIGNATURE_PSS: 310 default: 311 return $this->rsassa_pss_verify($message, $signature); 312 } 313 } 314 315 /** 316 * RSAES-PKCS1-V1_5-ENCRYPT 317 * 318 * See {@link http://tools.ietf.org/html/rfc3447#section-7.2.1 RFC3447#section-7.2.1}. 319 * 320 * @param string $m 321 * @param bool $pkcs15_compat optional 322 * @throws \LengthException if strlen($m) > $this->k - 11 323 * @return bool|string 324 */ 325 private function rsaes_pkcs1_v1_5_encrypt($m, $pkcs15_compat = false) 326 { 327 $mLen = strlen($m); 328 329 // Length checking 330 331 if ($mLen > $this->k - 11) { 332 throw new \LengthException('Message too long'); 333 } 334 335 // EME-PKCS1-v1_5 encoding 336 337 $psLen = $this->k - $mLen - 3; 338 $ps = ''; 339 while (strlen($ps) != $psLen) { 340 $temp = Random::string($psLen - strlen($ps)); 341 $temp = str_replace("\x00", '', $temp); 342 $ps .= $temp; 343 } 344 $type = 2; 345 $em = chr(0) . chr($type) . $ps . chr(0) . $m; 346 347 // RSA encryption 348 $m = $this->os2ip($em); 349 $c = $this->rsaep($m); 350 $c = $this->i2osp($c, $this->k); 351 352 // Output the ciphertext C 353 354 return $c; 355 } 356 357 /** 358 * RSAES-OAEP-ENCRYPT 359 * 360 * See {@link http://tools.ietf.org/html/rfc3447#section-7.1.1 RFC3447#section-7.1.1} and 361 * {http://en.wikipedia.org/wiki/Optimal_Asymmetric_Encryption_Padding OAES}. 362 * 363 * @param string $m 364 * @throws \LengthException if strlen($m) > $this->k - 2 * $this->hLen - 2 365 * @return string 366 */ 367 private function rsaes_oaep_encrypt($m) 368 { 369 $mLen = strlen($m); 370 371 // Length checking 372 373 // if $l is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error 374 // be output. 375 376 if ($mLen > $this->k - 2 * $this->hLen - 2) { 377 throw new \LengthException('Message too long'); 378 } 379 380 // EME-OAEP encoding 381 382 $lHash = $this->hash->hash($this->label); 383 $ps = str_repeat(chr(0), $this->k - $mLen - 2 * $this->hLen - 2); 384 $db = $lHash . $ps . chr(1) . $m; 385 $seed = Random::string($this->hLen); 386 $dbMask = $this->mgf1($seed, $this->k - $this->hLen - 1); 387 $maskedDB = $db ^ $dbMask; 388 $seedMask = $this->mgf1($maskedDB, $this->hLen); 389 $maskedSeed = $seed ^ $seedMask; 390 $em = chr(0) . $maskedSeed . $maskedDB; 391 392 // RSA encryption 393 394 $m = $this->os2ip($em); 395 $c = $this->rsaep($m); 396 $c = $this->i2osp($c, $this->k); 397 398 // Output the ciphertext C 399 400 return $c; 401 } 402 403 /** 404 * RSAEP 405 * 406 * See {@link http://tools.ietf.org/html/rfc3447#section-5.1.1 RFC3447#section-5.1.1}. 407 * 408 * @param \phpseclib3\Math\BigInteger $m 409 * @return bool|\phpseclib3\Math\BigInteger 410 */ 411 private function rsaep($m) 412 { 413 if ($m->compare(self::$zero) < 0 || $m->compare($this->modulus) > 0) { 414 throw new \OutOfRangeException('Message representative out of range'); 415 } 416 return $this->exponentiate($m); 417 } 418 419 /** 420 * Raw Encryption / Decryption 421 * 422 * Doesn't use padding and is not recommended. 423 * 424 * @param string $m 425 * @return bool|string 426 * @throws \LengthException if strlen($m) > $this->k 427 */ 428 private function raw_encrypt($m) 429 { 430 if (strlen($m) > $this->k) { 431 throw new \LengthException('Message too long'); 432 } 433 434 $temp = $this->os2ip($m); 435 $temp = $this->rsaep($temp); 436 return $this->i2osp($temp, $this->k); 437 } 438 439 /** 440 * Encryption 441 * 442 * Both self::PADDING_OAEP and self::PADDING_PKCS1 both place limits on how long $plaintext can be. 443 * If $plaintext exceeds those limits it will be broken up so that it does and the resultant ciphertext's will 444 * be concatenated together. 445 * 446 * @see self::decrypt() 447 * @param string $plaintext 448 * @return bool|string 449 * @throws \LengthException if the RSA modulus is too short 450 */ 451 public function encrypt($plaintext) 452 { 453 switch ($this->encryptionPadding) { 454 case self::ENCRYPTION_NONE: 455 return $this->raw_encrypt($plaintext); 456 case self::ENCRYPTION_PKCS1: 457 return $this->rsaes_pkcs1_v1_5_encrypt($plaintext); 458 //case self::ENCRYPTION_OAEP: 459 default: 460 return $this->rsaes_oaep_encrypt($plaintext); 461 } 462 } 463 464 /** 465 * Returns the public key 466 * 467 * The public key is only returned under two circumstances - if the private key had the public key embedded within it 468 * or if the public key was set via setPublicKey(). If the currently loaded key is supposed to be the public key this 469 * function won't return it since this library, for the most part, doesn't distinguish between public and private keys. 470 * 471 * @param string $type 472 * @param array $options optional 473 * @return mixed 474 */ 475 public function toString($type, array $options = []) 476 { 477 $type = self::validatePlugin('Keys', $type, 'savePublicKey'); 478 479 if ($type == PSS::class) { 480 if ($this->signaturePadding == self::SIGNATURE_PSS) { 481 $options += [ 482 'hash' => $this->hash->getHash(), 483 'MGFHash' => $this->mgfHash->getHash(), 484 'saltLength' => $this->getSaltLength() 485 ]; 486 } else { 487 throw new UnsupportedFormatException('The PSS format can only be used when the signature method has been explicitly set to PSS'); 488 } 489 } 490 491 return $type::savePublicKey($this->modulus, $this->publicExponent, $options); 492 } 493 494 /** 495 * Converts a public key to a private key 496 * 497 * @return RSA 498 */ 499 public function asPrivateKey() 500 { 501 $new = new PrivateKey(); 502 $new->exponent = $this->exponent; 503 $new->modulus = $this->modulus; 504 $new->k = $this->k; 505 $new->format = $this->format; 506 return $new 507 ->withHash($this->hash->getHash()) 508 ->withMGFHash($this->mgfHash->getHash()) 509 ->withSaltLength($this->sLen) 510 ->withLabel($this->label) 511 ->withPadding($this->signaturePadding | $this->encryptionPadding); 512 } 513 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body