1
0
mirror of https://github.com/danog/phpseclib.git synced 2024-11-30 04:39:21 +01:00

add a new diffie-hellman key exchange class

This commit is contained in:
terrafrost 2019-07-27 17:28:18 -05:00
parent cb1e6b285c
commit 1e7453b585
22 changed files with 1778 additions and 114 deletions

View File

@ -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);
}
}
}

401
phpseclib/Crypt/DH.php Normal file
View File

@ -0,0 +1,401 @@
<?php
/**
* Pure-PHP (EC)DH implementation
*
* PHP version 5
*
* Here's an example of how to compute a shared secret with this library:
* <code>
* <?php
* include 'vendor/autoload.php';
*
* $ourPrivate = \phpseclib\Crypt\DH::createKey();
* $secret = DH::computeSecret($ourPrivate, $theirPublic);
*
* ?>
* </code>
*
* @category Crypt
* @package DH
* @author Jim Wigginton <terrafrost@php.net>
* @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 <terrafrost@php.net>
* @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');
}
}

View File

@ -0,0 +1,83 @@
<?php
/**
* "PKCS1" Formatted EC Key Handler
*
* PHP version 5
*
* Processes keys with the following headers:
*
* -----BEGIN DH PARAMETERS-----
*
* Technically, PKCS1 is for RSA keys, only, but we're using PKCS1 to describe
* DSA, whose format isn't really formally described anywhere, so might as well
* use it to describe this, too.
*
* @category Crypt
* @package DH
* @author Jim Wigginton <terrafrost@php.net>
* @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 <terrafrost@php.net>
* @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";
}
}

View File

@ -0,0 +1,156 @@
<?php
/**
* PKCS#8 Formatted DH Key Handler
*
* PHP version 5
*
* Processes keys with the following headers:
*
* -----BEGIN ENCRYPTED PRIVATE KEY-----
* -----BEGIN PRIVATE KEY-----
* -----BEGIN PUBLIC KEY-----
*
* @category Crypt
* @package DH
* @author Jim Wigginton <terrafrost@php.net>
* @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 <terrafrost@php.net>
* @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);
}
}

View File

@ -0,0 +1,40 @@
<?php
/**
* DH Parameters
*
* @category Crypt
* @package DH
* @author Jim Wigginton <terrafrost@php.net>
* @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 <terrafrost@php.net>
* @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);
}
}

View File

@ -0,0 +1,82 @@
<?php
/**
* DH Private Key
*
* @category Crypt
* @package DH
* @author Jim Wigginton <terrafrost@php.net>
* @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 <terrafrost@php.net>
* @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);
}
}

View File

@ -0,0 +1,53 @@
<?php
/**
* DH Public Key
*
* @category Crypt
* @package DH
* @author Jim Wigginton <terrafrost@php.net>
* @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 <terrafrost@php.net>
* @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;
}
}

View File

@ -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();
}
}

View File

@ -0,0 +1,283 @@
<?php
/**
* Curves over y^2 = x^3 + a*x + x
*
* Technically, a Montgomery curve has a coefficient for y^2 but for Curve25519 and Curve448 that
* coefficient is 1.
*
* Curve25519 and Curve448 do not make use of the y coordinate, which makes it unsuitable for use
* with ECDSA / EdDSA. A few other differences between Curve25519 and Ed25519 are discussed at
* https://crypto.stackexchange.com/a/43058/4520
*
* More info:
*
* https://en.wikipedia.org/wiki/Montgomery_curve
*
* PHP version 5 and 7
*
* @category Crypt
* @package EC
* @author Jim Wigginton <terrafrost@php.net>
* @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 <terrafrost@php.net>
* @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)];
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,64 @@
<?php
/**
* Curve25519
*
* PHP version 5 and 7
*
* @category Crypt
* @package EC
* @author Jim Wigginton <terrafrost@php.net>
* @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);
}
}

View File

@ -0,0 +1,92 @@
<?php
/**
* Curve25519 Private Key Handler
*
* "Naked" Curve25519 private keys can pretty much be any sequence of random 32x bytes so unless
* we have a "hidden" key handler pretty much every 32 byte string will be loaded as a curve25519
* private key even if it probably isn't one by PublicKeyLoader.
*
* "Naked" Curve25519 public keys also a string of 32 bytes so distinguishing between a "naked"
* curve25519 private key and a public key is nigh impossible, hence separate plugins for each
*
* PHP version 5
*
* @category Crypt
* @package EC
* @author Jim Wigginton <terrafrost@php.net>
* @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 <terrafrost@php.net>
* @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);
}
}

View File

@ -0,0 +1,68 @@
<?php
/**
* Curve25519 Public Key Handler
*
* PHP version 5
*
* @category Crypt
* @package EC
* @author Jim Wigginton <terrafrost@php.net>
* @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 <terrafrost@php.net>
* @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));
}
}

View File

@ -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');
}

View File

@ -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,

View File

@ -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)) {

View File

@ -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) {

View File

@ -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) {

View File

@ -0,0 +1,42 @@
<?php
/**
* DHParameter
*
* From: https://www.teletrust.de/fileadmin/files/oid/oid_pkcs-3v1-4.pdf#page=6
*
* PHP version 5
*
* @category File
* @package ASN1
* @author Jim Wigginton <terrafrost@php.net>
* @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 <terrafrost@php.net>
* @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
]
]
];
}

View File

@ -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;
}

203
tests/Unit/Crypt/DHTest.php Normal file
View File

@ -0,0 +1,203 @@
<?php
/**
* @author Andreas Fischer <bantu@phpbb.com>
* @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]);
}
}
}

View File

@ -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];
}