[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/ -> PrivateKey.php (source)

   1  <?php
   2  
   3  /**
   4   * EC Private 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\EC;
  13  
  14  use phpseclib3\Common\Functions\Strings;
  15  use phpseclib3\Crypt\Common;
  16  use phpseclib3\Crypt\EC;
  17  use phpseclib3\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve;
  18  use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve;
  19  use phpseclib3\Crypt\EC\Curves\Curve25519;
  20  use phpseclib3\Crypt\EC\Curves\Ed25519;
  21  use phpseclib3\Crypt\EC\Formats\Keys\PKCS1;
  22  use phpseclib3\Crypt\EC\Formats\Signature\ASN1 as ASN1Signature;
  23  use phpseclib3\Crypt\Hash;
  24  use phpseclib3\Exception\UnsupportedOperationException;
  25  use phpseclib3\Math\BigInteger;
  26  
  27  /**
  28   * EC Private Key
  29   *
  30   * @author  Jim Wigginton <terrafrost@php.net>
  31   */
  32  final class PrivateKey extends EC implements Common\PrivateKey
  33  {
  34      use Common\Traits\PasswordProtected;
  35  
  36      /**
  37       * Private Key dA
  38       *
  39       * sign() converts this to a BigInteger so one might wonder why this is a FiniteFieldInteger instead of
  40       * a BigInteger. That's because a FiniteFieldInteger, when converted to a byte string, is null padded by
  41       * a certain amount whereas a BigInteger isn't.
  42       *
  43       * @var object
  44       */
  45      protected $dA;
  46  
  47      /**
  48       * @var string
  49       */
  50      protected $secret;
  51  
  52      /**
  53       * Multiplies an encoded point by the private key
  54       *
  55       * Used by ECDH
  56       *
  57       * @param string $coordinates
  58       * @return string
  59       */
  60      public function multiply($coordinates)
  61      {
  62          if ($this->curve instanceof MontgomeryCurve) {
  63              if ($this->curve instanceof Curve25519 && self::$engines['libsodium']) {
  64                  return sodium_crypto_scalarmult($this->dA->toBytes(), $coordinates);
  65              }
  66  
  67              $point = [$this->curve->convertInteger(new BigInteger(strrev($coordinates), 256))];
  68              $point = $this->curve->multiplyPoint($point, $this->dA);
  69              return strrev($point[0]->toBytes(true));
  70          }
  71          if (!$this->curve instanceof TwistedEdwardsCurve) {
  72              $coordinates = "\0$coordinates";
  73          }
  74          $point = PKCS1::extractPoint($coordinates, $this->curve);
  75          $point = $this->curve->multiplyPoint($point, $this->dA);
  76          if ($this->curve instanceof TwistedEdwardsCurve) {
  77              return $this->curve->encodePoint($point);
  78          }
  79          if (empty($point)) {
  80              throw new \RuntimeException('The infinity point is invalid');
  81          }
  82          return "\4" . $point[0]->toBytes(true) . $point[1]->toBytes(true);
  83      }
  84  
  85      /**
  86       * Create a signature
  87       *
  88       * @see self::verify()
  89       * @param string $message
  90       * @return mixed
  91       */
  92      public function sign($message)
  93      {
  94          if ($this->curve instanceof MontgomeryCurve) {
  95              throw new UnsupportedOperationException('Montgomery Curves cannot be used to create signatures');
  96          }
  97  
  98          $dA = $this->dA;
  99          $order = $this->curve->getOrder();
 100  
 101          $shortFormat = $this->shortFormat;
 102          $format = $this->sigFormat;
 103          if ($format === false) {
 104              return false;
 105          }
 106  
 107          if ($this->curve instanceof TwistedEdwardsCurve) {
 108              if ($this->curve instanceof Ed25519 && self::$engines['libsodium'] && !isset($this->context)) {
 109                  $result = sodium_crypto_sign_detached($message, $this->withPassword()->toString('libsodium'));
 110                  return $shortFormat == 'SSH2' ? Strings::packSSH2('ss', 'ssh-' . strtolower($this->getCurve()), $result) : $result;
 111              }
 112  
 113              // contexts (Ed25519ctx) are supported but prehashing (Ed25519ph) is not.
 114              // quoting https://tools.ietf.org/html/rfc8032#section-8.5 ,
 115              // "The Ed25519ph and Ed448ph variants ... SHOULD NOT be used"
 116              $A = $this->curve->encodePoint($this->QA);
 117              $curve = $this->curve;
 118              $hash = new Hash($curve::HASH);
 119  
 120              $secret = substr($hash->hash($this->secret), $curve::SIZE);
 121  
 122              if ($curve instanceof Ed25519) {
 123                  $dom = !isset($this->context) ? '' :
 124                      'SigEd25519 no Ed25519 collisions' . "\0" . chr(strlen($this->context)) . $this->context;
 125              } else {
 126                  $context = isset($this->context) ? $this->context : '';
 127                  $dom = 'SigEd448' . "\0" . chr(strlen($context)) . $context;
 128              }
 129              // SHA-512(dom2(F, C) || prefix || PH(M))
 130              $r = $hash->hash($dom . $secret . $message);
 131              $r = strrev($r);
 132              $r = new BigInteger($r, 256);
 133              list(, $r) = $r->divide($order);
 134              $R = $curve->multiplyPoint($curve->getBasePoint(), $r);
 135              $R = $curve->encodePoint($R);
 136              $k = $hash->hash($dom . $R . $A . $message);
 137              $k = strrev($k);
 138              $k = new BigInteger($k, 256);
 139              list(, $k) = $k->divide($order);
 140              $S = $k->multiply($dA)->add($r);
 141              list(, $S) = $S->divide($order);
 142              $S = str_pad(strrev($S->toBytes()), $curve::SIZE, "\0");
 143              return $shortFormat == 'SSH2' ? Strings::packSSH2('ss', 'ssh-' . strtolower($this->getCurve()), $R . $S) : $R . $S;
 144          }
 145  
 146          if (self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods())) {
 147              $signature = '';
 148              // altho PHP's OpenSSL bindings only supported EC key creation in PHP 7.1 they've long
 149              // supported signing / verification
 150              // we use specified curves to avoid issues with OpenSSL possibly not supporting a given named curve;
 151              // doing this may mean some curve-specific optimizations can't be used but idk if OpenSSL even
 152              // has curve-specific optimizations
 153              $result = openssl_sign($message, $signature, $this->withPassword()->toString('PKCS8', ['namedCurve' => false]), $this->hash->getHash());
 154  
 155              if ($result) {
 156                  if ($shortFormat == 'ASN1') {
 157                      return $signature;
 158                  }
 159  
 160                  extract(ASN1Signature::load($signature));
 161  
 162                  return $shortFormat == 'SSH2' ? $format::save($r, $s, $this->getCurve()) : $format::save($r, $s);
 163              }
 164          }
 165  
 166          $e = $this->hash->hash($message);
 167          $e = new BigInteger($e, 256);
 168  
 169          $Ln = $this->hash->getLength() - $order->getLength();
 170          $z = $Ln > 0 ? $e->bitwise_rightShift($Ln) : $e;
 171  
 172          while (true) {
 173              $k = BigInteger::randomRange(self::$one, $order->subtract(self::$one));
 174              list($x, $y) = $this->curve->multiplyPoint($this->curve->getBasePoint(), $k);
 175              $x = $x->toBigInteger();
 176              list(, $r) = $x->divide($order);
 177              if ($r->equals(self::$zero)) {
 178                  continue;
 179              }
 180              $kinv = $k->modInverse($order);
 181              $temp = $z->add($dA->multiply($r));
 182              $temp = $kinv->multiply($temp);
 183              list(, $s) = $temp->divide($order);
 184              if (!$s->equals(self::$zero)) {
 185                  break;
 186              }
 187          }
 188  
 189          // the following is an RFC6979 compliant implementation of deterministic ECDSA
 190          // it's unused because it's mainly intended for use when a good CSPRNG isn't
 191          // available. if phpseclib's CSPRNG isn't good then even key generation is
 192          // suspect
 193          /*
 194          // if this were actually being used it'd probably be better if this lived in load() and createKey()
 195          $this->q = $this->curve->getOrder();
 196          $dA = $this->dA->toBigInteger();
 197          $this->x = $dA;
 198  
 199          $h1 = $this->hash->hash($message);
 200          $k = $this->computek($h1);
 201          list($x, $y) = $this->curve->multiplyPoint($this->curve->getBasePoint(), $k);
 202          $x = $x->toBigInteger();
 203          list(, $r) = $x->divide($this->q);
 204          $kinv = $k->modInverse($this->q);
 205          $h1 = $this->bits2int($h1);
 206          $temp = $h1->add($dA->multiply($r));
 207          $temp = $kinv->multiply($temp);
 208          list(, $s) = $temp->divide($this->q);
 209          */
 210  
 211          return $shortFormat == 'SSH2' ? $format::save($r, $s, $this->getCurve()) : $format::save($r, $s);
 212      }
 213  
 214      /**
 215       * Returns the private key
 216       *
 217       * @param string $type
 218       * @param array $options optional
 219       * @return string
 220       */
 221      public function toString($type, array $options = [])
 222      {
 223          $type = self::validatePlugin('Keys', $type, 'savePrivateKey');
 224  
 225          return $type::savePrivateKey($this->dA, $this->curve, $this->QA, $this->secret, $this->password, $options);
 226      }
 227  
 228      /**
 229       * Returns the public key
 230       *
 231       * @see self::getPrivateKey()
 232       * @return mixed
 233       */
 234      public function getPublicKey()
 235      {
 236          $format = 'PKCS8';
 237          if ($this->curve instanceof MontgomeryCurve) {
 238              $format = 'MontgomeryPublic';
 239          }
 240  
 241          $type = self::validatePlugin('Keys', $format, 'savePublicKey');
 242  
 243          $key = $type::savePublicKey($this->curve, $this->QA);
 244          $key = EC::loadFormat($format, $key);
 245          if ($this->curve instanceof MontgomeryCurve) {
 246              return $key;
 247          }
 248          $key = $key
 249              ->withHash($this->hash->getHash())
 250              ->withSignatureFormat($this->shortFormat);
 251          if ($this->curve instanceof TwistedEdwardsCurve) {
 252              $key = $key->withContext($this->context);
 253          }
 254          return $key;
 255      }
 256  }