1<?php 2 3/** 4 * Pure-PHP implementation of EC. 5 * 6 * PHP version 5 7 * 8 * Here's an example of how to create signatures and verify signatures with this library: 9 * <code> 10 * <?php 11 * include 'vendor/autoload.php'; 12 * 13 * $private = \phpseclib3\Crypt\EC::createKey('secp256k1'); 14 * $public = $private->getPublicKey(); 15 * 16 * $plaintext = 'terrafrost'; 17 * 18 * $signature = $private->sign($plaintext); 19 * 20 * echo $public->verify($plaintext, $signature) ? 'verified' : 'unverified'; 21 * ?> 22 * </code> 23 * 24 * @author Jim Wigginton <terrafrost@php.net> 25 * @copyright 2016 Jim Wigginton 26 * @license http://www.opensource.org/licenses/mit-license.html MIT License 27 * @link http://phpseclib.sourceforge.net 28 */ 29 30namespace phpseclib3\Crypt; 31 32use phpseclib3\Crypt\Common\AsymmetricKey; 33use phpseclib3\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve; 34use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve; 35use phpseclib3\Crypt\EC\Curves\Curve25519; 36use phpseclib3\Crypt\EC\Curves\Curve448; 37use phpseclib3\Crypt\EC\Curves\Ed25519; 38use phpseclib3\Crypt\EC\Curves\Ed448; 39use phpseclib3\Crypt\EC\Formats\Keys\PKCS1; 40use phpseclib3\Crypt\EC\Formats\Keys\PKCS8; 41use phpseclib3\Crypt\EC\Parameters; 42use phpseclib3\Crypt\EC\PrivateKey; 43use phpseclib3\Crypt\EC\PublicKey; 44use phpseclib3\Exception\BadConfigurationException; 45use phpseclib3\Exception\UnsupportedAlgorithmException; 46use phpseclib3\Exception\UnsupportedCurveException; 47use phpseclib3\Exception\UnsupportedOperationException; 48use phpseclib3\File\ASN1; 49use phpseclib3\File\ASN1\Maps\ECParameters; 50use phpseclib3\Math\BigInteger; 51 52/** 53 * Pure-PHP implementation of EC. 54 * 55 * @author Jim Wigginton <terrafrost@php.net> 56 */ 57abstract class EC extends AsymmetricKey 58{ 59 /** 60 * Algorithm Name 61 * 62 * @var string 63 */ 64 const ALGORITHM = 'EC'; 65 66 /** 67 * Public Key QA 68 * 69 * @var object[] 70 */ 71 protected $QA; 72 73 /** 74 * Curve 75 * 76 * @var EC\BaseCurves\Base 77 */ 78 protected $curve; 79 80 /** 81 * Signature Format (Short) 82 * 83 * @var string 84 */ 85 protected $shortFormat; 86 87 /** 88 * Curve Name 89 * 90 * @var string 91 */ 92 private $curveName; 93 94 /** 95 * Curve Order 96 * 97 * Used for deterministic ECDSA 98 * 99 * @var BigInteger 100 */ 101 protected $q; 102 103 /** 104 * Alias for the private key 105 * 106 * Used for deterministic ECDSA. AsymmetricKey expects $x. I don't like x because 107 * with x you have x * the base point yielding an (x, y)-coordinate that is the 108 * public key. But the x is different depending on which side of the equal sign 109 * you're on. It's less ambiguous if you do dA * base point = (x, y)-coordinate. 110 * 111 * @var BigInteger 112 */ 113 protected $x; 114 115 /** 116 * Context 117 * 118 * @var string 119 */ 120 protected $context; 121 122 /** 123 * Signature Format 124 * 125 * @var string 126 */ 127 protected $sigFormat; 128 129 /** 130 * Forced Engine 131 * 132 * @var ?string 133 * @see parent::forceEngine() 134 */ 135 protected static $forcedEngine = null; 136 137 /** 138 * Create public / private key pair. 139 * 140 * @param string $curve 141 * @return PrivateKey 142 */ 143 public static function createKey($curve) 144 { 145 self::initialize_static_variables(); 146 147 $class = new \ReflectionClass(static::class); 148 if ($class->isFinal()) { 149 throw new \RuntimeException('createKey() should not be called from final classes (' . static::class . ')'); 150 } 151 152 $curveName = self::getCurveCase($curve); 153 $curve = '\phpseclib3\Crypt\EC\Curves\\' . $curveName; 154 155 if (!class_exists($curve)) { 156 throw new UnsupportedCurveException('Named Curve of ' . $curveName . ' is not supported'); 157 } 158 159 $reflect = new \ReflectionClass($curve); 160 $curveName = $reflect->isFinal() ? 161 $reflect->getParentClass()->getShortName() : 162 $reflect->getShortName(); 163 $curveEngineName = self::getOpenSSLCurveName(strtolower($curveName)); 164 165 switch ($curveName) { 166 case 'Ed25519': 167 $providers = [ 168 'libsodium' => function_exists('sodium_crypto_sign_keypair'), 169 // OPENSSL_KEYTYPE_ED25519 introduced in PHP 8.4.0 170 'OpenSSL' => defined('OPENSSL_KEYTYPE_ED25519'), 171 ]; 172 break; 173 case 'Ed448': 174 // OPENSSL_KEYTYPE_ED448 introduced in PHP 8.4.0 175 $providers = ['OpenSSL' => defined('OPENSSL_KEYTYPE_ED448')]; 176 break; 177 case 'Curve25519': 178 $providers = [ 179 'libsodium' => function_exists('sodium_crypto_box_publickey_from_secretkey'), 180 // OPENSSL_KEYTYPE_X25519 introduced in PHP 8.4.0 181 'OpenSSL' => defined('OPENSSL_KEYTYPE_X25519'), 182 ]; 183 // OPENSSL_KEYTYPE_X448 introduced in PHP 8.4.0 184 case 'Curve448': 185 $providers = ['OpenSSL' => defined('OPENSSL_KEYTYPE_X448')]; 186 break; 187 default: 188 // openssl_get_curve_names() was introduced in PHP 7.1.0 189 // exclude curve25519 and curve448 from testing 190 $providers = ['OpenSSL' => function_exists('openssl_get_curve_names') && substr($curveEngineName, 0, 5) != 'curve' && in_array($curveEngineName, openssl_get_curve_names())]; 191 }; 192 193 foreach ($providers as $engine => $isSupported) { 194 // if an engine is being forced and the forced engine doesn't match $engine, skip it 195 if (isset(self::$forcedEngine) && self::$forcedEngine !== $engine) { 196 continue; 197 } 198 if ($isSupported) { 199 $result = self::generateWithEngine($engine, $curveEngineName); 200 if (isset($result)) { 201 return $result; 202 } 203 } 204 if (self::$forcedEngine === $engine) { 205 throw new BadConfigurationException("EC::createKey: Engine $engine is forced but unsupported for $curve"); 206 } 207 } 208 209 $privatekey = new PrivateKey(); 210 211 $curve = new $curve(); 212 if ($curve instanceof TwistedEdwardsCurve) { 213 $arr = $curve->extractSecret(Random::string($curve instanceof Ed448 ? 57 : 32)); 214 $privatekey->dA = $dA = $arr['dA']; 215 $privatekey->secret = $arr['secret']; 216 } else { 217 $privatekey->dA = $dA = $curve->createRandomMultiplier(); 218 } 219 $privatekey->curve = $curve; 220 $privatekey->curveName = $curveName; 221 $privatekey->QA = $curve->multiplyPoint($curve->getBasePoint(), $dA); 222 223 //$publickey = clone $privatekey; 224 //unset($publickey->dA); 225 //unset($publickey->x); 226 227 //$publickey->curveName = $curveName; 228 229 if ($privatekey->curve instanceof TwistedEdwardsCurve) { 230 return $privatekey->withHash($curve::HASH); 231 } 232 233 return $privatekey; 234 } 235 236 /** 237 * Returns the actual case of the curve 238 * 239 * Useful for initializing the curve class 240 * 241 * @param string $curveName 242 * @return string 243 */ 244 private static function getCurveCase($curveName) 245 { 246 $curveName = strtolower($curveName); 247 if (preg_match('#(?:^curve|^ed)\d+$#', $curveName)) { 248 return ucfirst($curveName); 249 } 250 if (substr($curveName, 0, 10) == 'brainpoolp') { 251 return 'brainpoolP' . substr($curveName, 10); 252 } 253 return $curveName; 254 } 255 256 /** 257 * Return the OpenSSL name for a curve 258 * 259 * @param string $curve 260 * @return string 261 */ 262 private static function getOpenSSLCurveName($curve) 263 { 264 switch ($curve) { 265 case 'secp256r1': 266 return 'prime256v1'; 267 case 'secp192r1': 268 return 'prime192v1'; 269 } 270 return $curve; 271 } 272 273 /** 274 * Generate the key for a given curve / engine combo 275 * 276 * @param string $engine 277 * @param string $curve 278 * @return ?PrivateKey 279 */ 280 private static function generateWithEngine($engine, $curve) 281 { 282 if ($engine == 'libsodium') { 283 if ($curve == 'ed25519') { 284 $kp = sodium_crypto_sign_keypair(); 285 286 $privatekey = EC::loadFormat('libsodium', sodium_crypto_sign_secretkey($kp)); 287 //$publickey = EC::loadFormat('libsodium', sodium_crypto_sign_publickey($kp)); 288 289 $privatekey->curveName = 'Ed25519'; 290 //$publickey->curveName = $curve; 291 292 return $privatekey; 293 } else { // $curve == 'curve25519 294 $privatekey = new PrivateKey(); 295 $privatekey->curve = new Curve25519(); 296 $privatekey->curveName = 'Curve25519'; 297 $privatekey->dA = $privatekey->curve->createRandomMultiplier(); 298 $dA = str_pad($privatekey->dA->toBytes(), 32, "\0", STR_PAD_LEFT); 299 //$r = pack('H*', '0900000000000000000000000000000000000000000000000000000000000000'); 300 //$QA = sodium_crypto_scalarmult($dA, $r); 301 $QA = sodium_crypto_box_publickey_from_secretkey($dA); 302 $privatekey->QA = [$privatekey->curve->convertInteger(new BigInteger(strrev($QA), 256))]; 303 return $privatekey; 304 } 305 } 306 307 // at this point $engine == 'OpenSSL' 308 309 $curveName = self::getCurveCase($curve); 310 311 $config = []; 312 if (self::$configFile) { 313 $config['config'] = self::$configFile; 314 } 315 $params = $config; 316 switch ($curve) { 317 case 'ed25519': 318 $params['private_key_type'] = OPENSSL_KEYTYPE_ED25519; 319 break; 320 case 'ed448': 321 $params['private_key_type'] = OPENSSL_KEYTYPE_ED448; 322 break; 323 case 'curve25519': 324 $params['private_key_type'] = OPENSSL_KEYTYPE_X25519; 325 break; 326 case 'curve448': 327 $params['private_key_type'] = OPENSSL_KEYTYPE_X448; 328 break; 329 default: 330 $params['private_key_type'] = OPENSSL_KEYTYPE_EC; 331 $params['curve_name'] = $curveName; 332 } 333 334 $key = openssl_pkey_new($params); 335 if (!$key) { 336 return null; 337 } 338 $privateKeyStr = ''; 339 if (!openssl_pkey_export($key, $privateKeyStr, null, $config)) { 340 return null; 341 } 342 // clear the buffer of error strings 343 while (openssl_error_string() !== false) { 344 } 345 // some versions of OpenSSL / PHP return PKCS1 keys, others return PKCS8 keys 346 $privatekey = EC::load($privateKeyStr); 347 switch ($curveName) { 348 case 'prime256v1': 349 $privatekey->curveName = 'secp256r1'; 350 break; 351 case 'prime192v1': 352 $privatekey->curveName = 'secp192r1'; 353 break; 354 default: 355 $privatekey->curveName = $curveName; 356 } 357 return $privatekey; 358 } 359 360 /** 361 * OnLoad Handler 362 * 363 * @return bool 364 */ 365 protected static function onLoad(array $components) 366 { 367 if (!isset($components['dA']) && !isset($components['QA'])) { 368 $new = new Parameters(); 369 $new->curve = $components['curve']; 370 return $new; 371 } 372 373 $new = isset($components['dA']) ? 374 new PrivateKey() : 375 new PublicKey(); 376 $new->curve = $components['curve']; 377 $new->QA = $components['QA']; 378 379 if (isset($components['dA'])) { 380 $new->dA = $components['dA']; 381 $new->secret = $components['secret']; 382 } 383 384 if ($new->curve instanceof TwistedEdwardsCurve) { 385 return $new->withHash($components['curve']::HASH); 386 } 387 388 return $new; 389 } 390 391 /** 392 * Constructor 393 * 394 * PublicKey and PrivateKey objects can only be created from abstract RSA class 395 */ 396 protected function __construct() 397 { 398 $this->sigFormat = self::validatePlugin('Signature', 'ASN1'); 399 $this->shortFormat = 'ASN1'; 400 401 parent::__construct(); 402 } 403 404 /** 405 * Returns the curve 406 * 407 * Returns a string if it's a named curve, an array if not 408 * 409 * @return string|array 410 */ 411 public function getCurve() 412 { 413 if ($this->curveName) { 414 return $this->curveName; 415 } 416 417 if ($this->curve instanceof MontgomeryCurve) { 418 $this->curveName = $this->curve instanceof Curve25519 ? 'Curve25519' : 'Curve448'; 419 return $this->curveName; 420 } 421 422 if ($this->curve instanceof TwistedEdwardsCurve) { 423 $this->curveName = $this->curve instanceof Ed25519 ? 'Ed25519' : 'Ed448'; 424 return $this->curveName; 425 } 426 427 $params = $this->getParameters()->toString('PKCS8', ['namedCurve' => true]); 428 $decoded = ASN1::extractBER($params); 429 $decoded = ASN1::decodeBER($decoded); 430 $decoded = ASN1::asn1map($decoded[0], ECParameters::MAP); 431 if (isset($decoded['namedCurve'])) { 432 $this->curveName = $decoded['namedCurve']; 433 return $decoded['namedCurve']; 434 } 435 436 if (!$namedCurves) { 437 PKCS1::useSpecifiedCurve(); 438 } 439 440 return $decoded; 441 } 442 443 /** 444 * Returns the key size 445 * 446 * Quoting https://tools.ietf.org/html/rfc5656#section-2, 447 * 448 * "The size of a set of elliptic curve domain parameters on a prime 449 * curve is defined as the number of bits in the binary representation 450 * of the field order, commonly denoted by p. Size on a 451 * characteristic-2 curve is defined as the number of bits in the binary 452 * representation of the field, commonly denoted by m. A set of 453 * elliptic curve domain parameters defines a group of order n generated 454 * by a base point P" 455 * 456 * @return int 457 */ 458 public function getLength() 459 { 460 return $this->curve->getLength(); 461 } 462 463 /** 464 * Returns the public key coordinates as a string 465 * 466 * Used by ECDH 467 * 468 * @return string 469 */ 470 public function getEncodedCoordinates() 471 { 472 if ($this->curve instanceof MontgomeryCurve) { 473 return strrev($this->QA[0]->toBytes(true)); 474 } 475 if ($this->curve instanceof TwistedEdwardsCurve) { 476 return $this->curve->encodePoint($this->QA); 477 } 478 return "\4" . $this->QA[0]->toBytes(true) . $this->QA[1]->toBytes(true); 479 } 480 481 /** 482 * Convert point to public key 483 * 484 * For Weierstrass curves, if only the x coordinate is present (as is the case after doing a round of ECDH) 485 * then we'll guess at the y coordinate. There are only two possible y values and, atleast in-so-far as 486 * multiplication is concerned, neither value affects the resultant x value 487 * 488 * If $toPublicKey is set to false then a string will be returned - a kind of public key precursor 489 * 490 * @param string $curveName 491 * @param string $secret 492 * @param bool $toPublicKey optional 493 * @return PublicKey|string 494 */ 495 public static function convertPointToPublicKey($curveName, $secret, $toPublicKey = true) 496 { 497 $curveName = self::getCurveCase($curveName); 498 $curve = '\phpseclib3\Crypt\EC\Curves\\' . $curveName; 499 500 if (!class_exists($curve)) { 501 throw new UnsupportedCurveException('Named Curve of ' . $curveName . ' is not supported'); 502 } 503 504 $curve = new $curve(); 505 if (!$curve instanceof TwistedEdwardsCurve) { 506 if ($curve instanceof MontgomeryCurve) { 507 $secret = strrev($secret); 508 } elseif ($curve->getLengthInBytes() == strlen($secret)) { 509 $secret = "\3$secret"; 510 } 511 if (!$toPublicKey) { 512 return $secret; 513 } 514 $secret = "\0$secret"; 515 } elseif (!$toPublicKey) { 516 return $secret; 517 } 518 $QA = PKCS8::extractPoint($secret, $curve); 519 $key = PKCS8::savePublicKey($curve, $QA); 520 return EC::loadFormat('PKCS8', $key); 521 } 522 523 /** 524 * Returns the parameters 525 * 526 * @see self::getPublicKey() 527 * @param string $type optional 528 * @return mixed 529 */ 530 public function getParameters($type = 'PKCS1') 531 { 532 $type = self::validatePlugin('Keys', $type, 'saveParameters'); 533 534 $key = $type::saveParameters($this->curve); 535 536 return EC::load($key, 'PKCS1') 537 ->withHash($this->hash->getHash()) 538 ->withSignatureFormat($this->shortFormat); 539 } 540 541 /** 542 * Determines the signature padding mode 543 * 544 * Valid values are: ASN1, SSH2, Raw 545 * 546 * @param string $format 547 */ 548 public function withSignatureFormat($format) 549 { 550 if ($this->curve instanceof MontgomeryCurve) { 551 throw new UnsupportedOperationException('Montgomery Curves cannot be used to create signatures'); 552 } 553 554 $new = clone $this; 555 $new->shortFormat = $format; 556 $new->sigFormat = self::validatePlugin('Signature', $format); 557 return $new; 558 } 559 560 /** 561 * Returns the signature format currently being used 562 * 563 */ 564 public function getSignatureFormat() 565 { 566 return $this->shortFormat; 567 } 568 569 /** 570 * Sets the context 571 * 572 * Used by Ed25519 / Ed448. 573 * 574 * @see self::sign() 575 * @see self::verify() 576 * @param string $context optional 577 */ 578 public function withContext($context = null) 579 { 580 if (!$this->curve instanceof TwistedEdwardsCurve) { 581 throw new UnsupportedCurveException('Only Ed25519 and Ed448 support contexts'); 582 } 583 584 $new = clone $this; 585 if (!isset($context)) { 586 $new->context = null; 587 return $new; 588 } 589 if (!is_string($context)) { 590 throw new \InvalidArgumentException('setContext expects a string'); 591 } 592 if (strlen($context) > 255) { 593 throw new \LengthException('The context is supposed to be, at most, 255 bytes long'); 594 } 595 $new->context = $context; 596 return $new; 597 } 598 599 /** 600 * Returns the signature format currently being used 601 * 602 */ 603 public function getContext() 604 { 605 return $this->context; 606 } 607 608 /** 609 * Determines which hashing function should be used 610 * 611 * @param string $hash 612 */ 613 public function withHash($hash) 614 { 615 if ($this->curve instanceof MontgomeryCurve) { 616 throw new UnsupportedOperationException('Montgomery Curves cannot be used to create signatures'); 617 } 618 if ($this->curve instanceof Ed25519 && $hash != 'sha512') { 619 throw new UnsupportedAlgorithmException('Ed25519 only supports sha512 as a hash'); 620 } 621 if ($this->curve instanceof Ed448 && $hash != 'shake256-912') { 622 throw new UnsupportedAlgorithmException('Ed448 only supports shake256 with a length of 114 bytes'); 623 } 624 625 return parent::withHash($hash); 626 } 627} 628