1<?php 2 3/** 4 * EC Private Key 5 * 6 * @category Crypt 7 * @package EC 8 * @author Jim Wigginton <terrafrost@php.net> 9 * @copyright 2015 Jim Wigginton 10 * @license http://www.opensource.org/licenses/mit-license.html MIT License 11 * @link http://phpseclib.sourceforge.net 12 */ 13 14namespace phpseclib3\Crypt\EC; 15 16use phpseclib3\Common\Functions\Strings; 17use phpseclib3\Crypt\Common; 18use phpseclib3\Crypt\EC; 19use phpseclib3\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve; 20use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve; 21use phpseclib3\Crypt\EC\Curves\Curve25519; 22use phpseclib3\Crypt\EC\Curves\Ed25519; 23use phpseclib3\Crypt\EC\Formats\Keys\PKCS1; 24use phpseclib3\Crypt\EC\Formats\Signature\ASN1 as ASN1Signature; 25use phpseclib3\Crypt\Hash; 26use phpseclib3\Exception\UnsupportedOperationException; 27use phpseclib3\Math\BigInteger; 28 29/** 30 * EC Private Key 31 * 32 * @package EC 33 * @author Jim Wigginton <terrafrost@php.net> 34 * @access public 35 */ 36class PrivateKey extends EC implements Common\PrivateKey 37{ 38 use Common\Traits\PasswordProtected; 39 40 /** 41 * Private Key dA 42 * 43 * sign() converts this to a BigInteger so one might wonder why this is a FiniteFieldInteger instead of 44 * a BigInteger. That's because a FiniteFieldInteger, when converted to a byte string, is null padded by 45 * a certain amount whereas a BigInteger isn't. 46 * 47 * @var object 48 */ 49 protected $dA; 50 51 /** 52 * Multiplies an encoded point by the private key 53 * 54 * Used by ECDH 55 * 56 * @param string $coordinates 57 * @return string 58 */ 59 public function multiply($coordinates) 60 { 61 if ($this->curve instanceof MontgomeryCurve) { 62 if ($this->curve instanceof Curve25519 && self::$engines['libsodium']) { 63 return sodium_crypto_scalarmult($this->dA->toBytes(), $coordinates); 64 } 65 66 $point = [$this->curve->convertInteger(new BigInteger(strrev($coordinates), 256))]; 67 $point = $this->curve->multiplyPoint($point, $this->dA); 68 return strrev($point[0]->toBytes(true)); 69 } 70 if (!$this->curve instanceof TwistedEdwardsCurve) { 71 $coordinates = "\0$coordinates"; 72 } 73 $point = PKCS1::extractPoint($coordinates, $this->curve); 74 $point = $this->curve->multiplyPoint($point, $this->dA); 75 if ($this->curve instanceof TwistedEdwardsCurve) { 76 return $this->curve->encodePoint($point); 77 } 78 if (empty($point)) { 79 throw new \RuntimeException('The infinity point is invalid'); 80 } 81 return "\4" . $point[0]->toBytes(true) . $point[1]->toBytes(true); 82 } 83 84 /** 85 * Create a signature 86 * 87 * @see self::verify() 88 * @access public 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->dA->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->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->password, $options); 226 } 227 228 /** 229 * Returns the public key 230 * 231 * @see self::getPrivateKey() 232 * @access public 233 * @return mixed 234 */ 235 public function getPublicKey() 236 { 237 $format = 'PKCS8'; 238 if ($this->curve instanceof MontgomeryCurve) { 239 $format = 'MontgomeryPublic'; 240 } 241 242 $type = self::validatePlugin('Keys', $format, 'savePublicKey'); 243 244 $key = $type::savePublicKey($this->curve, $this->QA); 245 $key = EC::loadFormat($format, $key); 246 if ($this->curve instanceof MontgomeryCurve) { 247 return $key; 248 } 249 $key = $key 250 ->withHash($this->hash->getHash()) 251 ->withSignatureFormat($this->shortFormat); 252 if ($this->curve instanceof TwistedEdwardsCurve) { 253 $key = $key->withContext($this->context); 254 } 255 return $key; 256 } 257} 258