1<?php 2 3/** 4 * Pure-PHP (EC)DH implementation 5 * 6 * PHP version 5 7 * 8 * Here's an example of how to compute a shared secret with this library: 9 * <code> 10 * <?php 11 * include 'vendor/autoload.php'; 12 * 13 * $ourPrivate = \phpseclib3\Crypt\DH::createKey(); 14 * $secret = DH::computeSecret($ourPrivate, $theirPublic); 15 * 16 * ?> 17 * </code> 18 * 19 * @author Jim Wigginton <terrafrost@php.net> 20 * @copyright 2016 Jim Wigginton 21 * @license http://www.opensource.org/licenses/mit-license.html MIT License 22 * @link http://phpseclib.sourceforge.net 23 */ 24 25namespace phpseclib3\Crypt; 26 27use phpseclib3\Crypt\Common\AsymmetricKey; 28use phpseclib3\Crypt\DH\Parameters; 29use phpseclib3\Crypt\DH\PrivateKey; 30use phpseclib3\Crypt\DH\PublicKey; 31use phpseclib3\Crypt\EC\Curves\Curve25519; 32use phpseclib3\Crypt\EC\Curves\Curve448; 33use phpseclib3\Crypt\EC\Formats\Keys\PKCS1; 34use phpseclib3\Exception\BadConfigurationException; 35use phpseclib3\Exception\NoKeyLoadedException; 36use phpseclib3\Exception\UnsupportedOperationException; 37use phpseclib3\File\ASN1; 38use phpseclib3\File\ASN1\Maps; 39use phpseclib3\Math\BigInteger; 40 41/** 42 * Pure-PHP (EC)DH implementation 43 * 44 * @author Jim Wigginton <terrafrost@php.net> 45 */ 46abstract class DH extends AsymmetricKey 47{ 48 /** 49 * Algorithm Name 50 * 51 * @var string 52 */ 53 const ALGORITHM = 'DH'; 54 55 /** 56 * DH prime 57 * 58 * @var BigInteger 59 */ 60 protected $prime; 61 62 /** 63 * DH Base 64 * 65 * Prime divisor of p-1 66 * 67 * @var BigInteger 68 */ 69 protected $base; 70 71 /** 72 * Public Key 73 * 74 * @var BigInteger 75 */ 76 protected $publicKey; 77 78 /** 79 * Create DH parameters 80 * 81 * This method is a bit polymorphic. It can take any of the following: 82 * - two BigInteger's (prime and base) 83 * - an integer representing the size of the prime in bits (the base is assumed to be 2) 84 * - a string (eg. diffie-hellman-group14-sha1) 85 * 86 * @return Parameters 87 */ 88 public static function createParameters(...$args) 89 { 90 $class = new \ReflectionClass(static::class); 91 if ($class->isFinal()) { 92 throw new \RuntimeException('createParameters() should not be called from final classes (' . static::class . ')'); 93 } 94 95 $params = new Parameters(); 96 if (count($args) == 2 && $args[0] instanceof BigInteger && $args[1] instanceof BigInteger) { 97 //if (!$args[0]->isPrime()) { 98 // throw new \InvalidArgumentException('The first parameter should be a prime number'); 99 //} 100 $params->prime = $args[0]; 101 $params->base = $args[1]; 102 return $params; 103 } elseif (count($args) == 1 && is_numeric($args[0])) { 104 $params->prime = BigInteger::randomPrime($args[0]); 105 $params->base = new BigInteger(2); 106 return $params; 107 } elseif (count($args) != 1 || !is_string($args[0])) { 108 throw new \InvalidArgumentException('Valid parameters are either: two BigInteger\'s (prime and base), a single integer (the length of the prime; base is assumed to be 2) or a string'); 109 } 110 switch ($args[0]) { 111 // see http://tools.ietf.org/html/rfc2409#section-6.2 and 112 // http://tools.ietf.org/html/rfc2412, appendex E 113 case 'diffie-hellman-group1-sha1': 114 $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' . 115 '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' . 116 '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' . 117 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF'; 118 break; 119 // see http://tools.ietf.org/html/rfc3526#section-3 120 case 'diffie-hellman-group14-sha1': // 2048-bit MODP Group 121 case 'diffie-hellman-group14-sha256': 122 $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' . 123 '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' . 124 '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' . 125 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' . 126 '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' . 127 '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' . 128 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' . 129 '3995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF'; 130 break; 131 // see https://tools.ietf.org/html/rfc3526#section-4 132 case 'diffie-hellman-group15-sha512': // 3072-bit MODP Group 133 $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' . 134 '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' . 135 '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' . 136 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' . 137 '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' . 138 '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' . 139 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' . 140 '3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33' . 141 'A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7' . 142 'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864' . 143 'D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2' . 144 '08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF'; 145 break; 146 // see https://tools.ietf.org/html/rfc3526#section-5 147 case 'diffie-hellman-group16-sha512': // 4096-bit MODP Group 148 $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' . 149 '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' . 150 '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' . 151 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' . 152 '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' . 153 '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' . 154 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' . 155 '3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33' . 156 'A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7' . 157 'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864' . 158 'D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2' . 159 '08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7' . 160 '88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8' . 161 'DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2' . 162 '233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9' . 163 '93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF'; 164 break; 165 // see https://tools.ietf.org/html/rfc3526#section-6 166 case 'diffie-hellman-group17-sha512': // 6144-bit MODP Group 167 $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' . 168 '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' . 169 '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' . 170 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' . 171 '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' . 172 '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' . 173 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' . 174 '3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33' . 175 'A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7' . 176 'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864' . 177 'D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2' . 178 '08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7' . 179 '88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8' . 180 'DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2' . 181 '233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9' . 182 '93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026' . 183 'C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AE' . 184 'B06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B' . 185 'DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92EC' . 186 'F032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E' . 187 '59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA' . 188 'CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76' . 189 'F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468' . 190 '043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DCC4024FFFFFFFFFFFFFFFF'; 191 break; 192 // see https://tools.ietf.org/html/rfc3526#section-7 193 case 'diffie-hellman-group18-sha512': // 8192-bit MODP Group 194 $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' . 195 '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' . 196 '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' . 197 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' . 198 '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' . 199 '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' . 200 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' . 201 '3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33' . 202 'A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7' . 203 'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864' . 204 'D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2' . 205 '08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7' . 206 '88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8' . 207 'DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2' . 208 '233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9' . 209 '93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026' . 210 'C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AE' . 211 'B06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B' . 212 'DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92EC' . 213 'F032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E' . 214 '59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA' . 215 'CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76' . 216 'F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468' . 217 '043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E4' . 218 '38777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED' . 219 '2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652D' . 220 'E3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B' . 221 '4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A6' . 222 '6D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851D' . 223 'F9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F92' . 224 '4009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA' . 225 '9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF'; 226 break; 227 default: 228 throw new \InvalidArgumentException('Invalid named prime provided'); 229 } 230 231 $params->prime = new BigInteger($prime, 16); 232 $params->base = new BigInteger(2); 233 234 return $params; 235 } 236 237 /** 238 * Create public / private key pair. 239 * 240 * The rationale for the second parameter is described in http://tools.ietf.org/html/rfc4419#section-6.2 : 241 * 242 * "To increase the speed of the key exchange, both client and server may 243 * reduce the size of their private exponents. It should be at least 244 * twice as long as the key material that is generated from the shared 245 * secret. For more details, see the paper by van Oorschot and Wiener 246 * [VAN-OORSCHOT]." 247 * 248 * $length is in bits 249 * 250 * @param Parameters $params 251 * @param int $length optional 252 * @return PrivateKey 253 */ 254 public static function createKey(Parameters $params, $length = 0) 255 { 256 $class = new \ReflectionClass(static::class); 257 if ($class->isFinal()) { 258 throw new \RuntimeException('createKey() should not be called from final classes (' . static::class . ')'); 259 } 260 261 $one = new BigInteger(1); 262 if ($length) { 263 $max = $one->bitwise_leftShift($length); 264 $max = $max->subtract($one); 265 } else { 266 $max = $params->prime->subtract($one); 267 } 268 269 $key = new PrivateKey(); 270 $key->prime = $params->prime; 271 $key->base = $params->base; 272 $key->privateKey = BigInteger::randomRange($one, $max); 273 $key->publicKey = $key->base->powMod($key->privateKey, $key->prime); 274 return $key; 275 } 276 277 /** 278 * Compute Shared Secret 279 * 280 * @param PrivateKey|EC $private 281 * @param PublicKey|BigInteger|string $public 282 * @return mixed 283 */ 284 public static function computeSecret($private, $public) 285 { 286 if ($private instanceof PrivateKey) { // DH\PrivateKey 287 switch (true) { 288 case $public instanceof PublicKey: 289 if (!$private->prime->equals($public->prime) || !$private->base->equals($public->base)) { 290 throw new \InvalidArgumentException('The public and private key do not share the same prime and / or base numbers'); 291 } 292 return $public->publicKey->powMod($private->privateKey, $private->prime)->toBytes(true); 293 case is_string($public): 294 $public = new BigInteger($public, -256); 295 // fall-through 296 case $public instanceof BigInteger: 297 return $public->powMod($private->privateKey, $private->prime)->toBytes(true); 298 default: 299 throw new \InvalidArgumentException('$public needs to be an instance of DH\PublicKey, a BigInteger or a string'); 300 } 301 } 302 303 if ($private instanceof EC\PrivateKey) { 304 $privateCurve = $private->getCurve(); 305 switch (true) { 306 case $public instanceof EC\PublicKey: 307 if ($privateCurve !== $public->getCurve()) { 308 throw new \InvalidArgumentException("The public key curve (" . $public->getCurve() . ") and private key curve ($privateCurve) need to match"); 309 } 310 $orig = $public; 311 $public = $public->getEncodedCoordinates(); 312 // fall-through 313 case is_string($public): 314 $forcedEngine = EC::getForcedEngine(); 315 if ($forcedEngine === 'libsodium' && $privateCurve !== 'Curve25519') { 316 throw new BadConfigurationException('Engine libsodium is forced but can only used with Curve25519 for ECDH'); 317 } 318 if (!isset($forcedEngine) || $forcedEngine === 'OpenSSL') { 319 // PHP 7.3.0 introduced the openssl_pkey_derive() function 320 // openssl_dh_computee_key() has been around since PHP 5.3.0+ BUT it did not support ECDH 321 // until PHP 8.1.0 / OpenSSL 3.0.0 322 if ($forcedEngine === 'OpenSSL' && !function_exists('openssl_pkey_derive')) { 323 throw new BadConfigurationException('Engine OpenSSL is forced but unsupported for ECDH'); 324 } 325 if (function_exists('openssl_pkey_derive')) { 326 $privateStr = (string) $private->withPassword(); 327 $publicStr = (string) (isset($orig) ? $orig : EC::convertPointToPublicKey($private->getCurve(), $public)); 328 $result = openssl_pkey_derive($publicStr, $privateStr); 329 if ($result) { 330 return $result; 331 } 332 if ($forcedEngine === 'OpenSSL') { 333 // i suppose we _could_ try openssl_dh_compute_key() at this point 334 // quoting https://www.php.net/openssl-dh-compute-key "ECDH is only supported as of PHP 8.1.0 and OpenSSL 3.0.0". ie. 335 // PHP_VERSION_ID >= 80100 && OPENSSL_VERSION_NUMBER >= 0x3000000f 336 // but i think that's overkill. if openssl_pkey_derive() doesn't work it seems doubtful to me that openssl_dh_compute_key() would 337 throw new BadConfigurationException('Engine OpenSSL is forced but was unable to perform ECDH because of ' . openssl_error_string()); 338 } 339 } 340 } 341 $curveName = $private->getCurve(); 342 $isMontgomeryCurve = $curveName == 'Curve25519' || $curveName == 'Curve448'; 343 if (!$isMontgomeryCurve) { 344 $public = EC::convertPointToPublicKey($curveName, $public, false); 345 } 346 $point = $private->multiply($public); 347 // according to https://www.secg.org/sec1-v2.pdf#page=33 only X is returned 348 $secret = $isMontgomeryCurve ? $point : substr($point, 1, (strlen($point) - 1) >> 1); 349 /* 350 if (($secret[0] & "\x80") === "\x80") { 351 $secret = "\0$secret"; 352 } 353 */ 354 return $secret; 355 default: 356 throw new \InvalidArgumentException('$public needs to be an instance of EC\PublicKey or a string (an encoded coordinate)'); 357 } 358 } 359 } 360 361 /** 362 * Load the key 363 * 364 * @param string $key 365 * @param string $password optional 366 * @return AsymmetricKey 367 */ 368 public static function load($key, $password = false) 369 { 370 try { 371 return EC::load($key, $password); 372 } catch (NoKeyLoadedException $e) { 373 } 374 375 return parent::load($key, $password); 376 } 377 378 /** 379 * OnLoad Handler 380 * 381 * @return bool 382 */ 383 protected static function onLoad(array $components) 384 { 385 if (!isset($components['privateKey']) && !isset($components['publicKey'])) { 386 $new = new Parameters(); 387 } else { 388 $new = isset($components['privateKey']) ? 389 new PrivateKey() : 390 new PublicKey(); 391 } 392 393 $new->prime = $components['prime']; 394 $new->base = $components['base']; 395 396 if (isset($components['privateKey'])) { 397 $new->privateKey = $components['privateKey']; 398 } 399 if (isset($components['publicKey'])) { 400 $new->publicKey = $components['publicKey']; 401 } 402 403 return $new; 404 } 405 406 /** 407 * Determines which hashing function should be used 408 * 409 * @param string $hash 410 */ 411 public function withHash($hash) 412 { 413 throw new UnsupportedOperationException('DH does not use a hash algorithm'); 414 } 415 416 /** 417 * Returns the hash algorithm currently being used 418 * 419 */ 420 public function getHash() 421 { 422 throw new UnsupportedOperationException('DH does not use a hash algorithm'); 423 } 424 425 /** 426 * Returns the parameters 427 * 428 * A public / private key is only returned if the currently loaded "key" contains an x or y 429 * value. 430 * 431 * @see self::getPublicKey() 432 * @return mixed 433 */ 434 public function getParameters() 435 { 436 $type = DH::validatePlugin('Keys', 'PKCS1', 'saveParameters'); 437 438 $key = $type::saveParameters($this->prime, $this->base); 439 return DH::load($key, 'PKCS1'); 440 } 441} 442