phpseclib/phpseclib/Crypt/EC.php

449 lines
12 KiB
PHP
Raw Normal View History

2018-10-25 01:00:37 +00:00
<?php
/**
2019-06-28 00:10:40 +00:00
* Pure-PHP implementation of EC.
2018-10-25 01:00:37 +00:00
*
* PHP version 5
*
* Here's an example of how to create signatures and verify signatures with this library:
* <code>
* <?php
* include 'vendor/autoload.php';
*
* $private = \phpseclib3\Crypt\EC::createKey('secp256k1');
* $public = $private->getPublicKey();
2018-10-25 01:00:37 +00:00
*
* $plaintext = 'terrafrost';
*
* $signature = $private->sign($plaintext);
2018-10-25 01:00:37 +00:00
*
* echo $public->verify($plaintext, $signature) ? 'verified' : 'unverified';
2018-10-25 01:00:37 +00:00
* ?>
* </code>
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2016 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
2022-06-04 15:31:21 +00:00
declare(strict_types=1);
namespace phpseclib3\Crypt;
use phpseclib3\Crypt\Common\AsymmetricKey;
use phpseclib3\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve;
2022-01-30 15:34:42 +00:00
use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve;
use phpseclib3\Crypt\EC\Curves\Curve25519;
use phpseclib3\Crypt\EC\Curves\Ed25519;
use phpseclib3\Crypt\EC\Curves\Ed448;
use phpseclib3\Crypt\EC\Formats\Keys\PKCS1;
2022-01-30 15:34:42 +00:00
use phpseclib3\Crypt\EC\Parameters;
use phpseclib3\Crypt\EC\PrivateKey;
use phpseclib3\Crypt\EC\PublicKey;
use phpseclib3\Exception\UnsupportedAlgorithmException;
2022-01-30 15:34:42 +00:00
use phpseclib3\Exception\UnsupportedCurveException;
use phpseclib3\Exception\UnsupportedOperationException;
2022-01-30 15:34:42 +00:00
use phpseclib3\File\ASN1;
use phpseclib3\File\ASN1\Maps\ECParameters;
use phpseclib3\Math\BigInteger;
2018-10-25 01:00:37 +00:00
/**
2019-06-28 00:10:40 +00:00
* Pure-PHP implementation of EC.
2018-10-25 01:00:37 +00:00
*
* @author Jim Wigginton <terrafrost@php.net>
*/
2019-06-28 00:10:40 +00:00
abstract class EC extends AsymmetricKey
2018-10-25 01:00:37 +00:00
{
/**
* Algorithm Name
*
* @var string
*/
2019-06-28 00:10:40 +00:00
const ALGORITHM = 'EC';
2018-10-25 01:00:37 +00:00
/**
* Public Key QA
*
* @var object[]
*/
protected $QA;
2018-10-25 01:00:37 +00:00
/**
* Curve
*
* @var \phpseclib3\Crypt\EC\BaseCurves\Base
2018-10-25 01:00:37 +00:00
*/
protected $curve;
2018-10-25 01:00:37 +00:00
/**
* Signature Format
2018-10-25 01:00:37 +00:00
*
* @var string
*/
protected $format;
2018-10-25 01:00:37 +00:00
/**
* Signature Format (Short)
2018-10-25 01:00:37 +00:00
*
* @var string
2018-10-25 01:00:37 +00:00
*/
protected $shortFormat;
2018-10-25 01:00:37 +00:00
/**
* Curve Name
2018-10-25 01:00:37 +00:00
*
* @var string
2018-10-25 01:00:37 +00:00
*/
private $curveName;
2018-10-25 01:00:37 +00:00
/**
* Curve Order
*
* Used for deterministic ECDSA
*
* @var \phpseclib3\Math\BigInteger
*/
protected $q;
/**
* Alias for the private key
*
* Used for deterministic ECDSA. AsymmetricKey expects $x. I don't like x because
* with x you have x * the base point yielding an (x, y)-coordinate that is the
* public key. But the x is different depending on which side of the equal sign
* you're on. It's less ambiguous if you do dA * base point = (x, y)-coordinate.
*
* @var \phpseclib3\Math\BigInteger
*/
protected $x;
2018-10-25 01:00:37 +00:00
/**
* Context
2018-10-25 01:00:37 +00:00
*
* @var string
2018-10-25 01:00:37 +00:00
*/
protected $context;
2018-10-25 01:00:37 +00:00
/**
* Create public / private key pair.
*/
2022-06-04 15:31:21 +00:00
public static function createKey(string $curve): PrivateKey
2018-10-25 01:00:37 +00:00
{
self::initialize_static_variables();
if (!isset(self::$engines['PHP'])) {
self::useBestEngine();
}
$curve = strtolower($curve);
if (self::$engines['libsodium'] && $curve == 'ed25519' && function_exists('sodium_crypto_sign_keypair')) {
2018-10-25 01:00:37 +00:00
$kp = sodium_crypto_sign_keypair();
$privatekey = EC::loadFormat('libsodium', sodium_crypto_sign_secretkey($kp));
//$publickey = EC::loadFormat('libsodium', sodium_crypto_sign_publickey($kp));
2018-10-25 01:00:37 +00:00
$privatekey->curveName = 'Ed25519';
//$publickey->curveName = $curve;
2018-10-25 01:00:37 +00:00
return $privatekey;
2018-10-25 01:00:37 +00:00
}
2022-02-17 02:25:59 +00:00
$privatekey = new PrivateKey();
2018-10-25 01:00:37 +00:00
$curveName = $curve;
2021-01-08 01:35:34 +00:00
if (preg_match('#(?:^curve|^ed)\d+$#', $curveName)) {
$curveName = ucfirst($curveName);
} elseif (substr($curveName, 0, 10) == 'brainpoolp') {
$curveName = 'brainpoolP' . substr($curveName, 10);
}
$curve = '\phpseclib3\Crypt\EC\Curves\\' . $curveName;
2021-01-08 01:35:34 +00:00
2018-10-25 01:00:37 +00:00
if (!class_exists($curve)) {
2021-01-08 01:35:34 +00:00
throw new UnsupportedCurveException('Named Curve of ' . $curveName . ' is not supported');
2018-10-25 01:00:37 +00:00
}
$reflect = new \ReflectionClass($curve);
$curveName = $reflect->isFinal() ?
$reflect->getParentClass()->getShortName() :
$reflect->getShortName();
2018-10-25 01:00:37 +00:00
$curve = new $curve();
$privatekey->dA = $dA = $curve->createRandomMultiplier();
if ($curve instanceof Curve25519 && self::$engines['libsodium']) {
//$r = pack('H*', '0900000000000000000000000000000000000000000000000000000000000000');
//$QA = sodium_crypto_scalarmult($dA->toBytes(), $r);
$QA = sodium_crypto_box_publickey_from_secretkey($dA->toBytes());
$privatekey->QA = [$curve->convertInteger(new BigInteger(strrev($QA), 256))];
} else {
$privatekey->QA = $curve->multiplyPoint($curve->getBasePoint(), $dA);
}
2018-10-25 01:00:37 +00:00
$privatekey->curve = $curve;
//$publickey = clone $privatekey;
//unset($publickey->dA);
//unset($publickey->x);
2018-10-25 01:00:37 +00:00
$privatekey->curveName = $curveName;
//$publickey->curveName = $curveName;
if ($privatekey->curve instanceof TwistedEdwardsCurve) {
return $privatekey->withHash($curve::HASH);
}
2018-10-25 01:00:37 +00:00
return $privatekey;
2018-10-25 01:00:37 +00:00
}
/**
* OnLoad Handler
2018-10-25 01:00:37 +00:00
*
2022-06-04 15:31:21 +00:00
*@return bool
2018-10-25 01:00:37 +00:00
*/
2022-06-04 15:31:21 +00:00
protected static function onLoad(array $components)
2018-10-25 01:00:37 +00:00
{
if (!isset(self::$engines['PHP'])) {
self::useBestEngine();
}
if (!isset($components['dA']) && !isset($components['QA'])) {
2022-02-17 02:25:59 +00:00
$new = new Parameters();
$new->curve = $components['curve'];
return $new;
2018-10-25 01:00:37 +00:00
}
$new = isset($components['dA']) ?
2022-02-17 02:25:59 +00:00
new PrivateKey() :
new PublicKey();
$new->curve = $components['curve'];
$new->QA = $components['QA'];
2018-10-25 01:00:37 +00:00
if (isset($components['dA'])) {
$new->dA = $components['dA'];
}
if ($new->curve instanceof TwistedEdwardsCurve) {
return $new->withHash($components['curve']::HASH);
}
2018-10-25 01:00:37 +00:00
return $new;
2018-10-25 01:00:37 +00:00
}
/**
* Constructor
*
* PublicKey and PrivateKey objects can only be created from abstract RSA class
*/
protected function __construct()
{
$this->sigFormat = self::validatePlugin('Signature', 'ASN1');
$this->shortFormat = 'ASN1';
parent::__construct();
}
2018-10-25 01:00:37 +00:00
/**
* Returns the curve
*
* Returns a string if it's a named curve, an array if not
*
* @return string|array
*/
public function getCurve()
{
if ($this->curveName) {
return $this->curveName;
}
if ($this->curve instanceof MontgomeryCurve) {
$this->curveName = $this->curve instanceof Curve25519 ? 'Curve25519' : 'Curve448';
return $this->curveName;
}
2018-10-25 01:00:37 +00:00
if ($this->curve instanceof TwistedEdwardsCurve) {
$this->curveName = $this->curve instanceof Ed25519 ? 'Ed25519' : 'Ed448';
return $this->curveName;
}
$params = $this->getParameters()->toString('PKCS8', ['namedCurve' => true]);
2018-10-25 01:00:37 +00:00
$decoded = ASN1::extractBER($params);
$decoded = ASN1::decodeBER($decoded);
$decoded = ASN1::asn1map($decoded[0], ECParameters::MAP);
if (isset($decoded['namedCurve'])) {
$this->curveName = $decoded['namedCurve'];
return $decoded['namedCurve'];
}
if (!$namedCurves) {
PKCS1::useSpecifiedCurve();
}
return $decoded;
}
/**
* Returns the key size
*
* Quoting https://tools.ietf.org/html/rfc5656#section-2,
*
* "The size of a set of elliptic curve domain parameters on a prime
* curve is defined as the number of bits in the binary representation
* of the field order, commonly denoted by p. Size on a
* characteristic-2 curve is defined as the number of bits in the binary
* representation of the field, commonly denoted by m. A set of
* elliptic curve domain parameters defines a group of order n generated
* by a base point P"
*/
2022-06-04 15:31:21 +00:00
public function getLength(): int
2018-10-25 01:00:37 +00:00
{
return $this->curve->getLength();
}
/**
* Returns the current engine being used
2018-10-25 01:00:37 +00:00
*
* @see self::useInternalEngine()
* @see self::useBestEngine()
2018-10-25 01:00:37 +00:00
*/
2022-06-04 15:31:21 +00:00
public function getEngine(): string
2018-10-25 01:00:37 +00:00
{
if (!isset(self::$engines['PHP'])) {
self::useBestEngine();
}
if ($this->curve instanceof TwistedEdwardsCurve) {
return $this->curve instanceof Ed25519 && self::$engines['libsodium'] && !isset($this->context) ?
'libsodium' : 'PHP';
2018-10-25 01:00:37 +00:00
}
return self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods()) ?
'OpenSSL' : 'PHP';
2018-10-25 01:00:37 +00:00
}
/**
* Returns the public key coordinates as a string
*
* Used by ECDH
*/
2022-06-04 15:31:21 +00:00
public function getEncodedCoordinates(): string
{
if ($this->curve instanceof MontgomeryCurve) {
return strrev($this->QA[0]->toBytes(true));
}
if ($this->curve instanceof TwistedEdwardsCurve) {
return $this->curve->encodePoint($this->QA);
}
return "\4" . $this->QA[0]->toBytes(true) . $this->QA[1]->toBytes(true);
}
2018-10-25 01:00:37 +00:00
/**
* Returns the parameters
*
* @param string $type optional
2022-06-04 15:31:21 +00:00
*@see self::getPublicKey()
2018-10-25 01:00:37 +00:00
*/
2022-06-04 15:31:21 +00:00
public function getParameters(string $type = 'PKCS1')
2018-10-25 01:00:37 +00:00
{
$type = self::validatePlugin('Keys', $type, 'saveParameters');
$key = $type::saveParameters($this->curve);
2018-10-25 01:00:37 +00:00
2019-06-28 00:10:40 +00:00
return EC::load($key, 'PKCS1')
->withHash($this->hash->getHash())
->withSignatureFormat($this->shortFormat);
2018-10-25 01:00:37 +00:00
}
/**
* Determines the signature padding mode
*
* Valid values are: ASN1, SSH2, Raw
2018-10-25 01:00:37 +00:00
*/
2022-06-04 15:31:21 +00:00
public function withSignatureFormat(string $format): EC
2018-10-25 01:00:37 +00:00
{
if ($this->curve instanceof MontgomeryCurve) {
throw new UnsupportedOperationException('Montgomery Curves cannot be used to create signatures');
}
$new = clone $this;
$new->shortFormat = $format;
$new->sigFormat = self::validatePlugin('Signature', $format);
return $new;
2018-10-25 01:00:37 +00:00
}
/**
* Returns the signature format currently being used
*/
2022-06-04 15:31:21 +00:00
public function getSignatureFormat(): string
{
2022-02-17 02:25:59 +00:00
return $this->shortFormat;
}
2018-10-25 01:00:37 +00:00
/**
* Sets the context
*
* Used by Ed25519 / Ed448.
*
2022-06-04 15:31:21 +00:00
* @param string|null $context optional
*@see self::verify()
* @see self::sign()
2018-10-25 01:00:37 +00:00
*/
2022-06-04 15:31:21 +00:00
public function withContext(string $context = null): EC
2018-10-25 01:00:37 +00:00
{
if (!$this->curve instanceof TwistedEdwardsCurve) {
throw new UnsupportedCurveException('Only Ed25519 and Ed448 support contexts');
}
$new = clone $this;
2018-10-25 01:00:37 +00:00
if (!isset($context)) {
$new->context = null;
return $new;
2018-10-25 01:00:37 +00:00
}
if (!is_string($context)) {
2018-12-31 15:06:12 +00:00
throw new \InvalidArgumentException('setContext expects a string');
2018-10-25 01:00:37 +00:00
}
if (strlen($context) > 255) {
2018-12-31 15:06:12 +00:00
throw new \LengthException('The context is supposed to be, at most, 255 bytes long');
2018-10-25 01:00:37 +00:00
}
$new->context = $context;
return $new;
2018-10-25 01:00:37 +00:00
}
/**
* Returns the signature format currently being used
*/
2022-06-04 15:31:21 +00:00
public function getContext(): string
{
2022-02-17 02:25:59 +00:00
return $this->context;
}
/**
* Determines which hashing function should be used
*/
2022-06-04 15:31:21 +00:00
public function withHash(string $hash): AsymmetricKey
{
if ($this->curve instanceof MontgomeryCurve) {
throw new UnsupportedOperationException('Montgomery Curves cannot be used to create signatures');
}
if ($this->curve instanceof Ed25519 && $hash != 'sha512') {
2018-12-31 15:06:12 +00:00
throw new UnsupportedAlgorithmException('Ed25519 only supports sha512 as a hash');
}
if ($this->curve instanceof Ed448 && $hash != 'shake256-912') {
2018-12-31 15:06:12 +00:00
throw new UnsupportedAlgorithmException('Ed448 only supports shake256 with a length of 114 bytes');
}
return parent::withHash($hash);
2018-10-25 01:00:37 +00:00
}
/**
* __toString() magic method
*
* @return string
*/
public function __toString()
{
if ($this->curve instanceof MontgomeryCurve) {
return '';
}
return parent::__toString();
}
2022-02-17 02:25:59 +00:00
}