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 * @category Crypt 25 * @package EC 26 * @author Jim Wigginton <terrafrost@php.net> 27 * @copyright 2016 Jim Wigginton 28 * @license http://www.opensource.org/licenses/mit-license.html MIT License 29 * @link http://phpseclib.sourceforge.net 30 */ 31 32namespace phpseclib3\Crypt; 33 34use phpseclib3\Crypt\Common\AsymmetricKey; 35use phpseclib3\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve; 36use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve; 37use phpseclib3\Crypt\EC\Curves\Curve25519; 38use phpseclib3\Crypt\EC\Curves\Ed25519; 39use phpseclib3\Crypt\EC\Curves\Ed448; 40use phpseclib3\Crypt\EC\Formats\Keys\PKCS1; 41use phpseclib3\Crypt\EC\Parameters; 42use phpseclib3\Crypt\EC\PrivateKey; 43use phpseclib3\Crypt\EC\PublicKey; 44use phpseclib3\Exception\UnsupportedAlgorithmException; 45use phpseclib3\Exception\UnsupportedCurveException; 46use phpseclib3\Exception\UnsupportedOperationException; 47use phpseclib3\File\ASN1; 48use phpseclib3\File\ASN1\Maps\ECParameters; 49use phpseclib3\Math\BigInteger; 50 51/** 52 * Pure-PHP implementation of EC. 53 * 54 * @package EC 55 * @author Jim Wigginton <terrafrost@php.net> 56 * @access public 57 */ 58abstract class EC extends AsymmetricKey 59{ 60 /** 61 * Algorithm Name 62 * 63 * @var string 64 * @access private 65 */ 66 const ALGORITHM = 'EC'; 67 68 /** 69 * Public Key QA 70 * 71 * @var object[] 72 */ 73 protected $QA; 74 75 /** 76 * Curve 77 * 78 * @var \phpseclib3\Crypt\EC\BaseCurves\Base 79 */ 80 protected $curve; 81 82 /** 83 * Signature Format 84 * 85 * @var string 86 * @access private 87 */ 88 protected $format; 89 90 /** 91 * Signature Format (Short) 92 * 93 * @var string 94 * @access private 95 */ 96 protected $shortFormat; 97 98 /** 99 * Curve Name 100 * 101 * @var string 102 */ 103 private $curveName; 104 105 /** 106 * Curve Order 107 * 108 * Used for deterministic ECDSA 109 * 110 * @var \phpseclib3\Math\BigInteger 111 */ 112 protected $q; 113 114 /** 115 * Alias for the private key 116 * 117 * Used for deterministic ECDSA. AsymmetricKey expects $x. I don't like x because 118 * with x you have x * the base point yielding an (x, y)-coordinate that is the 119 * public key. But the x is different depending on which side of the equal sign 120 * you're on. It's less ambiguous if you do dA * base point = (x, y)-coordinate. 121 * 122 * @var \phpseclib3\Math\BigInteger 123 */ 124 protected $x; 125 126 /** 127 * Context 128 * 129 * @var string 130 */ 131 protected $context; 132 133 /** 134 * Create public / private key pair. 135 * 136 * @access public 137 * @param string $curve 138 * @return \phpseclib3\Crypt\EC\PrivateKey 139 */ 140 public static function createKey($curve) 141 { 142 self::initialize_static_variables(); 143 144 if (!isset(self::$engines['PHP'])) { 145 self::useBestEngine(); 146 } 147 148 $curve = strtolower($curve); 149 if (self::$engines['libsodium'] && $curve == 'ed25519' && function_exists('sodium_crypto_sign_keypair')) { 150 $kp = sodium_crypto_sign_keypair(); 151 152 $privatekey = EC::loadFormat('libsodium', sodium_crypto_sign_secretkey($kp)); 153 //$publickey = EC::loadFormat('libsodium', sodium_crypto_sign_publickey($kp)); 154 155 $privatekey->curveName = 'Ed25519'; 156 //$publickey->curveName = $curve; 157 158 return $privatekey; 159 } 160 161 $privatekey = new PrivateKey(); 162 163 $curveName = $curve; 164 if (preg_match('#(?:^curve|^ed)\d+$#', $curveName)) { 165 $curveName = ucfirst($curveName); 166 } elseif (substr($curveName, 0, 10) == 'brainpoolp') { 167 $curveName = 'brainpoolP' . substr($curveName, 10); 168 } 169 $curve = '\phpseclib3\Crypt\EC\Curves\\' . $curveName; 170 171 if (!class_exists($curve)) { 172 throw new UnsupportedCurveException('Named Curve of ' . $curveName . ' is not supported'); 173 } 174 175 $reflect = new \ReflectionClass($curve); 176 $curveName = $reflect->isFinal() ? 177 $reflect->getParentClass()->getShortName() : 178 $reflect->getShortName(); 179 180 $curve = new $curve(); 181 $privatekey->dA = $dA = $curve->createRandomMultiplier(); 182 if ($curve instanceof Curve25519 && self::$engines['libsodium']) { 183 //$r = pack('H*', '0900000000000000000000000000000000000000000000000000000000000000'); 184 //$QA = sodium_crypto_scalarmult($dA->toBytes(), $r); 185 $QA = sodium_crypto_box_publickey_from_secretkey($dA->toBytes()); 186 $privatekey->QA = [$curve->convertInteger(new BigInteger(strrev($QA), 256))]; 187 } else { 188 $privatekey->QA = $curve->multiplyPoint($curve->getBasePoint(), $dA); 189 } 190 $privatekey->curve = $curve; 191 192 //$publickey = clone $privatekey; 193 //unset($publickey->dA); 194 //unset($publickey->x); 195 196 $privatekey->curveName = $curveName; 197 //$publickey->curveName = $curveName; 198 199 if ($privatekey->curve instanceof TwistedEdwardsCurve) { 200 return $privatekey->withHash($curve::HASH); 201 } 202 203 return $privatekey; 204 } 205 206 /** 207 * OnLoad Handler 208 * 209 * @return bool 210 * @access protected 211 * @param array $components 212 */ 213 protected static function onLoad($components) 214 { 215 if (!isset(self::$engines['PHP'])) { 216 self::useBestEngine(); 217 } 218 219 if (!isset($components['dA']) && !isset($components['QA'])) { 220 $new = new Parameters(); 221 $new->curve = $components['curve']; 222 return $new; 223 } 224 225 $new = isset($components['dA']) ? 226 new PrivateKey() : 227 new PublicKey(); 228 $new->curve = $components['curve']; 229 $new->QA = $components['QA']; 230 231 if (isset($components['dA'])) { 232 $new->dA = $components['dA']; 233 } 234 235 if ($new->curve instanceof TwistedEdwardsCurve) { 236 return $new->withHash($components['curve']::HASH); 237 } 238 239 return $new; 240 } 241 242 /** 243 * Constructor 244 * 245 * PublicKey and PrivateKey objects can only be created from abstract RSA class 246 */ 247 protected function __construct() 248 { 249 $this->sigFormat = self::validatePlugin('Signature', 'ASN1'); 250 $this->shortFormat = 'ASN1'; 251 252 parent::__construct(); 253 } 254 255 /** 256 * Returns the curve 257 * 258 * Returns a string if it's a named curve, an array if not 259 * 260 * @access public 261 * @return string|array 262 */ 263 public function getCurve() 264 { 265 if ($this->curveName) { 266 return $this->curveName; 267 } 268 269 if ($this->curve instanceof MontgomeryCurve) { 270 $this->curveName = $this->curve instanceof Curve25519 ? 'Curve25519' : 'Curve448'; 271 return $this->curveName; 272 } 273 274 if ($this->curve instanceof TwistedEdwardsCurve) { 275 $this->curveName = $this->curve instanceof Ed25519 ? 'Ed25519' : 'Ed448'; 276 return $this->curveName; 277 } 278 279 $params = $this->getParameters()->toString('PKCS8', ['namedCurve' => true]); 280 $decoded = ASN1::extractBER($params); 281 $decoded = ASN1::decodeBER($decoded); 282 $decoded = ASN1::asn1map($decoded[0], ECParameters::MAP); 283 if (isset($decoded['namedCurve'])) { 284 $this->curveName = $decoded['namedCurve']; 285 return $decoded['namedCurve']; 286 } 287 288 if (!$namedCurves) { 289 PKCS1::useSpecifiedCurve(); 290 } 291 292 return $decoded; 293 } 294 295 /** 296 * Returns the key size 297 * 298 * Quoting https://tools.ietf.org/html/rfc5656#section-2, 299 * 300 * "The size of a set of elliptic curve domain parameters on a prime 301 * curve is defined as the number of bits in the binary representation 302 * of the field order, commonly denoted by p. Size on a 303 * characteristic-2 curve is defined as the number of bits in the binary 304 * representation of the field, commonly denoted by m. A set of 305 * elliptic curve domain parameters defines a group of order n generated 306 * by a base point P" 307 * 308 * @access public 309 * @return int 310 */ 311 public function getLength() 312 { 313 return $this->curve->getLength(); 314 } 315 316 /** 317 * Returns the current engine being used 318 * 319 * @see self::useInternalEngine() 320 * @see self::useBestEngine() 321 * @access public 322 * @return string 323 */ 324 public function getEngine() 325 { 326 if (!isset(self::$engines['PHP'])) { 327 self::useBestEngine(); 328 } 329 if ($this->curve instanceof TwistedEdwardsCurve) { 330 return $this->curve instanceof Ed25519 && self::$engines['libsodium'] && !isset($this->context) ? 331 'libsodium' : 'PHP'; 332 } 333 334 return self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods()) ? 335 'OpenSSL' : 'PHP'; 336 } 337 338 /** 339 * Returns the public key coordinates as a string 340 * 341 * Used by ECDH 342 * 343 * @return string 344 */ 345 public function getEncodedCoordinates() 346 { 347 if ($this->curve instanceof MontgomeryCurve) { 348 return strrev($this->QA[0]->toBytes(true)); 349 } 350 if ($this->curve instanceof TwistedEdwardsCurve) { 351 return $this->curve->encodePoint($this->QA); 352 } 353 return "\4" . $this->QA[0]->toBytes(true) . $this->QA[1]->toBytes(true); 354 } 355 356 /** 357 * Returns the parameters 358 * 359 * @see self::getPublicKey() 360 * @access public 361 * @param string $type optional 362 * @return mixed 363 */ 364 public function getParameters($type = 'PKCS1') 365 { 366 $type = self::validatePlugin('Keys', $type, 'saveParameters'); 367 368 $key = $type::saveParameters($this->curve); 369 370 return EC::load($key, 'PKCS1') 371 ->withHash($this->hash->getHash()) 372 ->withSignatureFormat($this->shortFormat); 373 } 374 375 /** 376 * Determines the signature padding mode 377 * 378 * Valid values are: ASN1, SSH2, Raw 379 * 380 * @access public 381 * @param string $format 382 */ 383 public function withSignatureFormat($format) 384 { 385 if ($this->curve instanceof MontgomeryCurve) { 386 throw new UnsupportedOperationException('Montgomery Curves cannot be used to create signatures'); 387 } 388 389 $new = clone $this; 390 $new->shortFormat = $format; 391 $new->sigFormat = self::validatePlugin('Signature', $format); 392 return $new; 393 } 394 395 /** 396 * Returns the signature format currently being used 397 * 398 * @access public 399 */ 400 public function getSignatureFormat() 401 { 402 return $this->shortFormat; 403 } 404 405 /** 406 * Sets the context 407 * 408 * Used by Ed25519 / Ed448. 409 * 410 * @see self::sign() 411 * @see self::verify() 412 * @access public 413 * @param string $context optional 414 */ 415 public function withContext($context = null) 416 { 417 if (!$this->curve instanceof TwistedEdwardsCurve) { 418 throw new UnsupportedCurveException('Only Ed25519 and Ed448 support contexts'); 419 } 420 421 $new = clone $this; 422 if (!isset($context)) { 423 $new->context = null; 424 return $new; 425 } 426 if (!is_string($context)) { 427 throw new \InvalidArgumentException('setContext expects a string'); 428 } 429 if (strlen($context) > 255) { 430 throw new \LengthException('The context is supposed to be, at most, 255 bytes long'); 431 } 432 $new->context = $context; 433 return $new; 434 } 435 436 /** 437 * Returns the signature format currently being used 438 * 439 * @access public 440 */ 441 public function getContext() 442 { 443 return $this->context; 444 } 445 446 /** 447 * Determines which hashing function should be used 448 * 449 * @access public 450 * @param string $hash 451 */ 452 public function withHash($hash) 453 { 454 if ($this->curve instanceof MontgomeryCurve) { 455 throw new UnsupportedOperationException('Montgomery Curves cannot be used to create signatures'); 456 } 457 if ($this->curve instanceof Ed25519 && $hash != 'sha512') { 458 throw new UnsupportedAlgorithmException('Ed25519 only supports sha512 as a hash'); 459 } 460 if ($this->curve instanceof Ed448 && $hash != 'shake256-912') { 461 throw new UnsupportedAlgorithmException('Ed448 only supports shake256 with a length of 114 bytes'); 462 } 463 464 return parent::withHash($hash); 465 } 466 467 /** 468 * __toString() magic method 469 * 470 * @return string 471 */ 472 public function __toString() 473 { 474 if ($this->curve instanceof MontgomeryCurve) { 475 return ''; 476 } 477 478 return parent::__toString(); 479 } 480} 481