From 1e7453b585e1092549ccc8ba45d7286ced908f21 Mon Sep 17 00:00:00 2001 From: terrafrost Date: Sat, 27 Jul 2019 17:28:18 -0500 Subject: [PATCH] add a new diffie-hellman key exchange class --- phpseclib/Crypt/Common/AsymmetricKey.php | 17 +- phpseclib/Crypt/DH.php | 401 ++++++++++++++++++ phpseclib/Crypt/DH/Formats/Keys/PKCS1.php | 83 ++++ phpseclib/Crypt/DH/Formats/Keys/PKCS8.php | 156 +++++++ phpseclib/Crypt/DH/Parameters.php | 40 ++ phpseclib/Crypt/DH/PrivateKey.php | 82 ++++ phpseclib/Crypt/DH/PublicKey.php | 53 +++ phpseclib/Crypt/EC.php | 60 ++- phpseclib/Crypt/EC/BaseCurves/Montgomery.php | 283 ++++++++++++ .../Crypt/EC/BaseCurves/TwistedEdwards.php | 17 - phpseclib/Crypt/EC/Curves/Curve25519.php | 64 +++ .../EC/Formats/Keys/Curve25519Private.php | 92 ++++ .../EC/Formats/Keys/Curve25519Public.php | 68 +++ phpseclib/Crypt/EC/Formats/Keys/PKCS1.php | 7 +- phpseclib/Crypt/EC/Formats/Keys/PKCS8.php | 10 + phpseclib/Crypt/EC/Formats/Keys/XML.php | 5 +- phpseclib/Crypt/EC/PrivateKey.php | 57 ++- phpseclib/Crypt/EC/PublicKey.php | 7 +- phpseclib/File/ASN1/Maps/DHParameter.php | 42 ++ phpseclib/Net/SSH2.php | 139 +++--- tests/Unit/Crypt/DHTest.php | 203 +++++++++ tests/Unit/Crypt/EC/CurveTest.php | 6 + 22 files changed, 1778 insertions(+), 114 deletions(-) create mode 100644 phpseclib/Crypt/DH.php create mode 100644 phpseclib/Crypt/DH/Formats/Keys/PKCS1.php create mode 100644 phpseclib/Crypt/DH/Formats/Keys/PKCS8.php create mode 100644 phpseclib/Crypt/DH/Parameters.php create mode 100644 phpseclib/Crypt/DH/PrivateKey.php create mode 100644 phpseclib/Crypt/DH/PublicKey.php create mode 100644 phpseclib/Crypt/EC/BaseCurves/Montgomery.php create mode 100644 phpseclib/Crypt/EC/Curves/Curve25519.php create mode 100644 phpseclib/Crypt/EC/Formats/Keys/Curve25519Private.php create mode 100644 phpseclib/Crypt/EC/Formats/Keys/Curve25519Public.php create mode 100644 phpseclib/File/ASN1/Maps/DHParameter.php create mode 100644 tests/Unit/Crypt/DHTest.php diff --git a/phpseclib/Crypt/Common/AsymmetricKey.php b/phpseclib/Crypt/Common/AsymmetricKey.php index 4bd532e4..2970ab42 100644 --- a/phpseclib/Crypt/Common/AsymmetricKey.php +++ b/phpseclib/Crypt/Common/AsymmetricKey.php @@ -81,13 +81,13 @@ abstract class AsymmetricKey private static $plugins = []; /** - * Supported plugins (original case) + * Invisible plugins * * @see self::initialize_static_variables() * @var array * @access private */ - private static $origPlugins = []; + private static $invisiblePlugins = []; /** * Supported signature formats (lower case) @@ -137,7 +137,7 @@ abstract class AsymmetricKey } self::loadPlugins('Keys'); - if (static::ALGORITHM != 'RSA') { + if (static::ALGORITHM != 'RSA' && static::ALGORITHM != 'DH') { self::loadPlugins('Signature'); } } @@ -155,6 +155,9 @@ abstract class AsymmetricKey $components = false; foreach (self::$plugins[static::ALGORITHM]['Keys'] as $format) { + if (isset(self::$invisiblePlugins[static::ALGORITHM]) && in_array($format, self::$invisiblePlugins[static::ALGORITHM])) { + continue; + } try { $components = $format::load($key, $password); } catch (\Exception $e) { @@ -252,7 +255,9 @@ abstract class AsymmetricKey continue; } self::$plugins[static::ALGORITHM][$format][strtolower($name)] = $type; - self::$origPlugins[static::ALGORITHM][$format][] = $name; + if ($reflect->hasConstant('IS_INVISIBLE')) { + self::$invisiblePlugins[static::ALGORITHM][] = $type; + } } } } @@ -289,7 +294,9 @@ abstract class AsymmetricKey $meta = new \ReflectionClass($fullname); $shortname = $meta->getShortName(); self::$plugins[static::ALGORITHM]['Keys'][strtolower($shortname)] = $fullname; - self::$origPlugins[static::ALGORITHM]['Keys'][] = $shortname; + if ($meta->hasConstant('IS_INVISIBLE')) { + self::$invisiblePlugins[static::ALGORITHM] = strtolower($name); + } } } diff --git a/phpseclib/Crypt/DH.php b/phpseclib/Crypt/DH.php new file mode 100644 index 00000000..098aef3e --- /dev/null +++ b/phpseclib/Crypt/DH.php @@ -0,0 +1,401 @@ + + * + * + * + * @category Crypt + * @package DH + * @author Jim Wigginton + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib\Crypt; + +use phpseclib\Exception\NoKeyLoadedException; +use phpseclib\Exception\UnsupportedOperationException; +use phpseclib\Crypt\Common\AsymmetricKey; +use phpseclib\Crypt\DH\PrivateKey; +use phpseclib\Crypt\DH\PublicKey; +use phpseclib\Crypt\DH\Parameters; +use phpseclib\Math\BigInteger; + +/** + * Pure-PHP (EC)DH implementation + * + * @package DH + * @author Jim Wigginton + * @access public + */ +abstract class DH extends AsymmetricKey +{ + /** + * Algorithm Name + * + * @var string + * @access private + */ + const ALGORITHM = 'DH'; + + /** + * DH prime + * + * @var \phpseclib\Math\BigInteger + * @access private + */ + protected $prime; + + /** + * DH Base + * + * Prime divisor of p-1 + * + * @var \phpseclib\Math\BigInteger + * @access private + */ + protected $base; + + /** + * Create DH parameters + * + * This method is a bit polymorphic. It can take any of the following: + * - two BigInteger's (prime and base) + * - an integer representing the size of the prime in bits (the base is assumed to be 2) + * - a string (eg. diffie-hellman-group14-sha1) + * + * @access public + * @return \phpseclib\Crypt\DH|bool + */ + public static function createParameters(...$args) + { + $params = new Parameters; + if (count($args) == 2 && $args[0] instanceof BigInteger && $args[1] instanceof BigInteger) { + //if (!$args[0]->isPrime()) { + // throw new \InvalidArgumentException('The first parameter should be a prime number'); + //} + $params->prime = $args[0]; + $params->base = $args[1]; + return $params; + } elseif (count($args) == 1 && is_numeric($args[0])) { + $params->prime = BigInteger::randomPrime($args[0]); + $params->base = new BigInteger(2); + return $params; + } elseif (count($args) != 1 || !is_string($args[0])) { + 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'); + } + switch ($args[0]) { + // see http://tools.ietf.org/html/rfc2409#section-6.2 and + // http://tools.ietf.org/html/rfc2412, appendex E + case 'diffie-hellman-group1-sha1': + $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' . + '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' . + '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' . + 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF'; + break; + // see http://tools.ietf.org/html/rfc3526#section-3 + case 'diffie-hellman-group14-sha1': // 2048-bit MODP Group + case 'diffie-hellman-group14-sha256': + $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' . + '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' . + '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' . + 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' . + '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' . + '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' . + 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' . + '3995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF'; + break; + // see https://tools.ietf.org/html/rfc3526#section-4 + case 'diffie-hellman-group15-sha512': // 3072-bit MODP Group + $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' . + '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' . + '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' . + 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' . + '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' . + '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' . + 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' . + '3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33' . + 'A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7' . + 'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864' . + 'D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2' . + '08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF'; + break; + // see https://tools.ietf.org/html/rfc3526#section-5 + case 'diffie-hellman-group16-sha512': // 4096-bit MODP Group + $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' . + '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' . + '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' . + 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' . + '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' . + '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' . + 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' . + '3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33' . + 'A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7' . + 'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864' . + 'D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2' . + '08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7' . + '88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8' . + 'DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2' . + '233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9' . + '93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF'; + break; + // see https://tools.ietf.org/html/rfc3526#section-6 + case 'diffie-hellman-group17-sha512': // 6144-bit MODP Group + $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' . + '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' . + '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' . + 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' . + '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' . + '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' . + 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' . + '3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33' . + 'A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7' . + 'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864' . + 'D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2' . + '08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7' . + '88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8' . + 'DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2' . + '233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9' . + '93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026' . + 'C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AE' . + 'B06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B' . + 'DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92EC' . + 'F032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E' . + '59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA' . + 'CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76' . + 'F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468' . + '043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DCC4024FFFFFFFFFFFFFFFF'; + break; + // see https://tools.ietf.org/html/rfc3526#section-7 + case 'diffie-hellman-group18-sha512': // 8192-bit MODP Group + $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' . + '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' . + '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' . + 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' . + '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' . + '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' . + 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' . + '3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33' . + 'A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7' . + 'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864' . + 'D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2' . + '08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7' . + '88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8' . + 'DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2' . + '233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9' . + '93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026' . + 'C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AE' . + 'B06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B' . + 'DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92EC' . + 'F032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E' . + '59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA' . + 'CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76' . + 'F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468' . + '043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E4' . + '38777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED' . + '2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652D' . + 'E3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B' . + '4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A6' . + '6D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851D' . + 'F9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F92' . + '4009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA' . + '9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF'; + break; + default: + throw new \InvalidArgumentException('Invalid named prime provided'); + } + + $params->prime = new BigInteger($prime, 16); + $params->base = new BigInteger(2); + + return $params; + } + + /** + * Create public / private key pair. + * + * The rationale for the second parameter is described in http://tools.ietf.org/html/rfc4419#section-6.2 : + * + * "To increase the speed of the key exchange, both client and server may + * reduce the size of their private exponents. It should be at least + * twice as long as the key material that is generated from the shared + * secret. For more details, see the paper by van Oorschot and Wiener + * [VAN-OORSCHOT]." + * + * $length is in bits + * + * @param Parameters $params + * @param int $length optional + * @access public + * @return DH\PrivateKey + */ + public static function createKey(Parameters $params, $length = 0) + { + $one = new BigInteger(1); + if ($length) { + $max = $one->bitwise_leftShift($length); + $max = $max->subtract($one); + } else { + $max = $params->prime->subtract($one); + } + + $key = new PrivateKey; + $key->prime = $params->prime; + $key->base = $params->base; + $key->privateKey = BigInteger::randomRange($one, $max); + $key->publicKey = $key->base->powMod($key->privateKey, $key->prime); + return $key; + } + + /** + * Compute Shared Secret + * + * @param PrivateKey|EC $private + * @param PublicKey|BigInteger|string $public + * @access public + * @return mixed + */ + public static function computeSecret($private, $public) + { + if ($private instanceof PrivateKey) { // DH\PrivateKey + switch (true) { + case $public instanceof PublicKey: + if (!$private->prime->equals($public->prime) || !$private->base->equals($public->base)) { + throw new \InvalidArgumentException('The public and private key do not share the same prime and / or base numbers'); + } + return $public->publicKey->powMod($private->privateKey, $private->prime)->toBytes(true); + case is_string($public): + $public = new BigInteger($public, -256); + case $public instanceof BigInteger: + return $public->powMod($private->privateKey, $private->prime)->toBytes(true); + default: + throw new \InvalidArgumentException('$public needs to be an instance of DH\PublicKey, a BigInteger or a string'); + } + } + + if ($private instanceof EC\PrivateKey) { + switch (true) { + case $public instanceof EC\PublicKey: + $public = $public->getEncodedCoordinates(); + case is_string($public): + $point = $private->multiply($public); + switch ($private->getCurve()) { + case 'Curve25519': + case 'Curve448': + $secret = $point; + break; + default: + // according to https://www.secg.org/sec1-v2.pdf#page=33 only X is returned + $secret = substr($point, 1, (strlen($point) - 1) >> 1); + } + /* + if (($secret[0] & "\x80") === "\x80") { + $secret = "\0$secret"; + } + */ + return $secret; + default: + throw new \InvalidArgumentException('$public needs to be an instance of EC\PublicKey or a string (an encoded coordinate)'); + } + } + } + + /** + * Load the key + * + * @param string $key + * @param string $password optional + * @return AsymmetricKey + */ + public static function load($key, $password = false) + { + try { + return EC::load($key, $password); + } catch (NoKeyLoadedException $e) {} + + return parent::load($key, $password); + } + + /** + * OnLoad Handler + * + * @return bool + * @access protected + * @param array $components + */ + protected static function onLoad($components) + { + if (!isset($components['privateKey']) && !isset($components['publicKey'])) { + $new = new Parameters; + } else { + $new = isset($components['privateKey']) ? + new PrivateKey : + new PublicKey; + } + + $new->prime = $components['prime']; + $new->base = $components['base']; + + if (isset($components['privateKey'])) { + $new->privateKey = $components['privateKey']; + } + if (isset($components['publicKey'])) { + $new->publicKey = $components['publicKey']; + } + + return $new; + } + + /** + * Determines which hashing function should be used + * + * @access public + * @param string $hash + */ + public function withHash($hash) + { + throw new UnsupportedOperationException('DH does not use a hash algorithm'); + } + + /** + * Returns the hash algorithm currently being used + * + * @access public + */ + public function getHash() + { + throw new UnsupportedOperationException('DH does not use a hash algorithm'); + } + + /** + * Returns the parameters + * + * A public / private key is only returned if the currently loaded "key" contains an x or y + * value. + * + * @see self::getPublicKey() + * @access public + * @param string $type optional + * @return mixed + */ + public function getParameters() + { + $type = self::validatePlugin('Keys', 'PKCS1', 'saveParameters'); + + $key = $type::saveParameters($this->prime, $this->base); + return self::load($key, 'PKCS1'); + } +} \ No newline at end of file diff --git a/phpseclib/Crypt/DH/Formats/Keys/PKCS1.php b/phpseclib/Crypt/DH/Formats/Keys/PKCS1.php new file mode 100644 index 00000000..86561565 --- /dev/null +++ b/phpseclib/Crypt/DH/Formats/Keys/PKCS1.php @@ -0,0 +1,83 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib\Crypt\DH\Formats\Keys; + +use phpseclib\Math\BigInteger; +use phpseclib\Crypt\Common\Formats\Keys\PKCS1 as Progenitor; +use phpseclib\File\ASN1; +use phpseclib\File\ASN1\Maps; + +/** + * "PKCS1" Formatted DH Key Handler + * + * @package DH + * @author Jim Wigginton + * @access public + */ +abstract class PKCS1 extends Progenitor +{ + /** + * Break a public or private key down into its constituent components + * + * @access public + * @param string $key + * @param string $password optional + * @return array + */ + public static function load($key, $password = '') + { + $key = parent::load($key, $password); + + $decoded = ASN1::decodeBER($key); + if (empty($decoded)) { + throw new \RuntimeException('Unable to decode BER'); + } + + $components = ASN1::asn1map($decoded[0], Maps\DHParameter::MAP); + if (!is_array($components)) { + throw new \RuntimeException('Unable to perform ASN1 mapping on parameters'); + } + + return $components; + } + + /** + * Convert EC parameters to the appropriate format + * + * @access public + * @return string + */ + public static function saveParameters(BigInteger $prime, BigInteger $base, array $options = []) + { + $params = [ + 'prime' => $prime, + 'base' => $base + ]; + $params = ASN1::encodeDER($params, Maps\DHParameter::MAP); + + return "-----BEGIN DH PARAMETERS-----\r\n" . + chunk_split(base64_encode($params), 64) . + "-----END DH PARAMETERS-----\r\n"; + } +} diff --git a/phpseclib/Crypt/DH/Formats/Keys/PKCS8.php b/phpseclib/Crypt/DH/Formats/Keys/PKCS8.php new file mode 100644 index 00000000..423c185e --- /dev/null +++ b/phpseclib/Crypt/DH/Formats/Keys/PKCS8.php @@ -0,0 +1,156 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib\Crypt\DH\Formats\Keys; + +use phpseclib\Math\BigInteger; +use phpseclib\Crypt\Common\Formats\Keys\PKCS8 as Progenitor; +use phpseclib\File\ASN1; +use phpseclib\File\ASN1\Maps; + +/** + * PKCS#8 Formatted DH Key Handler + * + * @package DH + * @author Jim Wigginton + * @access public + */ +abstract class PKCS8 extends Progenitor +{ + /** + * OID Name + * + * @var string + * @access private + */ + const OID_NAME = 'dhKeyAgreement'; + + /** + * OID Value + * + * @var string + * @access private + */ + const OID_VALUE = '1.2.840.113549.1.3.1'; + + /** + * Child OIDs loaded + * + * @var bool + * @access private + */ + protected static $childOIDsLoaded = false; + + /** + * Break a public or private key down into its constituent components + * + * @access public + * @param string $key + * @param string $password optional + * @return array + */ + public static function load($key, $password = '') + { + if (!is_string($key)) { + throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key)); + } + + $isPublic = strpos($key, 'PUBLIC') !== false; + + $key = parent::load($key, $password); + + $type = isset($key['privateKey']) ? 'privateKey' : 'publicKey'; + + switch (true) { + case !$isPublic && $type == 'publicKey': + throw new \UnexpectedValueException('Human readable string claims non-public key but DER encoded string claims public key'); + case $isPublic && $type == 'privateKey': + throw new \UnexpectedValueException('Human readable string claims public key but DER encoded string claims private key'); + } + + $decoded = ASN1::decodeBER($key[$type . 'Algorithm']['parameters']->element); + if (empty($decoded)) { + throw new \RuntimeException('Unable to decode BER of parameters'); + } + $components = ASN1::asn1map($decoded[0], Maps\DHParameter::MAP); + if (!is_array($components)) { + throw new \RuntimeException('Unable to perform ASN1 mapping on parameters'); + } + + $decoded = ASN1::decodeBER($key[$type]); + switch (true) { + case empty($decoded): + case !is_array($decoded): + case !isset($decoded[0]['content']): + case !$decoded[0]['content'] instanceof BigInteger: + throw new \RuntimeException('Unable to decode BER of parameters'); + } + $components[$type] = $decoded[0]['content']; + + return $components; + } + + /** + * Convert a private key to the appropriate format. + * + * @access public + * @param \phpseclib\Math\BigInteger $prime + * @param \phpseclib\Math\BigInteger $base + * @param \phpseclib\Math\BigInteger $privateKey + * @param \phpseclib\Math\BigInteger $publicKey + * @param string $password optional + * @param array $options optional + * @return string + */ + public static function savePrivateKey(BigInteger $prime, BigInteger $base, BigInteger $privateKey, BigInteger $publicKey, $password = '', array $options = []) + { + $params = [ + 'prime' => $prime, + 'base' => $base + ]; + $params = ASN1::encodeDER($params, Maps\DHParameter::MAP); + $params = new ASN1\Element($params); + $key = ASN1::encodeDER($privateKey, ['type' => ASN1::TYPE_INTEGER]); + return self::wrapPrivateKey($key, [], $params, $password, $options); + } + + /** + * Convert a public key to the appropriate format + * + * @access public + * @param \phpseclib\Math\BigInteger $prime + * @param \phpseclib\Math\BigInteger $base + * @param \phpseclib\Math\BigInteger $publicKey + * @param array $options optional + * @return string + */ + public static function savePublicKey(BigInteger $prime, BigInteger $base, BigInteger $publicKey, array $options = []) + { + $params = [ + 'prime' => $prime, + 'base' => $base + ]; + $params = ASN1::encodeDER($params, Maps\DHParameter::MAP); + $params = new ASN1\Element($params); + $key = ASN1::encodeDER($publicKey, ['type' => ASN1::TYPE_INTEGER]); + return self::wrapPublicKey($key, $params); + } +} \ No newline at end of file diff --git a/phpseclib/Crypt/DH/Parameters.php b/phpseclib/Crypt/DH/Parameters.php new file mode 100644 index 00000000..d07ebea3 --- /dev/null +++ b/phpseclib/Crypt/DH/Parameters.php @@ -0,0 +1,40 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib\Crypt\DH; + +use phpseclib\Crypt\DH; + +/** + * DH Parameters + * + * @package DH + * @author Jim Wigginton + * @access public + */ +class Parameters extends DH +{ + /** + * Returns the parameters + * + * @param string $type + * @param array $options optional + * @return string + */ + public function toString($type = 'PKCS1', array $options = []) + { + $type = self::validatePlugin('Keys', 'PKCS1', 'saveParameters'); + + return $type::saveParameters($this->prime, $this->base, $options); + } +} diff --git a/phpseclib/Crypt/DH/PrivateKey.php b/phpseclib/Crypt/DH/PrivateKey.php new file mode 100644 index 00000000..67a01188 --- /dev/null +++ b/phpseclib/Crypt/DH/PrivateKey.php @@ -0,0 +1,82 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib\Crypt\DH; + +use phpseclib\Crypt\DH; +use phpseclib\Crypt\Common; + +/** + * DH Private Key + * + * @package DH + * @author Jim Wigginton + * @access public + */ +class PrivateKey extends DH +{ + use Common\Traits\PasswordProtected; + + /** + * Private Key + * + * @var \phpseclib\Math\BigInteger + * @access private + */ + protected $privateKey; + + /** + * Public Key + * + * @var \phpseclib\Math\BigInteger + * @access private + */ + protected $publicKey; + + /** + * Returns the public key + * + * @access public + * @return DH + */ + public function getPublicKey() + { + $type = self::validatePlugin('Keys', 'PKCS8', 'savePublicKey'); + + if (!isset($this->publicKey)) { + $this->publicKey = $this->base->powMod($this->privateKey, $this->prime); + } + + $key = $type::savePublicKey($this->prime, $this->base, $this->publicKey); + + return DH::loadFormat('PKCS8', $key); + } + + /** + * Returns the private key + * + * @param string $type + * @param array $options optional + * @return string + */ + public function toString($type, array $options = []) + { + $type = self::validatePlugin('Keys', $type, 'savePrivateKey'); + + if (!isset($this->publicKey)) { + $this->publicKey = $this->base->powMod($this->privateKey, $this->prime); + } + + return $type::savePrivateKey($this->prime, $this->base, $this->privateKey, $this->publicKey, $this->password, $options); + } +} diff --git a/phpseclib/Crypt/DH/PublicKey.php b/phpseclib/Crypt/DH/PublicKey.php new file mode 100644 index 00000000..1ae00486 --- /dev/null +++ b/phpseclib/Crypt/DH/PublicKey.php @@ -0,0 +1,53 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib\Crypt\DH; + +use phpseclib\Crypt\DH; +use phpseclib\Crypt\Common; + +/** + * DH Public Key + * + * @package DH + * @author Jim Wigginton + * @access public + */ +class PublicKey extends DH +{ + use Common\Traits\Fingerprint; + + /** + * Returns the public key + * + * @param string $type + * @param array $options optional + * @return string + */ + public function toString($type, array $options = []) + { + $type = self::validatePlugin('Keys', $type, 'savePublicKey'); + + return $type::savePublicKey($this->prime, $this->base, $this->publicKey, $options); + } + + /** + * Returns the public key as a BigInteger + * + * @return \phpseclib\Math\BigInteger + */ + public function toBigInteger() + { + return $this->publicKey; + } +} diff --git a/phpseclib/Crypt/EC.php b/phpseclib/Crypt/EC.php index 5c0f7494..3366824a 100644 --- a/phpseclib/Crypt/EC.php +++ b/phpseclib/Crypt/EC.php @@ -36,13 +36,17 @@ use phpseclib\Crypt\EC\PrivateKey; use phpseclib\Crypt\EC\PublicKey; use phpseclib\Crypt\EC\Parameters; use phpseclib\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve; +use phpseclib\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve; +use phpseclib\Crypt\EC\Curves\Curve25519; use phpseclib\Crypt\EC\Curves\Ed25519; use phpseclib\Crypt\EC\Curves\Ed448; use phpseclib\Crypt\EC\Formats\Keys\PKCS1; use phpseclib\File\ASN1\Maps\ECParameters; use phpseclib\File\ASN1; +use phpseclib\Math\BigInteger; use phpseclib\Exception\UnsupportedCurveException; use phpseclib\Exception\UnsupportedAlgorithmException; +use phpseclib\Exception\UnsupportedOperationException; /** * Pure-PHP implementation of EC. @@ -157,9 +161,13 @@ abstract class EC extends AsymmetricKey $privatekey = new PrivateKey; $curveName = $curve; - $curve = '\phpseclib\Crypt\EC\Curves\\' . $curve; + $curve = '\phpseclib\Crypt\EC\Curves\\' . $curveName; if (!class_exists($curve)) { - throw new UnsupportedCurveException('Named Curve of ' . $curveName . ' is not supported'); + $curveName = ucfirst($curveName); + $curve = '\phpseclib\Crypt\EC\Curves\\' . $curveName; + if (!class_exists($curve)) { + throw new UnsupportedCurveException('Named Curve of ' . $curveName . ' is not supported'); + } } $reflect = new \ReflectionClass($curve); @@ -169,7 +177,14 @@ abstract class EC extends AsymmetricKey $curve = new $curve(); $privatekey->dA = $dA = $curve->createRandomMultiplier(); - $privatekey->QA = $curve->multiplyPoint($curve->getBasePoint(), $dA); + if ($curve instanceof Curve25519 && self::$engines['libsodium']) { + //$r = pack('H*', '0900000000000000000000000000000000000000000000000000000000000000'); + //$QA = sodium_crypto_scalarmult($dA->toBytes(), $r); + $QA = sodium_crypto_box_publickey_from_secretkey($dA->toBytes()); + $privatekey->QA = [$curve->convertInteger(new BigInteger(strrev($QA), 256))]; + } else { + $privatekey->QA = $curve->multiplyPoint($curve->getBasePoint(), $dA); + } $privatekey->curve = $curve; //$publickey = clone $privatekey; @@ -310,6 +325,24 @@ abstract class EC extends AsymmetricKey 'OpenSSL' : 'PHP'; } + /** + * Returns the public key coordinates as a string + * + * Used by ECDH + * + * @return string + */ + public function getEncodedCoordinates() + { + if ($this->curve instanceof MontgomeryCurve) { + return strrev($this->QA[0]->toBytes(true)); + } + if ($this->curve instanceof TwistedEdwardsCurve) { + return $this->curve->encodePoint($this->QA); + } + return "\4" . $this->QA[0]->toBytes(true) . $this->QA[1]->toBytes(true); + } + /** * Returns the parameters * @@ -339,6 +372,10 @@ abstract class EC extends AsymmetricKey */ public function withSignatureFormat($format) { + if ($this->curve instanceof MontgomeryCurve) { + throw new UnsupportedOperationException('Montgomery Curves cannot be used to create signatures'); + } + $new = clone $this; $new->shortFormat = $format; $new->format = self::validatePlugin('Signature', $format); @@ -404,6 +441,9 @@ abstract class EC extends AsymmetricKey */ public function withHash($hash) { + if ($this->curve instanceof MontgomeryCurve) { + throw new UnsupportedOperationException('Montgomery Curves cannot be used to create signatures'); + } if ($this->curve instanceof Ed25519 && $hash != 'sha512') { throw new UnsupportedAlgorithmException('Ed25519 only supports sha512 as a hash'); } @@ -413,4 +453,18 @@ abstract class EC extends AsymmetricKey return parent::withHash($hash); } + + /** + * __toString() magic method + * + * @return string + */ + public function __toString() + { + if ($this->curve instanceof MontgomeryCurve) { + return ''; + } + + return parent::__toString(); + } } \ No newline at end of file diff --git a/phpseclib/Crypt/EC/BaseCurves/Montgomery.php b/phpseclib/Crypt/EC/BaseCurves/Montgomery.php new file mode 100644 index 00000000..7c0daa6e --- /dev/null +++ b/phpseclib/Crypt/EC/BaseCurves/Montgomery.php @@ -0,0 +1,283 @@ + + * @copyright 2019 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib\Crypt\EC\BaseCurves; + +use phpseclib\Math\Common\FiniteField\Integer; +use phpseclib\Common\Functions\Strings; +use phpseclib\Math\PrimeField; +use phpseclib\Math\BigInteger; +use phpseclib\Math\PrimeField\Integer as PrimeInteger; + +/** + * Curves over y^2 = x^3 + a*x + x + * + * @package EC + * @author Jim Wigginton + * @access public + */ +class Montgomery extends Base +{ + /** + * Prime Field Integer factory + * + * @var \phpseclib\Math\PrimeFields + */ + protected $factory; + + /** + * Cofficient for x + * + * @var object + */ + protected $a; + + /** + * Constant used for point doubling + * + * @var object + */ + protected $a24; + + /** + * The Number Zero + * + * @var object + */ + protected $zero; + + /** + * The Number One + * + * @var object + */ + protected $one; + + /** + * Base Point + * + * @var object + */ + protected $p; + + /** + * The modulo + * + * @var BigInteger + */ + protected $modulo; + + /** + * The Order + * + * @var BigInteger + */ + protected $order; + + /** + * Sets the modulo + */ + public function setModulo(BigInteger $modulo) + { + $this->modulo = $modulo; + $this->factory = new PrimeField($modulo); + $this->zero = $this->factory->newInteger(new BigInteger()); + $this->one = $this->factory->newInteger(new BigInteger(1)); + } + + /** + * Set coefficients a + */ + public function setCoefficients(BigInteger $a) + { + if (!isset($this->factory)) { + throw new \RuntimeException('setModulo needs to be called before this method'); + } + $this->a = $this->factory->newInteger($a); + $two = $this->factory->newInteger(new BigInteger(2)); + $four = $this->factory->newInteger(new BigInteger(4)); + $this->a24 = $this->a->subtract($two)->divide($four); + } + + /** + * Set x and y coordinates for the base point + * + * @param BigInteger|PrimeInteger $x + * @param BigInteger|PrimeInteger $y + * @return PrimeInteger[] + */ + public function setBasePoint($x, $y) + { + switch (true) { + case !$x instanceof BigInteger && !$x instanceof PrimeInteger: + throw new \UnexpectedValueException('Argument 1 passed to Prime::setBasePoint() must be an instance of either BigInteger or PrimeField\Integer'); + case !$y instanceof BigInteger && !$y instanceof PrimeInteger: + throw new \UnexpectedValueException('Argument 2 passed to Prime::setBasePoint() must be an instance of either BigInteger or PrimeField\Integer'); + } + if (!isset($this->factory)) { + throw new \RuntimeException('setModulo needs to be called before this method'); + } + $this->p = [ + $x instanceof BigInteger ? $this->factory->newInteger($x) : $x, + $y instanceof BigInteger ? $this->factory->newInteger($y) : $y + ]; + } + + /** + * Retrieve the base point as an array + * + * @return array + */ + public function getBasePoint() + { + if (!isset($this->factory)) { + throw new \RuntimeException('setModulo needs to be called before this method'); + } + /* + if (!isset($this->p)) { + throw new \RuntimeException('setBasePoint needs to be called before this method'); + } + */ + return $this->p; + } + + /** + * Doubles and adds a point on a curve + * + * See https://tools.ietf.org/html/draft-ietf-tls-curve25519-01#appendix-A.1.3 + * + * @return FiniteField[][] + */ + private function doubleAndAddPoint(array $p, array $q, PrimeInteger $x1) + { + if (!isset($this->factory)) { + throw new \RuntimeException('setModulo needs to be called before this method'); + } + + if (!count($p) || !count($q)) { + return []; + } + + if (!isset($p[1])) { + throw new \RuntimeException('Affine coordinates need to be manually converted to XZ coordinates'); + } + + list($x2, $z2) = $p; + list($x3, $z3) = $q; + + $a = $x2->add($z2); + $aa = $a->multiply($a); + $b = $x2->subtract($z2); + $bb = $b->multiply($b); + $e = $aa->subtract($bb); + $c = $x3->add($z3); + $d = $x3->subtract($z3); + $da = $d->multiply($a); + $cb = $c->multiply($b); + $temp = $da->add($cb); + $x5 = $temp->multiply($temp); + $temp = $da->subtract($cb); + $z5 = $x1->multiply($temp->multiply($temp)); + $x4 = $aa->multiply($bb); + $z4 = $e->multiply($bb->add($this->a24->multiply($e))); + + return [ + [$x4, $z4], + [$x5, $z5] + ]; + } + + /** + * Multiply a point on the curve by a scalar + * + * Uses the montgomery ladder technique as described here: + * + * https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication#Montgomery_ladder + * https://github.com/phpecc/phpecc/issues/16#issuecomment-59176772 + * + * @return array + */ + public function multiplyPoint(array $p, Integer $d) + { + $p1 = [$this->one, $this->zero]; + $alreadyInternal = isset($x[1]); + $p2 = $this->convertToInternal($p); + $x = $p[0]; + + $b = $d->toBits(); + $b = str_pad($b, 256, '0', STR_PAD_LEFT); + for ($i = 0; $i < strlen($b); $i++) { + $b_i = (int) $b[$i]; + if ($b_i) { + list($p2, $p1) = $this->doubleAndAddPoint($p2, $p1, $x); + } else { + list($p1, $p2) = $this->doubleAndAddPoint($p1, $p2, $x); + } + } + + return $alreadyInternal ? $p1 : $this->convertToAffine($p1); + } + + /** + * Converts an affine point to an XZ coordinate + * + * From https://hyperelliptic.org/EFD/g1p/auto-montgom-xz.html + * + * XZ coordinates represent x y as X Z satsfying the following equations: + * + * x=X/Z + * + * @return \phpseclib\Math\PrimeField\Integer[] + */ + public function convertToInternal(array $p) + { + if (empty($p)) { + return [clone $this->zero, clone $this->one]; + } + + if (isset($p[1])) { + return $p; + } + + $p[1] = clone $this->one; + + return $p; + } + + /** + * Returns the affine point + * + * @return \phpseclib\Math\PrimeField\Integer[] + */ + public function convertToAffine(array $p) + { + if (!isset($p[1])) { + return $p; + } + list($x, $z) = $p; + return [$x->divide($z)]; + } +} \ No newline at end of file diff --git a/phpseclib/Crypt/EC/BaseCurves/TwistedEdwards.php b/phpseclib/Crypt/EC/BaseCurves/TwistedEdwards.php index 8c6d1e33..b93cd9b6 100644 --- a/phpseclib/Crypt/EC/BaseCurves/TwistedEdwards.php +++ b/phpseclib/Crypt/EC/BaseCurves/TwistedEdwards.php @@ -216,21 +216,4 @@ class TwistedEdwards extends Base return $lhs->equals($rhs); } - - /** - * Tests whether or not the x / y values satisfy the equation - * - * @return boolean - */ - public function get(array $p) - { - list($x, $y) = $p; - $x2 = $x->multiply($x); - $y2 = $y->multiply($y); - - $lhs = $this->a->multiply($x2)->add($y2); - $rhs = $this->d->multiply($x2)->multiply($y2)->add($this->one); - - return $lhs->equals($rhs); - } } \ No newline at end of file diff --git a/phpseclib/Crypt/EC/Curves/Curve25519.php b/phpseclib/Crypt/EC/Curves/Curve25519.php new file mode 100644 index 00000000..2eeaf4af --- /dev/null +++ b/phpseclib/Crypt/EC/Curves/Curve25519.php @@ -0,0 +1,64 @@ + + * @copyright 2019 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib\Crypt\EC\Curves; + +use phpseclib\Math\Common\FiniteField\Integer; +use phpseclib\Crypt\EC\BaseCurves\Montgomery; +use phpseclib\Math\BigInteger; + +class Curve25519 extends Montgomery +{ + public function __construct() + { + // 2^255 - 19 + $this->setModulo(new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED', 16)); + $this->a24 = $this->factory->newInteger(new BigInteger('121666')); + $this->p = [$this->factory->newInteger(new BigInteger(9))]; + // 2^252 + 0x14def9dea2f79cd65812631a5cf5d3ed + $this->setOrder(new BigInteger('1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED', 16)); + + /* + $this->setCoefficients( + new BigInteger('486662'), // a + ); + $this->setBasePoint( + new BigInteger(9), + new BigInteger('14781619447589544791020593568409986887264606134616475288964881837755586237401', 16) + ); + */ + } + + /** + * Multiply a point on the curve by a scalar + * + * Modifies the scalar as described at https://tools.ietf.org/html/rfc7748#page-8 + * + * @return array + */ + public function multiplyPoint(array $p, Integer $d) + { + //$r = strrev(sodium_crypto_scalarmult($d->toBytes(), strrev($p[0]->toBytes()))); + //return [$this->factory->newInteger(new BigInteger($r, 256))]; + + $d = $d->toBytes(); + $d&= "\xF8" . str_repeat("\xFF", 30) . "\x7F"; + $d = strrev($d); + $d|= "\x40"; + $d = $this->factory->newInteger(new BigInteger($d, -256)); + + return parent::multiplyPoint($p, $d); + } +} \ No newline at end of file diff --git a/phpseclib/Crypt/EC/Formats/Keys/Curve25519Private.php b/phpseclib/Crypt/EC/Formats/Keys/Curve25519Private.php new file mode 100644 index 00000000..79391f02 --- /dev/null +++ b/phpseclib/Crypt/EC/Formats/Keys/Curve25519Private.php @@ -0,0 +1,92 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib\Crypt\EC\Formats\Keys; + +use phpseclib\Crypt\EC\Curves\Curve25519; +use phpseclib\Math\Common\FiniteField\Integer; +use phpseclib\Math\BigInteger; + +/** + * Curve25519 Private Key Handler + * + * @package EC + * @author Jim Wigginton + * @access public + */ +abstract class Curve25519Private +{ + /** + * Is invisible flag + * + * @access private + */ + const IS_INVISIBLE = true; + + /** + * Break a public or private key down into its constituent components + * + * @access public + * @param string $key + * @param string $password optional + * @return array + */ + public static function load($key, $password = '') + { + $curve = new Curve25519(); + + $components = ['curve' => $curve]; + $components['dA'] = $components['curve']->convertInteger(new BigInteger($key, -256)); + // note that EC::getEncodedCoordinates does some additional "magic" (it does strrev on the result) + $components['QA'] = $components['curve']->multiplyPoint($components['curve']->getBasePoint(), $components['dA']); + + return $components; + } + + /** + * Convert an EC public key to the appropriate format + * + * @access public + * @param \phpseclib\Crypt\EC\Curves\Curve25519 $curve + * @param \phpseclib\Math\Common\FiniteField\Integer[] $publicKey + * @return string + */ + public static function savePublicKey(Curve25519 $curve, array $publicKey) + { + return strrev($publicKey[0]->toBytes(true)); + } + + /** + * Convert a private key to the appropriate format. + * + * @access public + * @param \phpseclib\Math\Common\FiniteField\Integer $privateKey + * @param \phpseclib\Crypt\EC\Curves\Curve25519 $curve + * @param \phpseclib\Math\Common\FiniteField\Integer[] $publicKey + * @param string $password optional + * @return string + */ + public static function savePrivateKey(Integer $privateKey, Curve25519 $curve, array $publicKey, $password = '') + { + return $privateKey->toBytes(true); + } +} diff --git a/phpseclib/Crypt/EC/Formats/Keys/Curve25519Public.php b/phpseclib/Crypt/EC/Formats/Keys/Curve25519Public.php new file mode 100644 index 00000000..22828f1a --- /dev/null +++ b/phpseclib/Crypt/EC/Formats/Keys/Curve25519Public.php @@ -0,0 +1,68 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib\Crypt\EC\Formats\Keys; + +use phpseclib\Crypt\EC\Curves\Curve25519; +use phpseclib\Math\Common\FiniteField\Integer; +use phpseclib\Math\BigInteger; + +/** + * Curve25519 Public Key Handler + * + * @package EC + * @author Jim Wigginton + * @access public + */ +abstract class Curve25519Public +{ + /** + * Is invisible flag + * + * @access private + */ + const IS_INVISIBLE = true; + + /** + * Break a public or private key down into its constituent components + * + * @access public + * @param string $key + * @param string $password optional + * @return array + */ + public static function load($key, $password = '') + { + $curve = new Curve25519(); + + $components = ['curve' => $curve]; + $components['QA'] = [$components['curve']->convertInteger(new BigInteger(strrev($key), -256))]; + + return $components; + } + + /** + * Convert an EC public key to the appropriate format + * + * @access public + * @param \phpseclib\Crypt\EC\Curves\Curve25519 $curve + * @param \phpseclib\Math\Common\FiniteField\Integer[] $publicKey + * @return string + */ + public static function savePublicKey(Curve25519 $curve, array $publicKey) + { + return strrev($publicKey[0]->toBytes(true)); + } +} diff --git a/phpseclib/Crypt/EC/Formats/Keys/PKCS1.php b/phpseclib/Crypt/EC/Formats/Keys/PKCS1.php index 96cef1ca..76af302f 100644 --- a/phpseclib/Crypt/EC/Formats/Keys/PKCS1.php +++ b/phpseclib/Crypt/EC/Formats/Keys/PKCS1.php @@ -35,6 +35,7 @@ use phpseclib\Crypt\EC\BaseCurves\Base as BaseCurve; use phpseclib\Math\BigInteger; use ParagonIE\ConstantTime\Base64; use phpseclib\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve; +use phpseclib\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve; use phpseclib\Exception\UnsupportedCurveException; /** @@ -96,8 +97,8 @@ abstract class PKCS1 extends Progenitor { self::initialize_static_variables(); - if ($curve instanceof TwistedEdwardsCurve) { - throw new UnsupportedCurveException('TwistedEdwards Curves are not supported'); + if ($curve instanceof TwistedEdwardsCurve || $curve instanceof MontgomeryCurve) { + throw new UnsupportedCurveException('TwistedEdwards and Montgomery Curves are not supported'); } $key = self::encodeParameters($curve, false, $options); @@ -122,7 +123,7 @@ abstract class PKCS1 extends Progenitor { self::initialize_static_variables(); - if ($curve instanceof TwistedEdwardsCurve) { + if ($curve instanceof TwistedEdwardsCurve || $curve instanceof MontgomeryCurve) { throw new UnsupportedCurveException('TwistedEdwards Curves are not supported'); } diff --git a/phpseclib/Crypt/EC/Formats/Keys/PKCS8.php b/phpseclib/Crypt/EC/Formats/Keys/PKCS8.php index 6e9f3d9d..d031454d 100644 --- a/phpseclib/Crypt/EC/Formats/Keys/PKCS8.php +++ b/phpseclib/Crypt/EC/Formats/Keys/PKCS8.php @@ -31,9 +31,11 @@ use phpseclib\File\ASN1; use phpseclib\File\ASN1\Maps; use phpseclib\Crypt\EC\BaseCurves\Base as BaseCurve; use phpseclib\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve; +use phpseclib\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve; use phpseclib\Math\Common\FiniteField\Integer; use phpseclib\Crypt\EC\Curves\Ed25519; use phpseclib\Crypt\EC\Curves\Ed448; +use phpseclib\Exception\UnsupportedCurveException; /** * PKCS#8 Formatted EC Key Handler @@ -176,6 +178,10 @@ abstract class PKCS8 extends Progenitor { self::initialize_static_variables(); + if ($curve instanceof MontgomeryCurve) { + throw new UnsupportedCurveException('Montgomery Curves are not supported'); + } + if ($curve instanceof TwistedEdwardsCurve) { return self::wrapPublicKey( $curve->encodePoint($publicKey), @@ -206,6 +212,10 @@ abstract class PKCS8 extends Progenitor { self::initialize_static_variables(); + if ($curve instanceof MontgomeryCurve) { + throw new UnsupportedCurveException('Montgomery Curves are not supported'); + } + if ($curve instanceof TwistedEdwardsCurve) { return self::wrapPrivateKey( "\x04\x20" . $privateKey->secret, diff --git a/phpseclib/Crypt/EC/Formats/Keys/XML.php b/phpseclib/Crypt/EC/Formats/Keys/XML.php index 9c6f47ae..5d2c370c 100644 --- a/phpseclib/Crypt/EC/Formats/Keys/XML.php +++ b/phpseclib/Crypt/EC/Formats/Keys/XML.php @@ -25,6 +25,7 @@ use phpseclib\Math\BigInteger; use phpseclib\Crypt\EC\BaseCurves\Base as BaseCurve; use phpseclib\Crypt\EC\BaseCurves\Prime as PrimeCurve; use phpseclib\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve; +use phpseclib\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve; use phpseclib\Exception\UnsupportedCurveException; /** @@ -372,8 +373,8 @@ abstract class XML { self::initialize_static_variables(); - if ($curve instanceof TwistedEdwardsCurve) { - throw new UnsupportedCurveException('TwistedEdwards Curves are not supported'); + if ($curve instanceof TwistedEdwardsCurve || $curve instanceof MontgomeryCurve) { + throw new UnsupportedCurveException('TwistedEdwards and Montgomery Curves are not supported'); } if (empty(static::$namespace)) { diff --git a/phpseclib/Crypt/EC/PrivateKey.php b/phpseclib/Crypt/EC/PrivateKey.php index 97fcae0a..e50819fa 100644 --- a/phpseclib/Crypt/EC/PrivateKey.php +++ b/phpseclib/Crypt/EC/PrivateKey.php @@ -17,10 +17,13 @@ use phpseclib\Crypt\EC; use phpseclib\Crypt\EC\Formats\Signature\ASN1 as ASN1Signature; use phpseclib\Math\BigInteger; use phpseclib\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve; +use phpseclib\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve; use phpseclib\Crypt\Hash; use phpseclib\Crypt\EC\Curves\Ed25519; -use phpseclib\Crypt\EC\Formats\Keys\PKCS8; +use phpseclib\Crypt\EC\Curves\Curve25519; +use phpseclib\Crypt\EC\Formats\Keys\PKCS1; use phpseclib\Crypt\Common; +use phpseclib\Exception\UnsupportedOperationException; /** * EC Private Key @@ -44,6 +47,39 @@ class PrivateKey extends EC implements Common\PrivateKey */ protected $dA; + /** + * Multiplies an encoded point by the private key + * + * Used by ECDH + * + * @param string $coordinates + * @return string + */ + public function multiply($coordinates) + { + 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)) { + throw new \RuntimeException('The infinity point is invalid'); + } + return "\4" . $point[0]->toBytes(true) . $point[1]->toBytes(true); + } + /** * Create a signature * @@ -54,6 +90,10 @@ class PrivateKey extends EC implements Common\PrivateKey */ public function sign($message) { + if ($this->curve instanceof MontgomeryCurve) { + throw new UnsupportedOperationException('Montgomery Curves cannot be used to create signatures'); + } + $dA = $this->dA->toBigInteger(); $order = $this->curve->getOrder(); @@ -193,10 +233,21 @@ class PrivateKey extends EC implements Common\PrivateKey */ public function getPublicKey() { - $type = self::validatePlugin('Keys', 'PKCS8', 'savePublicKey'); + $format = 'PKCS8'; + if ($this->curve instanceof MontgomeryCurve) { + $format = $this->curve instanceof Curve25519 ? + 'Curve25519Public' : + 'Curve448Public'; + } + + $type = self::validatePlugin('Keys', $format, 'savePublicKey'); $key = $type::savePublicKey($this->curve, $this->QA); - $key = EC::loadFormat('PKCS8', $key) + $key = EC::loadFormat($format, $key); + if ($this->curve instanceof MontgomeryCurve) { + return $key; + } + $key = $key ->withHash($this->hash->getHash()) ->withSignatureFormat($this->shortFormat); if ($this->curve instanceof TwistedEdwardsCurve) { diff --git a/phpseclib/Crypt/EC/PublicKey.php b/phpseclib/Crypt/EC/PublicKey.php index 2d0d6fc8..d88dc1bb 100644 --- a/phpseclib/Crypt/EC/PublicKey.php +++ b/phpseclib/Crypt/EC/PublicKey.php @@ -18,10 +18,11 @@ use phpseclib\Crypt\Hash; use phpseclib\Math\BigInteger; use phpseclib\Crypt\EC\Formats\Signature\ASN1 as ASN1Signature; use phpseclib\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve; +use phpseclib\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve; use phpseclib\Crypt\EC\Curves\Ed25519; use phpseclib\Crypt\EC\Formats\Keys\PKCS1; -use phpseclib\Crypt\EC\Formats\Keys\PKCS8; use phpseclib\Crypt\Common; +use phpseclib\Exception\UnsupportedOperationException; /** * EC Public Key @@ -45,6 +46,10 @@ class PublicKey extends EC implements Common\PublicKey */ public function verify($message, $signature) { + if ($this->curve instanceof MontgomeryCurve) { + throw new UnsupportedOperationException('Montgomery Curves cannot be used to create signatures'); + } + $order = $this->curve->getOrder(); if ($this->curve instanceof TwistedEdwardsCurve) { diff --git a/phpseclib/File/ASN1/Maps/DHParameter.php b/phpseclib/File/ASN1/Maps/DHParameter.php new file mode 100644 index 00000000..de074308 --- /dev/null +++ b/phpseclib/File/ASN1/Maps/DHParameter.php @@ -0,0 +1,42 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib\File\ASN1\Maps; + +use phpseclib\File\ASN1; + +/** + * DHParameter + * + * @package ASN1 + * @author Jim Wigginton + * @access public + */ +abstract class DHParameter +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'prime' => ['type' => ASN1::TYPE_INTEGER], + 'base' => ['type' => ASN1::TYPE_INTEGER], + 'privateValueLength' => [ + 'type' => ASN1::TYPE_INTEGER, + 'optional' => true + ] + ] + ]; +} diff --git a/phpseclib/Net/SSH2.php b/phpseclib/Net/SSH2.php index ec1a1277..99354435 100644 --- a/phpseclib/Net/SSH2.php +++ b/phpseclib/Net/SSH2.php @@ -58,6 +58,7 @@ use phpseclib\Crypt\Common\PrivateKey; use phpseclib\Crypt\RSA; use phpseclib\Crypt\DSA; use phpseclib\Crypt\EC; +use phpseclib\Crypt\DH; use phpseclib\Crypt\TripleDES; use phpseclib\Crypt\Twofish; use phpseclib\Crypt\ChaCha20; @@ -1348,6 +1349,7 @@ class SSH2 /** * Key Exchange + * * @return bool * @param string|bool $kexinit_payload_server optional * @throws \UnexpectedValueException on receipt of unexpected packets @@ -1485,12 +1487,14 @@ class SSH2 // Only relevant in diffie-hellman-group-exchange-sha{1,256}, otherwise empty. $exchange_hash_rfc4419 = ''; - if ($this->kex_algorithm === 'curve25519-sha256@libssh.org') { - $x = Random::string(32); - $eBytes = \Sodium\crypto_box_publickey_from_secretkey($x); + if (strpos($this->kex_algorithm, 'curve25519-sha256') === 0 || strpos($this->kex_algorithm, 'ecdh-sha2-nistp') === 0) { + $curve = strpos($this->kex_algorithm, 'curve25519-sha256') === 0 ? + 'Curve25519' : + substr($this->kex_algorithm, 10); + $ourPrivate = EC::createKey($curve); + $ourPublicBytes = $ourPrivate->getPublicKey()->getEncodedCoordinates(); $clientKexInitMessage = NET_SSH2_MSG_KEX_ECDH_INIT; $serverKexReplyMessage = NET_SSH2_MSG_KEX_ECDH_REPLY; - $kexHash = new Hash('sha256'); } else { if (strpos($this->kex_algorithm, 'diffie-hellman-group-exchange') === 0) { $dh_group_sizes_packed = pack( @@ -1512,7 +1516,7 @@ class SSH2 throw new \RuntimeException('Connection closed by server'); } - list($type, $primeBytes, $gBytes) = unpack('Css', $response); + list($type, $primeBytes, $gBytes) = Strings::unpackSSH2('Css', $response); if ($type != NET_SSH2_MSG_KEXDH_GEX_GROUP) { throw new \UnexpectedValueException('Expected SSH_MSG_KEX_DH_GEX_GROUP'); } @@ -1525,65 +1529,43 @@ class SSH2 $gBytes ); + $params = DH::createParameters($prime, $g); $clientKexInitMessage = NET_SSH2_MSG_KEXDH_GEX_INIT; $serverKexReplyMessage = NET_SSH2_MSG_KEXDH_GEX_REPLY; } else { - switch ($this->kex_algorithm) { - // see http://tools.ietf.org/html/rfc2409#section-6.2 and - // http://tools.ietf.org/html/rfc2412, appendex E - case 'diffie-hellman-group1-sha1': - $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' . - '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' . - '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' . - 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF'; - break; - // see http://tools.ietf.org/html/rfc3526#section-3 - case 'diffie-hellman-group14-sha1': - $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' . - '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' . - '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' . - 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' . - '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' . - '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' . - 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' . - '3995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF'; - break; - } - // For both diffie-hellman-group1-sha1 and diffie-hellman-group14-sha1 - // the generator field element is 2 (decimal) and the hash function is sha1. - $g = new BigInteger(2); - $prime = new BigInteger($prime, 16); + $params = DH::createParameters($this->kex_algorithm); $clientKexInitMessage = NET_SSH2_MSG_KEXDH_INIT; $serverKexReplyMessage = NET_SSH2_MSG_KEXDH_REPLY; } - switch ($this->kex_algorithm) { - case 'diffie-hellman-group-exchange-sha256': - $kexHash = new Hash('sha256'); - break; - default: - $kexHash = new Hash('sha1'); - } - - /* To increase the speed of the key exchange, both client and server may - reduce the size of their private exponents. It should be at least - twice as long as the key material that is generated from the shared - secret. For more details, see the paper by van Oorschot and Wiener - [VAN-OORSCHOT]. - - -- http://tools.ietf.org/html/rfc4419#section-6.2 */ - $one = new BigInteger(1); - $keyLength = min($kexHash->getLengthInBytes(), max($encryptKeyLength, $decryptKeyLength)); - $max = $one->bitwise_leftShift(16 * $keyLength); // 2 * 8 * $keyLength - $max = $max->subtract($one); - - $x = BigInteger::randomRange($one, $max); - $e = $g->modPow($x, $prime); - - $eBytes = $e->toBytes(true); + $ourPrivate = DH::createKey($params, 16 * $keyLength); // 2 * 8 * $keyLength + $ourPublic = $ourPrivate->getPublicKey()->toBigInteger(); + $ourPublicBytes = $ourPublic->toBytes(true); } - $data = pack('CNa*', $clientKexInitMessage, strlen($eBytes), $eBytes); + switch ($this->kex_algorithm) { + case 'diffie-hellman-group15-sha512': + case 'diffie-hellman-group16-sha512': + case 'diffie-hellman-group17-sha512': + case 'diffie-hellman-group18-sha512': + case 'ecdh-sha2-nistp521': + $kexHash = new Hash('sha512'); + break; + case 'ecdh-sha2-nistp384': + $kexHash = new Hash('sha384'); + break; + case 'diffie-hellman-group-exchange-sha256': + case 'diffie-hellman-group14-sha256': + case 'ecdh-sha2-nistp256': + case 'curve25519-sha256@libssh.org': + case 'curve25519-sha256': + $kexHash = new Hash('sha256'); + break; + default: + $kexHash = new Hash('sha1'); + } + + $data = pack('CNa*', $clientKexInitMessage, strlen($ourPublicBytes), $ourPublicBytes); $this->send_binary_packet($data); @@ -1599,7 +1581,7 @@ class SSH2 list( $type, $server_public_host_key, - $fBytes, + $theirPublicBytes, $this->signature ) = Strings::unpackSSH2('Csss', $response); @@ -1615,18 +1597,10 @@ class SSH2 } $temp = unpack('Nlength', substr($this->signature, 0, 4)); $this->signature_format = substr($this->signature, 4, $temp['length']); - if ($this->kex_algorithm === 'curve25519-sha256@libssh.org') { - if (strlen($fBytes) !== 32) { - throw new \RuntimeException('Received curve25519 public key of invalid length.'); - return false; - } - $key = new BigInteger(\Sodium\crypto_scalarmult($x, $fBytes), 256); - \Sodium\memzero($x); - } else { - $f = new BigInteger($fBytes, -256); - $key = $f->modPow($x, $prime); + $keyBytes = DH::computeSecret($ourPrivate, $theirPublicBytes); + if (($keyBytes[0] & "\x80") === "\x80") { + $keyBytes = "\0$keyBytes"; } - $keyBytes = $key->toBytes(true); $this->exchange_hash = Strings::packSSH2('s5', $this->identifier, @@ -1637,8 +1611,8 @@ class SSH2 ); $this->exchange_hash.= $exchange_hash_rfc4419; $this->exchange_hash.= Strings::packSSH2('s3', - $eBytes, - $fBytes, + $ourPublicBytes, + $theirPublicBytes, $keyBytes ); @@ -3377,7 +3351,7 @@ class SSH2 // see http://tools.ietf.org/html/rfc4252#section-5.4; only called when the encryption has been activated and when we haven't already logged in if (($this->bitmap & self::MASK_CONNECTED) && !$this->isAuthenticated() && ord($payload[0]) == NET_SSH2_MSG_USERAUTH_BANNER) { Strings::shift($payload, 1); - list($this->banner_message) = Strings::unpackSSH2('s', $response); + list($this->banner_message) = Strings::unpackSSH2('s', $payload); $payload = $this->get_binary_packet(); } @@ -4262,22 +4236,27 @@ class SSH2 // Elliptic Curve Diffie-Hellman Key Agreement (ECDH) using // Curve25519. See doc/curve25519-sha256@libssh.org.txt in the // libssh repository for more information. + 'curve25519-sha256', 'curve25519-sha256@libssh.org', + 'ecdh-sha2-nistp256', // RFC 5656 + 'ecdh-sha2-nistp384', // RFC 5656 + 'ecdh-sha2-nistp521', // RFC 5656 + + 'diffie-hellman-group-exchange-sha256',// RFC 4419 + 'diffie-hellman-group-exchange-sha1', // RFC 4419 + // Diffie-Hellman Key Agreement (DH) using integer modulo prime // groups. - 'diffie-hellman-group1-sha1', // REQUIRED + 'diffie-hellman-group14-sha256', 'diffie-hellman-group14-sha1', // REQUIRED - 'diffie-hellman-group-exchange-sha1', // RFC 4419 - 'diffie-hellman-group-exchange-sha256', // RFC 4419 - ]; + 'diffie-hellman-group15-sha512', + 'diffie-hellman-group16-sha512', + 'diffie-hellman-group17-sha512', + 'diffie-hellman-group18-sha512', - if (!function_exists('\\Sodium\\library_version_major')) { - $kex_algorithms = array_diff( - $kex_algorithms, - ['curve25519-sha256@libssh.org'] - ); - } + 'diffie-hellman-group1-sha1', // REQUIRED + ]; return $kex_algorithms; } diff --git a/tests/Unit/Crypt/DHTest.php b/tests/Unit/Crypt/DHTest.php new file mode 100644 index 00000000..6c8fee0c --- /dev/null +++ b/tests/Unit/Crypt/DHTest.php @@ -0,0 +1,203 @@ + + * @copyright 2013 Andreas Fischer + * @license http://www.opensource.org/licenses/mit-license.html MIT License + */ + +use phpseclib\Crypt\DH; +use phpseclib\Crypt\DH\PublicKey; +use phpseclib\Crypt\DH\PrivateKey; +use phpseclib\Crypt\DH\Parameters; +use phpseclib\Crypt\EC; +use phpseclib\Math\BigInteger; +use phpseclib\Crypt\AES; + +class Unit_Crypt_DHTest extends PhpseclibTestCase +{ + public function testParametersWithString() + { + $a = DH::createParameters('diffie-hellman-group1-sha1'); + $a = str_replace("\r\n", "\n", trim($a)); + $b = '-----BEGIN DH PARAMETERS----- +MIGHAoGBAP//////////yQ/aoiFowjTExmKLgNwc0SkCTgiKZ8x0Agu+pjsTmyJR +Sgh5jjQE3e+VGbPNOkMbMCsKbfJfFDdP4TVtbVHCReSFtXZiXn7G9ExC6aY37WsL +/1y29Aa37e44a/taiZ+lrp8kEXxLH+ZJKGZR7OZTgf//////////AgEC +-----END DH PARAMETERS-----'; + $this->assertSame($b, "$a"); + } + + public function testParametersWithInteger() + { + $a = DH::createParameters(512); + $this->assertInternalType('string', "$a"); + } + + public function testParametersWithBigIntegers() + { + $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' . + '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' . + '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' . + 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF'; + $prime = new BigInteger($prime, 16); + $base = new BigInteger(2); + $a = DH::createParameters($prime, $base); + $a = str_replace("\r\n", "\n", trim($a)); + $b = '-----BEGIN DH PARAMETERS----- +MIGHAoGBAP//////////yQ/aoiFowjTExmKLgNwc0SkCTgiKZ8x0Agu+pjsTmyJR +Sgh5jjQE3e+VGbPNOkMbMCsKbfJfFDdP4TVtbVHCReSFtXZiXn7G9ExC6aY37WsL +/1y29Aa37e44a/taiZ+lrp8kEXxLH+ZJKGZR7OZTgf//////////AgEC +-----END DH PARAMETERS-----'; + $this->assertSame($b, "$a"); + } + + public function testCreateKey() + { + $param = DH::createParameters('diffie-hellman-group1-sha1'); + $key = DH::createKey($param); + $this->assertInternalType('string', "$key"); + $this->assertInternalType('string', (string) $key->getPublicKey()); + } + + public function testLoadPrivate() + { + $a = DH::load('-----BEGIN PRIVATE KEY----- +MIIBIgIBADCBlQYJKoZIhvcNAQMBMIGHAoGBAP//////////yQ/aoiFowjTExmKL +gNwc0SkCTgiKZ8x0Agu+pjsTmyJRSgh5jjQE3e+VGbPNOkMbMCsKbfJfFDdP4TVt +bVHCReSFtXZiXn7G9ExC6aY37WsL/1y29Aa37e44a/taiZ+lrp8kEXxLH+ZJKGZR +7OZTgf//////////AgECBIGEAoGBALJhtp0aNlkpKTcY1qj519XB8CPc7aZii0xV +bbb/3R93sweVmk2PlkSqxc2kdcofhL8Ev0DJKxB40Ipdqja71VoBbDZ2nMzS0J6s +b6R8Z19Xazc0wq+p/wqalmnuCMBUBuuQ8aNNaW8FGwFwAI3I6CuQSsKObVDJO25m +eKDXQq5i +-----END PRIVATE KEY-----'); + $this->assertInstanceOf(PrivateKey::class, $a); + $this->assertInstanceOf(PublicKey::class, $a->getPublicKey()); + $this->assertInstanceOf(Parameters::class, $a->getParameters()); + } + + public function testLoadPublic() + { + $a = DH::load('-----BEGIN PUBLIC KEY----- +MIIBHzCBlQYJKoZIhvcNAQMBMIGHAoGBAP//////////yQ/aoiFowjTExmKLgNwc +0SkCTgiKZ8x0Agu+pjsTmyJRSgh5jjQE3e+VGbPNOkMbMCsKbfJfFDdP4TVtbVHC +ReSFtXZiXn7G9ExC6aY37WsL/1y29Aa37e44a/taiZ+lrp8kEXxLH+ZJKGZR7OZT +gf//////////AgECA4GEAAKBgCsa1YmlaQIvbOuIi/6DKr7jsdMcv50u/Opemca5 +i2REGZNPWmF3SRPrtq/4urrDRU0F2eQks7qnTkrauPK1/UvE1gwbqWrWgBko+6L+ +Q3ADAIcv9LEmTBnSAOsCs1K9ExAmSv/T2/4+9dW28UYb+p/uV477d1wf+nCWS6VU +/gTm +-----END PUBLIC KEY-----'); + $this->assertInstanceOf(PublicKey::class, $a); + } + + public function testLoadParameters() + { + $a = DH::load('-----BEGIN DH PARAMETERS----- +MIGHAoGBAP//////////yQ/aoiFowjTExmKLgNwc0SkCTgiKZ8x0Agu+pjsTmyJR +Sgh5jjQE3e+VGbPNOkMbMCsKbfJfFDdP4TVtbVHCReSFtXZiXn7G9ExC6aY37WsL +/1y29Aa37e44a/taiZ+lrp8kEXxLH+ZJKGZR7OZTgf//////////AgEC +-----END DH PARAMETERS-----'); + $this->assertInstanceOf(Parameters::class, $a); + } + + public function testComputeSecretWithPublicKey() + { + $ourPriv = DH::load('-----BEGIN PRIVATE KEY----- +MIIBIgIBADCBlQYJKoZIhvcNAQMBMIGHAoGBAP//////////yQ/aoiFowjTExmKL +gNwc0SkCTgiKZ8x0Agu+pjsTmyJRSgh5jjQE3e+VGbPNOkMbMCsKbfJfFDdP4TVt +bVHCReSFtXZiXn7G9ExC6aY37WsL/1y29Aa37e44a/taiZ+lrp8kEXxLH+ZJKGZR +7OZTgf//////////AgECBIGEAoGBALJhtp0aNlkpKTcY1qj519XB8CPc7aZii0xV +bbb/3R93sweVmk2PlkSqxc2kdcofhL8Ev0DJKxB40Ipdqja71VoBbDZ2nMzS0J6s +b6R8Z19Xazc0wq+p/wqalmnuCMBUBuuQ8aNNaW8FGwFwAI3I6CuQSsKObVDJO25m +eKDXQq5i +-----END PRIVATE KEY-----'); + $theirPub = DH::load('-----BEGIN PUBLIC KEY----- +MIIBHzCBlQYJKoZIhvcNAQMBMIGHAoGBAP//////////yQ/aoiFowjTExmKLgNwc +0SkCTgiKZ8x0Agu+pjsTmyJRSgh5jjQE3e+VGbPNOkMbMCsKbfJfFDdP4TVtbVHC +ReSFtXZiXn7G9ExC6aY37WsL/1y29Aa37e44a/taiZ+lrp8kEXxLH+ZJKGZR7OZT +gf//////////AgECA4GEAAKBgCsa1YmlaQIvbOuIi/6DKr7jsdMcv50u/Opemca5 +i2REGZNPWmF3SRPrtq/4urrDRU0F2eQks7qnTkrauPK1/UvE1gwbqWrWgBko+6L+ +Q3ADAIcv9LEmTBnSAOsCs1K9ExAmSv/T2/4+9dW28UYb+p/uV477d1wf+nCWS6VU +/gTm +-----END PUBLIC KEY-----'); + $this->assertInternalType('string', DH::computeSecret($ourPriv, $theirPub)); + } + + public function testComputeSecret() + { + // Ed25519 isn't normally used for DH (that honor goes to Curve25519) but that's not to say it can't + // be used + $curves = ['nistp256', 'curve25519', 'Ed25519']; + foreach ($curves as $curve) { + $ourPriv = EC::createKey($curve); + $theirPub = EC::createKey($curve)->getPublicKey(); + $this->assertInternalType('string', DH::computeSecret($ourPriv, $theirPub)); + } + } + + public function testEphemeralECDH() + { + // an RSA like hybrid cryptosystem can be done with ephemeral key ECDH + + $plaintext = 'hello, world!'; + + $ourEphemeralPrivate = EC::createKey('Curve25519'); + $ourEphemeralPublic = $ourEphemeralPrivate->getPublicKey(); + + $theirPrivate = EC::createKey('Curve25519'); + $theirPublic = $theirPrivate->getPublicKey(); + + $key = DH::computeSecret($ourEphemeralPrivate, $theirPublic); + + $aes = new AES('ctr'); + $aes->setKey(substr($key, 0, 16)); + $aes->setIV(substr($key, 16, 16)); + + $encrypted = + $ourEphemeralPublic->toString('Curve25519Public') . + $aes->encrypt($plaintext); + + $theirPublic = substr($encrypted, 0, 32); + $theirPublic = EC::loadFormat('Curve25519Public', $theirPublic); + + $ourPrivate = $theirPrivate; + + $key = DH::computeSecret($ourPrivate, $theirPublic); + + $aes = new AES('ctr'); + $aes->setKey(substr($key, 0, 16)); + $aes->setIV(substr($key, 16, 16)); + + $this->assertSame($plaintext, $aes->decrypt(substr($encrypted, 32))); + } + + public function testMultiPartyDH() + { + // in multi party (EC)DH everyone, for each public key, everyone (save for the public key owner) "applies" + // their private key to it. they do so in series (as opposed to in parallel) and then everyone winds up + // with the same shared secret + + $numParties = 4; + + // create private keys + $parties = []; + for ($i = 0; $i < $numParties; $i++) { + $parties[] = EC::createKey('Curve25519'); + } + + // create shared secrets + $secrets = []; + for ($i = 0; $i < $numParties; $i++) { + $secrets[$i] = $parties[$i]->getPublicKey(); + for ($j = 0; $j < $numParties; $j++) { + if ($i == $j) { + continue; + } + $secrets[$i] = DH::computeSecret($parties[$j], $secrets[$i]); + } + } + + for ($i = 1; $i < $numParties; $i++) { + $this->assertSame($secrets[0], $secrets[$i]); + } + } +} diff --git a/tests/Unit/Crypt/EC/CurveTest.php b/tests/Unit/Crypt/EC/CurveTest.php index b0166547..ada1590f 100644 --- a/tests/Unit/Crypt/EC/CurveTest.php +++ b/tests/Unit/Crypt/EC/CurveTest.php @@ -47,6 +47,9 @@ class Unit_Crypt_EC_CurveTest extends PhpseclibTestCase continue; } $testName = $file->getBasename('.php'); + if ($testName == 'Curve25519' || $testName == 'Curve448') { + continue; + } $class = 'phpseclib\Crypt\EC\Curves\\' . $testName; $reflect = new \ReflectionClass($class); if ($reflect->isFinal()) { @@ -66,6 +69,9 @@ class Unit_Crypt_EC_CurveTest extends PhpseclibTestCase continue; } $testName = $file->getBasename('.php'); + if ($testName == 'Curve25519' || $testName == 'Curve448') { + continue; + } $curves[] = [$testName]; }