2019-05-19 20:35:29 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
/**
|
2019-06-28 00:10:40 +00:00
|
|
|
* EC Private Key
|
2019-05-19 20:35:29 +00:00
|
|
|
*
|
|
|
|
* @author Jim Wigginton <terrafrost@php.net>
|
|
|
|
* @copyright 2015 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);
|
|
|
|
|
2019-11-07 05:41:40 +00:00
|
|
|
namespace phpseclib3\Crypt\EC;
|
2019-05-19 20:35:29 +00:00
|
|
|
|
2022-01-30 15:34:42 +00:00
|
|
|
use phpseclib3\Common\Functions\Strings;
|
|
|
|
use phpseclib3\Crypt\Common;
|
2019-11-07 05:41:40 +00:00
|
|
|
use phpseclib3\Crypt\EC;
|
|
|
|
use phpseclib3\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve;
|
2022-01-30 15:34:42 +00:00
|
|
|
use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve;
|
2019-11-07 05:41:40 +00:00
|
|
|
use phpseclib3\Crypt\EC\Curves\Curve25519;
|
2022-01-30 15:34:42 +00:00
|
|
|
use phpseclib3\Crypt\EC\Curves\Ed25519;
|
2019-11-07 05:41:40 +00:00
|
|
|
use phpseclib3\Crypt\EC\Formats\Keys\PKCS1;
|
2022-01-30 15:34:42 +00:00
|
|
|
use phpseclib3\Crypt\EC\Formats\Signature\ASN1 as ASN1Signature;
|
|
|
|
use phpseclib3\Crypt\Hash;
|
2022-08-18 13:05:57 +00:00
|
|
|
use phpseclib3\Exception\RuntimeException;
|
2019-11-07 05:41:40 +00:00
|
|
|
use phpseclib3\Exception\UnsupportedOperationException;
|
2022-01-30 15:34:42 +00:00
|
|
|
use phpseclib3\Math\BigInteger;
|
2019-05-19 20:35:29 +00:00
|
|
|
|
|
|
|
/**
|
2019-06-28 00:10:40 +00:00
|
|
|
* EC Private Key
|
2019-05-19 20:35:29 +00:00
|
|
|
*
|
|
|
|
* @author Jim Wigginton <terrafrost@php.net>
|
|
|
|
*/
|
2023-02-05 23:44:51 +00:00
|
|
|
final class PrivateKey extends EC implements Common\PrivateKey
|
2019-05-19 20:35:29 +00:00
|
|
|
{
|
2019-06-26 04:20:37 +00:00
|
|
|
use Common\Traits\PasswordProtected;
|
2019-05-19 20:35:29 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Private Key dA
|
|
|
|
*
|
|
|
|
* sign() converts this to a BigInteger so one might wonder why this is a FiniteFieldInteger instead of
|
|
|
|
* a BigInteger. That's because a FiniteFieldInteger, when converted to a byte string, is null padded by
|
|
|
|
* a certain amount whereas a BigInteger isn't.
|
|
|
|
*
|
|
|
|
* @var object
|
|
|
|
*/
|
|
|
|
protected $dA;
|
|
|
|
|
2022-07-28 14:23:01 +00:00
|
|
|
/**
|
|
|
|
* @var string
|
|
|
|
*/
|
2022-08-24 02:26:31 +00:00
|
|
|
protected $secret;
|
2022-07-28 14:23:01 +00:00
|
|
|
|
2019-07-27 22:28:18 +00:00
|
|
|
/**
|
|
|
|
* Multiplies an encoded point by the private key
|
|
|
|
*
|
|
|
|
* Used by ECDH
|
|
|
|
*/
|
2022-06-04 15:31:21 +00:00
|
|
|
public function multiply(string $coordinates): string
|
2019-07-27 22:28:18 +00:00
|
|
|
{
|
|
|
|
if ($this->curve instanceof MontgomeryCurve) {
|
|
|
|
if ($this->curve instanceof Curve25519 && self::$engines['libsodium']) {
|
|
|
|
return sodium_crypto_scalarmult($this->dA->toBytes(), $coordinates);
|
|
|
|
}
|
|
|
|
|
|
|
|
$point = [$this->curve->convertInteger(new BigInteger(strrev($coordinates), 256))];
|
|
|
|
$point = $this->curve->multiplyPoint($point, $this->dA);
|
|
|
|
return strrev($point[0]->toBytes(true));
|
|
|
|
}
|
|
|
|
if (!$this->curve instanceof TwistedEdwardsCurve) {
|
|
|
|
$coordinates = "\0$coordinates";
|
|
|
|
}
|
|
|
|
$point = PKCS1::extractPoint($coordinates, $this->curve);
|
|
|
|
$point = $this->curve->multiplyPoint($point, $this->dA);
|
|
|
|
if ($this->curve instanceof TwistedEdwardsCurve) {
|
|
|
|
return $this->curve->encodePoint($point);
|
|
|
|
}
|
|
|
|
if (empty($point)) {
|
2022-08-18 13:05:57 +00:00
|
|
|
throw new RuntimeException('The infinity point is invalid');
|
2019-07-27 22:28:18 +00:00
|
|
|
}
|
|
|
|
return "\4" . $point[0]->toBytes(true) . $point[1]->toBytes(true);
|
|
|
|
}
|
|
|
|
|
2019-05-19 20:35:29 +00:00
|
|
|
/**
|
|
|
|
* Create a signature
|
|
|
|
*
|
|
|
|
* @see self::verify()
|
|
|
|
* @param string $message
|
|
|
|
*/
|
|
|
|
public function sign($message)
|
|
|
|
{
|
2019-07-27 22:28:18 +00:00
|
|
|
if ($this->curve instanceof MontgomeryCurve) {
|
|
|
|
throw new UnsupportedOperationException('Montgomery Curves cannot be used to create signatures');
|
|
|
|
}
|
|
|
|
|
2021-11-15 21:05:37 +00:00
|
|
|
$dA = $this->dA;
|
2019-05-19 20:35:29 +00:00
|
|
|
$order = $this->curve->getOrder();
|
|
|
|
|
2019-11-23 06:58:12 +00:00
|
|
|
$shortFormat = $this->shortFormat;
|
2020-04-06 12:53:42 +00:00
|
|
|
$format = $this->sigFormat;
|
2019-11-23 06:58:12 +00:00
|
|
|
if ($format === false) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-05-19 20:35:29 +00:00
|
|
|
if ($this->curve instanceof TwistedEdwardsCurve) {
|
|
|
|
if ($this->curve instanceof Ed25519 && self::$engines['libsodium'] && !isset($this->context)) {
|
2022-02-04 16:38:47 +00:00
|
|
|
$result = sodium_crypto_sign_detached($message, $this->withPassword()->toString('libsodium'));
|
2019-11-23 06:58:12 +00:00
|
|
|
return $shortFormat == 'SSH2' ? Strings::packSSH2('ss', 'ssh-' . strtolower($this->getCurve()), $result) : $result;
|
2019-05-19 20:35:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// contexts (Ed25519ctx) are supported but prehashing (Ed25519ph) is not.
|
|
|
|
// quoting https://tools.ietf.org/html/rfc8032#section-8.5 ,
|
|
|
|
// "The Ed25519ph and Ed448ph variants ... SHOULD NOT be used"
|
|
|
|
$A = $this->curve->encodePoint($this->QA);
|
|
|
|
$curve = $this->curve;
|
|
|
|
$hash = new Hash($curve::HASH);
|
|
|
|
|
2022-07-28 14:23:01 +00:00
|
|
|
$secret = substr($hash->hash($this->secret), $curve::SIZE);
|
2019-05-19 20:35:29 +00:00
|
|
|
|
|
|
|
if ($curve instanceof Ed25519) {
|
|
|
|
$dom = !isset($this->context) ? '' :
|
|
|
|
'SigEd25519 no Ed25519 collisions' . "\0" . chr(strlen($this->context)) . $this->context;
|
|
|
|
} else {
|
2022-06-04 15:31:21 +00:00
|
|
|
$context = $this->context ?? '';
|
2019-05-19 20:35:29 +00:00
|
|
|
$dom = 'SigEd448' . "\0" . chr(strlen($context)) . $context;
|
|
|
|
}
|
|
|
|
// SHA-512(dom2(F, C) || prefix || PH(M))
|
|
|
|
$r = $hash->hash($dom . $secret . $message);
|
|
|
|
$r = strrev($r);
|
|
|
|
$r = new BigInteger($r, 256);
|
2022-06-04 15:31:21 +00:00
|
|
|
[, $r] = $r->divide($order);
|
2021-11-15 21:05:37 +00:00
|
|
|
$R = $curve->multiplyPoint($curve->getBasePoint(), $r);
|
2019-05-19 20:35:29 +00:00
|
|
|
$R = $curve->encodePoint($R);
|
|
|
|
$k = $hash->hash($dom . $R . $A . $message);
|
|
|
|
$k = strrev($k);
|
|
|
|
$k = new BigInteger($k, 256);
|
2022-06-04 15:31:21 +00:00
|
|
|
[, $k] = $k->divide($order);
|
2019-05-19 20:35:29 +00:00
|
|
|
$S = $k->multiply($dA)->add($r);
|
2022-06-04 15:31:21 +00:00
|
|
|
[, $S] = $S->divide($order);
|
2019-05-19 20:35:29 +00:00
|
|
|
$S = str_pad(strrev($S->toBytes()), $curve::SIZE, "\0");
|
2019-11-23 06:58:12 +00:00
|
|
|
return $shortFormat == 'SSH2' ? Strings::packSSH2('ss', 'ssh-' . strtolower($this->getCurve()), $R . $S) : $R . $S;
|
2019-05-19 20:35:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods())) {
|
|
|
|
$signature = '';
|
2019-06-28 00:10:40 +00:00
|
|
|
// altho PHP's OpenSSL bindings only supported EC key creation in PHP 7.1 they've long
|
2019-05-19 20:35:29 +00:00
|
|
|
// supported signing / verification
|
2019-06-03 13:16:13 +00:00
|
|
|
// we use specified curves to avoid issues with OpenSSL possibly not supporting a given named curve;
|
|
|
|
// doing this may mean some curve-specific optimizations can't be used but idk if OpenSSL even
|
|
|
|
// has curve-specific optimizations
|
|
|
|
$result = openssl_sign($message, $signature, $this->toString('PKCS8', ['namedCurve' => false]), $this->hash->getHash());
|
2019-05-19 20:35:29 +00:00
|
|
|
|
|
|
|
if ($result) {
|
|
|
|
if ($shortFormat == 'ASN1') {
|
|
|
|
return $signature;
|
|
|
|
}
|
|
|
|
|
|
|
|
extract(ASN1Signature::load($signature));
|
|
|
|
|
|
|
|
return $shortFormat == 'SSH2' ? $format::save($r, $s, $this->getCurve()) : $format::save($r, $s);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$e = $this->hash->hash($message);
|
|
|
|
$e = new BigInteger($e, 256);
|
|
|
|
|
|
|
|
$Ln = $this->hash->getLength() - $order->getLength();
|
|
|
|
$z = $Ln > 0 ? $e->bitwise_rightShift($Ln) : $e;
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
$k = BigInteger::randomRange(self::$one, $order->subtract(self::$one));
|
2022-06-04 15:31:21 +00:00
|
|
|
[$x, $y] = $this->curve->multiplyPoint($this->curve->getBasePoint(), $k);
|
2019-05-19 20:35:29 +00:00
|
|
|
$x = $x->toBigInteger();
|
2022-06-04 15:31:21 +00:00
|
|
|
[, $r] = $x->divide($order);
|
2019-05-19 20:35:29 +00:00
|
|
|
if ($r->equals(self::$zero)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
$kinv = $k->modInverse($order);
|
|
|
|
$temp = $z->add($dA->multiply($r));
|
|
|
|
$temp = $kinv->multiply($temp);
|
2022-06-04 15:31:21 +00:00
|
|
|
[, $s] = $temp->divide($order);
|
2019-05-19 20:35:29 +00:00
|
|
|
if (!$s->equals(self::$zero)) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// the following is an RFC6979 compliant implementation of deterministic ECDSA
|
|
|
|
// it's unused because it's mainly intended for use when a good CSPRNG isn't
|
|
|
|
// available. if phpseclib's CSPRNG isn't good then even key generation is
|
|
|
|
// suspect
|
|
|
|
/*
|
|
|
|
// if this were actually being used it'd probably be better if this lived in load() and createKey()
|
|
|
|
$this->q = $this->curve->getOrder();
|
|
|
|
$dA = $this->dA->toBigInteger();
|
|
|
|
$this->x = $dA;
|
|
|
|
|
|
|
|
$h1 = $this->hash->hash($message);
|
|
|
|
$k = $this->computek($h1);
|
2021-11-15 21:05:37 +00:00
|
|
|
list($x, $y) = $this->curve->multiplyPoint($this->curve->getBasePoint(), $k);
|
2019-05-19 20:35:29 +00:00
|
|
|
$x = $x->toBigInteger();
|
|
|
|
list(, $r) = $x->divide($this->q);
|
|
|
|
$kinv = $k->modInverse($this->q);
|
|
|
|
$h1 = $this->bits2int($h1);
|
|
|
|
$temp = $h1->add($dA->multiply($r));
|
|
|
|
$temp = $kinv->multiply($temp);
|
|
|
|
list(, $s) = $temp->divide($this->q);
|
|
|
|
*/
|
|
|
|
|
|
|
|
return $shortFormat == 'SSH2' ? $format::save($r, $s, $this->getCurve()) : $format::save($r, $s);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the private key
|
|
|
|
*
|
2019-06-01 18:23:11 +00:00
|
|
|
* @param array $options optional
|
2019-05-19 20:35:29 +00:00
|
|
|
*/
|
2022-06-04 15:31:21 +00:00
|
|
|
public function toString(string $type, array $options = []): string
|
2019-05-19 20:35:29 +00:00
|
|
|
{
|
|
|
|
$type = self::validatePlugin('Keys', $type, 'savePrivateKey');
|
|
|
|
|
2022-07-28 14:56:00 +00:00
|
|
|
return $type::savePrivateKey($this->dA, $this->curve, $this->QA, $this->secret, $this->password, $options);
|
2019-05-19 20:35:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the public key
|
|
|
|
*
|
|
|
|
* @see self::getPrivateKey()
|
|
|
|
*/
|
|
|
|
public function getPublicKey()
|
|
|
|
{
|
2019-07-27 22:28:18 +00:00
|
|
|
$format = 'PKCS8';
|
|
|
|
if ($this->curve instanceof MontgomeryCurve) {
|
2019-08-08 13:01:49 +00:00
|
|
|
$format = 'MontgomeryPublic';
|
2019-07-27 22:28:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
$type = self::validatePlugin('Keys', $format, 'savePublicKey');
|
2019-05-19 20:35:29 +00:00
|
|
|
|
|
|
|
$key = $type::savePublicKey($this->curve, $this->QA);
|
2019-07-27 22:28:18 +00:00
|
|
|
$key = EC::loadFormat($format, $key);
|
|
|
|
if ($this->curve instanceof MontgomeryCurve) {
|
|
|
|
return $key;
|
|
|
|
}
|
|
|
|
$key = $key
|
2019-05-19 20:35:29 +00:00
|
|
|
->withHash($this->hash->getHash())
|
|
|
|
->withSignatureFormat($this->shortFormat);
|
|
|
|
if ($this->curve instanceof TwistedEdwardsCurve) {
|
|
|
|
$key = $key->withContext($this->context);
|
|
|
|
}
|
|
|
|
return $key;
|
|
|
|
}
|
|
|
|
}
|