Subversion Repositories oidplus

Rev

Rev 1101 | Blame | Compare with Previous | Last modification | View Log | RSS feed

  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. }
  257.