2023-03-11 12:04:29 +03:00
|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* Zend Framework (http://framework.zend.com/)
|
|
|
|
*
|
|
|
|
* @link http://github.com/zendframework/zf2 for the canonical source repository
|
2023-04-01 09:03:34 +03:00
|
|
|
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
|
2023-03-11 12:04:29 +03:00
|
|
|
* @license http://framework.zend.com/license/new-bsd New BSD License
|
|
|
|
*/
|
|
|
|
|
|
|
|
namespace Zend\Crypt\PublicKey;
|
|
|
|
|
|
|
|
use Zend\Crypt\Exception;
|
|
|
|
use Zend\Math;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* PHP implementation of the Diffie-Hellman public key encryption algorithm.
|
|
|
|
* Allows two unassociated parties to establish a joint shared secret key
|
|
|
|
* to be used in encrypting subsequent communications.
|
|
|
|
*/
|
|
|
|
class DiffieHellman
|
|
|
|
{
|
|
|
|
const DEFAULT_KEY_SIZE = 2048;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Key formats
|
|
|
|
*/
|
|
|
|
const FORMAT_BINARY = 'binary';
|
|
|
|
const FORMAT_NUMBER = 'number';
|
|
|
|
const FORMAT_BTWOC = 'btwoc';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Static flag to select whether to use PHP5.3's openssl extension
|
|
|
|
* if available.
|
|
|
|
*
|
|
|
|
* @var bool
|
|
|
|
*/
|
|
|
|
public static $useOpenssl = true;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Default large prime number; required by the algorithm.
|
|
|
|
*
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
private $prime = null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The default generator number. This number must be greater than 0 but
|
|
|
|
* less than the prime number set.
|
|
|
|
*
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
private $generator = null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A private number set by the local user. It's optional and will
|
|
|
|
* be generated if not set.
|
|
|
|
*
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
private $privateKey = null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* BigInteger support object courtesy of Zend\Math
|
|
|
|
*
|
|
|
|
* @var \Zend\Math\BigInteger\Adapter\AdapterInterface
|
|
|
|
*/
|
|
|
|
private $math = null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The public key generated by this instance after calling generateKeys().
|
|
|
|
*
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
private $publicKey = null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The shared secret key resulting from a completed Diffie Hellman
|
|
|
|
* exchange
|
|
|
|
*
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
private $secretKey = null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var resource
|
|
|
|
*/
|
|
|
|
protected $opensslKeyResource = null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Constructor; if set construct the object using the parameter array to
|
|
|
|
* set values for Prime, Generator and Private.
|
|
|
|
* If a Private Key is not set, one will be generated at random.
|
|
|
|
*
|
|
|
|
* @param string $prime
|
|
|
|
* @param string $generator
|
|
|
|
* @param string $privateKey
|
|
|
|
* @param string $privateKeyFormat
|
|
|
|
*/
|
|
|
|
public function __construct($prime, $generator, $privateKey = null, $privateKeyFormat = self::FORMAT_NUMBER)
|
|
|
|
{
|
|
|
|
$this->setPrime($prime);
|
|
|
|
$this->setGenerator($generator);
|
|
|
|
if ($privateKey !== null) {
|
|
|
|
$this->setPrivateKey($privateKey, $privateKeyFormat);
|
|
|
|
}
|
|
|
|
|
|
|
|
// set up BigInteger adapter
|
|
|
|
$this->math = Math\BigInteger\BigInteger::factory();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set whether to use openssl extension
|
|
|
|
*
|
|
|
|
* @static
|
|
|
|
* @param bool $flag
|
|
|
|
*/
|
|
|
|
public static function useOpensslExtension($flag = true)
|
|
|
|
{
|
|
|
|
static::$useOpenssl = (bool) $flag;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate own public key. If a private number has not already been set,
|
|
|
|
* one will be generated at this stage.
|
|
|
|
*
|
|
|
|
* @return DiffieHellman
|
|
|
|
* @throws \Zend\Crypt\Exception\RuntimeException
|
|
|
|
*/
|
|
|
|
public function generateKeys()
|
|
|
|
{
|
|
|
|
if (function_exists('openssl_dh_compute_key') && static::$useOpenssl !== false) {
|
|
|
|
$details = array(
|
|
|
|
'p' => $this->convert($this->getPrime(), self::FORMAT_NUMBER, self::FORMAT_BINARY),
|
|
|
|
'g' => $this->convert($this->getGenerator(), self::FORMAT_NUMBER, self::FORMAT_BINARY)
|
|
|
|
);
|
|
|
|
if ($this->hasPrivateKey()) {
|
|
|
|
$details['priv_key'] = $this->convert(
|
|
|
|
$this->privateKey,
|
|
|
|
self::FORMAT_NUMBER,
|
|
|
|
self::FORMAT_BINARY
|
|
|
|
);
|
|
|
|
$opensslKeyResource = openssl_pkey_new(array('dh' => $details));
|
|
|
|
} else {
|
|
|
|
$opensslKeyResource = openssl_pkey_new(array(
|
|
|
|
'dh' => $details,
|
|
|
|
'private_key_bits' => self::DEFAULT_KEY_SIZE,
|
|
|
|
'private_key_type' => OPENSSL_KEYTYPE_DH
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (false === $opensslKeyResource) {
|
|
|
|
throw new Exception\RuntimeException(
|
|
|
|
'Can not generate new key; openssl ' . openssl_error_string()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
$data = openssl_pkey_get_details($opensslKeyResource);
|
|
|
|
|
|
|
|
$this->setPrivateKey($data['dh']['priv_key'], self::FORMAT_BINARY);
|
|
|
|
$this->setPublicKey($data['dh']['pub_key'], self::FORMAT_BINARY);
|
|
|
|
|
|
|
|
$this->opensslKeyResource = $opensslKeyResource;
|
|
|
|
} else {
|
|
|
|
// Private key is lazy generated in the absence of ext/openssl
|
|
|
|
$publicKey = $this->math->powmod($this->getGenerator(), $this->getPrivateKey(), $this->getPrime());
|
|
|
|
$this->setPublicKey($publicKey);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Setter for the value of the public number
|
|
|
|
*
|
|
|
|
* @param string $number
|
|
|
|
* @param string $format
|
|
|
|
* @return DiffieHellman
|
|
|
|
* @throws \Zend\Crypt\Exception\InvalidArgumentException
|
|
|
|
*/
|
|
|
|
public function setPublicKey($number, $format = self::FORMAT_NUMBER)
|
|
|
|
{
|
|
|
|
$number = $this->convert($number, $format, self::FORMAT_NUMBER);
|
|
|
|
if (!preg_match('/^\d+$/', $number)) {
|
|
|
|
throw new Exception\InvalidArgumentException('Invalid parameter; not a positive natural number');
|
|
|
|
}
|
|
|
|
$this->publicKey = (string) $number;
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns own public key for communication to the second party to this transaction
|
|
|
|
*
|
|
|
|
* @param string $format
|
|
|
|
* @return string
|
|
|
|
* @throws \Zend\Crypt\Exception\InvalidArgumentException
|
|
|
|
*/
|
|
|
|
public function getPublicKey($format = self::FORMAT_NUMBER)
|
|
|
|
{
|
|
|
|
if ($this->publicKey === null) {
|
|
|
|
throw new Exception\InvalidArgumentException(
|
|
|
|
'A public key has not yet been generated using a prior call to generateKeys()'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->convert($this->publicKey, self::FORMAT_NUMBER, $format);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Compute the shared secret key based on the public key received from the
|
|
|
|
* the second party to this transaction. This should agree to the secret
|
|
|
|
* key the second party computes on our own public key.
|
|
|
|
* Once in agreement, the key is known to only to both parties.
|
|
|
|
* By default, the function expects the public key to be in binary form
|
|
|
|
* which is the typical format when being transmitted.
|
|
|
|
*
|
|
|
|
* If you need the binary form of the shared secret key, call
|
|
|
|
* getSharedSecretKey() with the optional parameter for Binary output.
|
|
|
|
*
|
|
|
|
* @param string $publicKey
|
|
|
|
* @param string $publicKeyFormat
|
|
|
|
* @param string $secretKeyFormat
|
|
|
|
* @return string
|
|
|
|
* @throws \Zend\Crypt\Exception\InvalidArgumentException
|
|
|
|
* @throws \Zend\Crypt\Exception\RuntimeException
|
|
|
|
*/
|
|
|
|
public function computeSecretKey(
|
|
|
|
$publicKey,
|
|
|
|
$publicKeyFormat = self::FORMAT_NUMBER,
|
|
|
|
$secretKeyFormat = self::FORMAT_NUMBER
|
|
|
|
) {
|
|
|
|
if (function_exists('openssl_dh_compute_key') && static::$useOpenssl !== false) {
|
|
|
|
$publicKey = $this->convert($publicKey, $publicKeyFormat, self::FORMAT_BINARY);
|
|
|
|
$secretKey = openssl_dh_compute_key($publicKey, $this->opensslKeyResource);
|
|
|
|
if (false === $secretKey) {
|
|
|
|
throw new Exception\RuntimeException(
|
|
|
|
'Can not compute key; openssl ' . openssl_error_string()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
$this->secretKey = $this->convert($secretKey, self::FORMAT_BINARY, self::FORMAT_NUMBER);
|
|
|
|
} else {
|
|
|
|
$publicKey = $this->convert($publicKey, $publicKeyFormat, self::FORMAT_NUMBER);
|
|
|
|
if (!preg_match('/^\d+$/', $publicKey)) {
|
|
|
|
throw new Exception\InvalidArgumentException(
|
|
|
|
'Invalid parameter; not a positive natural number'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
$this->secretKey = $this->math->powmod($publicKey, $this->getPrivateKey(), $this->getPrime());
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->getSharedSecretKey($secretKeyFormat);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return the computed shared secret key from the DiffieHellman transaction
|
|
|
|
*
|
|
|
|
* @param string $format
|
|
|
|
* @return string
|
|
|
|
* @throws \Zend\Crypt\Exception\InvalidArgumentException
|
|
|
|
*/
|
|
|
|
public function getSharedSecretKey($format = self::FORMAT_NUMBER)
|
|
|
|
{
|
|
|
|
if (!isset($this->secretKey)) {
|
|
|
|
throw new Exception\InvalidArgumentException(
|
|
|
|
'A secret key has not yet been computed; call computeSecretKey() first'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->convert($this->secretKey, self::FORMAT_NUMBER, $format);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Setter for the value of the prime number
|
|
|
|
*
|
|
|
|
* @param string $number
|
|
|
|
* @return DiffieHellman
|
|
|
|
* @throws \Zend\Crypt\Exception\InvalidArgumentException
|
|
|
|
*/
|
|
|
|
public function setPrime($number)
|
|
|
|
{
|
|
|
|
if (!preg_match('/^\d+$/', $number) || $number < 11) {
|
|
|
|
throw new Exception\InvalidArgumentException(
|
|
|
|
'Invalid parameter; not a positive natural number or too small: ' .
|
|
|
|
'should be a large natural number prime'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
$this->prime = (string) $number;
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Getter for the value of the prime number
|
|
|
|
*
|
|
|
|
* @param string $format
|
|
|
|
* @return string
|
|
|
|
* @throws \Zend\Crypt\Exception\InvalidArgumentException
|
|
|
|
*/
|
|
|
|
public function getPrime($format = self::FORMAT_NUMBER)
|
|
|
|
{
|
|
|
|
if (!isset($this->prime)) {
|
|
|
|
throw new Exception\InvalidArgumentException('No prime number has been set');
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->convert($this->prime, self::FORMAT_NUMBER, $format);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Setter for the value of the generator number
|
|
|
|
*
|
|
|
|
* @param string $number
|
|
|
|
* @return DiffieHellman
|
|
|
|
* @throws \Zend\Crypt\Exception\InvalidArgumentException
|
|
|
|
*/
|
|
|
|
public function setGenerator($number)
|
|
|
|
{
|
|
|
|
if (!preg_match('/^\d+$/', $number) || $number < 2) {
|
|
|
|
throw new Exception\InvalidArgumentException(
|
|
|
|
'Invalid parameter; not a positive natural number greater than 1'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
$this->generator = (string) $number;
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Getter for the value of the generator number
|
|
|
|
*
|
|
|
|
* @param string $format
|
|
|
|
* @return string
|
|
|
|
* @throws \Zend\Crypt\Exception\InvalidArgumentException
|
|
|
|
*/
|
|
|
|
public function getGenerator($format = self::FORMAT_NUMBER)
|
|
|
|
{
|
|
|
|
if (!isset($this->generator)) {
|
|
|
|
throw new Exception\InvalidArgumentException('No generator number has been set');
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->convert($this->generator, self::FORMAT_NUMBER, $format);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Setter for the value of the private number
|
|
|
|
*
|
|
|
|
* @param string $number
|
|
|
|
* @param string $format
|
|
|
|
* @return DiffieHellman
|
|
|
|
* @throws \Zend\Crypt\Exception\InvalidArgumentException
|
|
|
|
*/
|
|
|
|
public function setPrivateKey($number, $format = self::FORMAT_NUMBER)
|
|
|
|
{
|
|
|
|
$number = $this->convert($number, $format, self::FORMAT_NUMBER);
|
|
|
|
if (!preg_match('/^\d+$/', $number)) {
|
|
|
|
throw new Exception\InvalidArgumentException('Invalid parameter; not a positive natural number');
|
|
|
|
}
|
|
|
|
$this->privateKey = (string) $number;
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Getter for the value of the private number
|
|
|
|
*
|
|
|
|
* @param string $format
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function getPrivateKey($format = self::FORMAT_NUMBER)
|
|
|
|
{
|
|
|
|
if (!$this->hasPrivateKey()) {
|
|
|
|
$this->setPrivateKey($this->generatePrivateKey(), self::FORMAT_BINARY);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->convert($this->privateKey, self::FORMAT_NUMBER, $format);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check whether a private key currently exists.
|
|
|
|
*
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function hasPrivateKey()
|
|
|
|
{
|
|
|
|
return isset($this->privateKey);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Convert number between formats
|
|
|
|
*
|
|
|
|
* @param string $number
|
|
|
|
* @param string $inputFormat
|
|
|
|
* @param string $outputFormat
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
protected function convert($number, $inputFormat = self::FORMAT_NUMBER, $outputFormat = self::FORMAT_BINARY)
|
|
|
|
{
|
|
|
|
if ($inputFormat == $outputFormat) {
|
|
|
|
return $number;
|
|
|
|
}
|
|
|
|
|
|
|
|
// convert to number
|
|
|
|
switch ($inputFormat) {
|
|
|
|
case self::FORMAT_BINARY:
|
|
|
|
case self::FORMAT_BTWOC:
|
|
|
|
$number = $this->math->binToInt($number);
|
|
|
|
break;
|
|
|
|
case self::FORMAT_NUMBER:
|
|
|
|
default:
|
|
|
|
// do nothing
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// convert to output format
|
|
|
|
switch ($outputFormat) {
|
|
|
|
case self::FORMAT_BINARY:
|
|
|
|
return $this->math->intToBin($number);
|
|
|
|
case self::FORMAT_BTWOC:
|
|
|
|
return $this->math->intToBin($number, true);
|
|
|
|
case self::FORMAT_NUMBER:
|
|
|
|
default:
|
|
|
|
return $number;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* In the event a private number/key has not been set by the user,
|
|
|
|
* or generated by ext/openssl, a best attempt will be made to
|
|
|
|
* generate a random key. Having a random number generator installed
|
|
|
|
* on linux/bsd is highly recommended! The alternative is not recommended
|
|
|
|
* for production unless without any other option.
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
protected function generatePrivateKey()
|
|
|
|
{
|
|
|
|
return Math\Rand::getBytes(strlen($this->getPrime()), true);
|
|
|
|
}
|
|
|
|
}
|