Viel neues

This commit is contained in:
Sven Steinert
2026-04-30 12:06:00 +02:00
parent 118809bfae
commit fce31ebcd7
1274 changed files with 181255 additions and 0 deletions

View File

@@ -0,0 +1,130 @@
<?php
/**
* JSON Web Key (RFC7517) Formatted RSA Handler
*
* PHP version 5
*
* @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
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\RSA\Formats\Keys;
use phpseclib3\Common\Functions\Strings;
use phpseclib3\Crypt\Common\Formats\Keys\JWK as Progenitor;
use phpseclib3\Math\BigInteger;
/**
* JWK Formatted RSA Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class JWK extends Progenitor
{
/**
* Break a public or private key down into its constituent components
*
* @param string|array $key
*/
public static function load($key, ?string $password = null): array
{
$key = parent::loadHelper($key);
if ($key->kty != 'RSA') {
throw new \RuntimeException('Only RSA JWK keys are supported');
}
$count = $publicCount = 0;
$vars = ['n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'qi'];
foreach ($vars as $var) {
if (!isset($key->$var) || !is_string($key->$var)) {
continue;
}
$count++;
$value = new BigInteger(Strings::base64url_decode($key->$var), 256);
switch ($var) {
case 'n':
$publicCount++;
$components['modulus'] = $value;
break;
case 'e':
$publicCount++;
$components['publicExponent'] = $value;
break;
case 'd':
$components['privateExponent'] = $value;
break;
case 'p':
$components['primes'][1] = $value;
break;
case 'q':
$components['primes'][2] = $value;
break;
case 'dp':
$components['exponents'][1] = $value;
break;
case 'dq':
$components['exponents'][2] = $value;
break;
case 'qi':
$components['coefficients'][2] = $value;
}
}
if ($count == count($vars)) {
return $components + ['isPublicKey' => false];
}
if ($count == 2 && $publicCount == 2) {
return $components + ['isPublicKey' => true];
}
throw new \UnexpectedValueException('Key does not have an appropriate number of RSA parameters');
}
/**
* Convert a private key to the appropriate format.
*
* @param string $password optional
* @param array $options optional
*/
public static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, array $primes, array $exponents, array $coefficients, ?string $password = null, array $options = []): string
{
if (count($primes) != 2) {
throw new \InvalidArgumentException('JWK does not support multi-prime RSA keys');
}
$key = [
'kty' => 'RSA',
'n' => Strings::base64url_encode($n->toBytes()),
'e' => Strings::base64url_encode($e->toBytes()),
'd' => Strings::base64url_encode($d->toBytes()),
'p' => Strings::base64url_encode($primes[1]->toBytes()),
'q' => Strings::base64url_encode($primes[2]->toBytes()),
'dp' => Strings::base64url_encode($exponents[1]->toBytes()),
'dq' => Strings::base64url_encode($exponents[2]->toBytes()),
'qi' => Strings::base64url_encode($coefficients[2]->toBytes()),
];
return self::wrapKey($key, $options);
}
/**
* Convert a public key to the appropriate format
*/
public static function savePublicKey(BigInteger $n, BigInteger $e, array $options = []): string
{
$key = [
'kty' => 'RSA',
'n' => Strings::base64url_encode($n->toBytes()),
'e' => Strings::base64url_encode($e->toBytes()),
];
return self::wrapKey($key, $options);
}
}

View File

@@ -0,0 +1,210 @@
<?php
/**
* Miccrosoft BLOB Formatted RSA Key Handler
*
* More info:
*
* https://msdn.microsoft.com/en-us/library/windows/desktop/aa375601(v=vs.85).aspx
*
* PHP version 5
*
* @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
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\RSA\Formats\Keys;
use phpseclib3\Common\Functions\Strings;
use phpseclib3\Exception\InvalidArgumentException;
use phpseclib3\Exception\UnexpectedValueException;
use phpseclib3\Exception\UnsupportedFormatException;
use phpseclib3\Math\BigInteger;
/**
* Microsoft BLOB Formatted RSA Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class MSBLOB
{
/**
* Public/Private Key Pair
*/
public const PRIVATEKEYBLOB = 0x7;
/**
* Public Key
*/
public const PUBLICKEYBLOB = 0x6;
/**
* Public Key
*/
public const PUBLICKEYBLOBEX = 0xA;
/**
* RSA public key exchange algorithm
*/
public const CALG_RSA_KEYX = 0x0000A400;
/**
* RSA public key exchange algorithm
*/
public const CALG_RSA_SIGN = 0x00002400;
/**
* Public Key
*/
public const RSA1 = 0x31415352;
/**
* Private Key
*/
public const RSA2 = 0x32415352;
/**
* Break a public or private key down into its constituent components
*
* @param string|array $key
*/
public static function load($key, ?string $password = null): array
{
if (!Strings::is_stringable($key)) {
throw new UnexpectedValueException('Key should be a string - not a ' . gettype($key));
}
$key = Strings::base64_decode($key);
if (!is_string($key)) {
throw new UnexpectedValueException('Base64 decoding produced an error');
}
if (strlen($key) < 20) {
throw new UnexpectedValueException('Key appears to be malformed');
}
// PUBLICKEYSTRUC publickeystruc
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa387453(v=vs.85).aspx
extract(unpack('atype/aversion/vreserved/Valgo', Strings::shift($key, 8)));
/**
* @var string $type
* @var string $version
* @var integer $reserved
* @var integer $algo
*/
switch (ord($type)) {
case self::PUBLICKEYBLOB:
case self::PUBLICKEYBLOBEX:
$publickey = true;
break;
case self::PRIVATEKEYBLOB:
$publickey = false;
break;
default:
throw new UnexpectedValueException('Key appears to be malformed');
}
$components = ['isPublicKey' => $publickey];
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa375549(v=vs.85).aspx
switch ($algo) {
case self::CALG_RSA_KEYX:
case self::CALG_RSA_SIGN:
break;
default:
throw new UnexpectedValueException('Key appears to be malformed');
}
// RSAPUBKEY rsapubkey
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa387685(v=vs.85).aspx
// could do V for pubexp but that's unsigned 32-bit whereas some PHP installs only do signed 32-bit
extract(unpack('Vmagic/Vbitlen/a4pubexp', Strings::shift($key, 12)));
/**
* @var integer $magic
* @var integer $bitlen
* @var string $pubexp
*/
switch ($magic) {
case self::RSA2:
$components['isPublicKey'] = false;
// fall-through
case self::RSA1:
break;
default:
throw new UnexpectedValueException('Key appears to be malformed');
}
$baseLength = $bitlen / 16;
if (strlen($key) != 2 * $baseLength && strlen($key) != 9 * $baseLength) {
throw new UnexpectedValueException('Key appears to be malformed');
}
$components[$components['isPublicKey'] ? 'publicExponent' : 'privateExponent'] = new BigInteger(strrev($pubexp), 256);
// BYTE modulus[rsapubkey.bitlen/8]
$components['modulus'] = new BigInteger(strrev(Strings::shift($key, $bitlen / 8)), 256);
if ($publickey) {
return $components;
}
$components['isPublicKey'] = false;
// BYTE prime1[rsapubkey.bitlen/16]
$components['primes'] = [1 => new BigInteger(strrev(Strings::shift($key, $bitlen / 16)), 256)];
// BYTE prime2[rsapubkey.bitlen/16]
$components['primes'][] = new BigInteger(strrev(Strings::shift($key, $bitlen / 16)), 256);
// BYTE exponent1[rsapubkey.bitlen/16]
$components['exponents'] = [1 => new BigInteger(strrev(Strings::shift($key, $bitlen / 16)), 256)];
// BYTE exponent2[rsapubkey.bitlen/16]
$components['exponents'][] = new BigInteger(strrev(Strings::shift($key, $bitlen / 16)), 256);
// BYTE coefficient[rsapubkey.bitlen/16]
$components['coefficients'] = [2 => new BigInteger(strrev(Strings::shift($key, $bitlen / 16)), 256)];
if (isset($components['privateExponent'])) {
$components['publicExponent'] = $components['privateExponent'];
}
// BYTE privateExponent[rsapubkey.bitlen/8]
$components['privateExponent'] = new BigInteger(strrev(Strings::shift($key, $bitlen / 8)), 256);
return $components;
}
/**
* Convert a private key to the appropriate format.
*/
public static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, array $primes, array $exponents, array $coefficients, ?string $password = null): string
{
if (count($primes) != 2) {
throw new InvalidArgumentException('MSBLOB does not support multi-prime RSA keys');
}
if (!empty($password) && is_string($password)) {
throw new UnsupportedFormatException('MSBLOB private keys do not support encryption');
}
$n = strrev($n->toBytes());
$e = str_pad(strrev($e->toBytes()), 4, "\0");
$key = pack('aavV', chr(self::PRIVATEKEYBLOB), chr(2), 0, self::CALG_RSA_KEYX);
$key .= pack('VVa*', self::RSA2, 8 * strlen($n), $e);
$key .= $n;
$key .= strrev($primes[1]->toBytes());
$key .= strrev($primes[2]->toBytes());
$key .= strrev($exponents[1]->toBytes());
$key .= strrev($exponents[2]->toBytes());
$key .= strrev($coefficients[2]->toBytes());
$key .= strrev($d->toBytes());
return Strings::base64_encode($key);
}
/**
* Convert a public key to the appropriate format
*/
public static function savePublicKey(BigInteger $n, BigInteger $e): string
{
$n = strrev($n->toBytes());
$e = str_pad(strrev($e->toBytes()), 4, "\0");
$key = pack('aavV', chr(self::PUBLICKEYBLOB), chr(2), 0, self::CALG_RSA_KEYX);
$key .= pack('VVa*', self::RSA1, 8 * strlen($n), $e);
$key .= $n;
return Strings::base64_encode($key);
}
}

View File

@@ -0,0 +1,120 @@
<?php
/**
* OpenSSH Formatted RSA Key Handler
*
* PHP version 5
*
* Place in $HOME/.ssh/authorized_keys
*
* @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
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\RSA\Formats\Keys;
use phpseclib3\Common\Functions\Strings;
use phpseclib3\Crypt\Common\Formats\Keys\OpenSSH as Progenitor;
use phpseclib3\Exception\RuntimeException;
use phpseclib3\Math\BigInteger;
/**
* OpenSSH Formatted RSA Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class OpenSSH extends Progenitor
{
/**
* Supported Key Types
*
* @var array
*/
protected static $types = ['ssh-rsa'];
/**
* Break a public or private key down into its constituent components
*
* @param string|array $key
*/
public static function load($key, ?string $password = null): array
{
static $one;
if (!isset($one)) {
$one = new BigInteger(1);
}
$parsed = parent::load($key, $password);
if (isset($parsed['paddedKey'])) {
[$type] = Strings::unpackSSH2('s', $parsed['paddedKey']);
if ($type != $parsed['type']) {
throw new RuntimeException("The public and private keys are not of the same type ($type vs $parsed[type])");
}
$primes = $coefficients = [];
[
$modulus,
$publicExponent,
$privateExponent,
$coefficients[2],
$primes[1],
$primes[2],
$comment,
] = Strings::unpackSSH2('i6s', $parsed['paddedKey']);
$temp = $primes[1]->subtract($one);
$exponents = [1 => $publicExponent->modInverse($temp)];
$temp = $primes[2]->subtract($one);
$exponents[] = $publicExponent->modInverse($temp);
$isPublicKey = false;
return compact('publicExponent', 'modulus', 'privateExponent', 'primes', 'coefficients', 'exponents', 'comment', 'isPublicKey');
}
[$publicExponent, $modulus] = Strings::unpackSSH2('ii', $parsed['publicKey']);
return [
'isPublicKey' => true,
'modulus' => $modulus,
'publicExponent' => $publicExponent,
'comment' => $parsed['comment'],
];
}
/**
* Convert a public key to the appropriate format
*
* @param array $options optional
*/
public static function savePublicKey(BigInteger $n, BigInteger $e, array $options = []): string
{
$RSAPublicKey = Strings::packSSH2('sii', 'ssh-rsa', $e, $n);
if ($options['binary'] ?? self::$binary) {
return $RSAPublicKey;
}
$comment = $options['comment'] ?? self::$comment;
$RSAPublicKey = 'ssh-rsa ' . base64_encode($RSAPublicKey) . ' ' . $comment;
return $RSAPublicKey;
}
/**
* Convert a private key to the appropriate format.
*/
public static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, array $primes, array $exponents, array $coefficients, ?string $password = null, array $options = []): string
{
$publicKey = self::savePublicKey($n, $e, ['binary' => true]);
$privateKey = Strings::packSSH2('si6', 'ssh-rsa', $n, $e, $d, $coefficients[2], $primes[1], $primes[2]);
return self::wrapPrivateKey($publicKey, $privateKey, $password, $options);
}
}

View File

@@ -0,0 +1,152 @@
<?php
/**
* PKCS#1 Formatted RSA Key Handler
*
* PHP version 5
*
* Used by File/X509.php
*
* Processes keys with the following headers:
*
* -----BEGIN RSA PRIVATE KEY-----
* -----BEGIN RSA PUBLIC KEY-----
*
* Analogous to ssh-keygen's pem format (as specified by -m)
*
* @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
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\RSA\Formats\Keys;
use phpseclib3\Common\Functions\Strings;
use phpseclib3\Crypt\Common\Formats\Keys\PKCS1 as Progenitor;
use phpseclib3\Exception\RuntimeException;
use phpseclib3\Exception\UnexpectedValueException;
use phpseclib3\File\ASN1;
use phpseclib3\File\ASN1\Maps;
use phpseclib3\Math\BigInteger;
/**
* PKCS#1 Formatted RSA Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class PKCS1 extends Progenitor
{
/**
* Break a public or private key down into its constituent components
*
* @param string|array $key
* @param string|false $password
*/
public static function load($key, ?string $password = null): array
{
if (!Strings::is_stringable($key)) {
throw new UnexpectedValueException('Key should be a string - not a ' . gettype($key));
}
if (str_contains($key, 'PUBLIC')) {
$components = ['isPublicKey' => true];
} elseif (str_contains($key, 'PRIVATE')) {
$components = ['isPublicKey' => false];
} else {
$components = [];
}
$key = parent::load($key, $password);
$decoded = ASN1::decodeBER($key);
if (!$decoded) {
throw new RuntimeException('Unable to decode BER');
}
$key = ASN1::asn1map($decoded[0], Maps\RSAPrivateKey::MAP);
if (is_array($key)) {
$components += [
'modulus' => $key['modulus'],
'publicExponent' => $key['publicExponent'],
'privateExponent' => $key['privateExponent'],
'primes' => [1 => $key['prime1'], $key['prime2']],
'exponents' => [1 => $key['exponent1'], $key['exponent2']],
'coefficients' => [2 => $key['coefficient']],
];
if ($key['version'] == 'multi') {
foreach ($key['otherPrimeInfos'] as $primeInfo) {
$components['primes'][] = $primeInfo['prime'];
$components['exponents'][] = $primeInfo['exponent'];
$components['coefficients'][] = $primeInfo['coefficient'];
}
}
if (!isset($components['isPublicKey'])) {
$components['isPublicKey'] = false;
}
return $components;
}
$key = ASN1::asn1map($decoded[0], Maps\RSAPublicKey::MAP);
if (!is_array($key)) {
throw new RuntimeException('Unable to perform ASN1 mapping');
}
if (!isset($components['isPublicKey'])) {
$components['isPublicKey'] = true;
}
return $components + $key;
}
/**
* Convert a private key to the appropriate format.
*
* @param string|false $password
* @param array $options optional
*/
public static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, array $primes, array $exponents, array $coefficients, ?string $password = null, array $options = []): string
{
$num_primes = count($primes);
$key = [
'version' => $num_primes == 2 ? 'two-prime' : 'multi',
'modulus' => $n,
'publicExponent' => $e,
'privateExponent' => $d,
'prime1' => $primes[1],
'prime2' => $primes[2],
'exponent1' => $exponents[1],
'exponent2' => $exponents[2],
'coefficient' => $coefficients[2],
];
for ($i = 3; $i <= $num_primes; $i++) {
$key['otherPrimeInfos'][] = [
'prime' => $primes[$i],
'exponent' => $exponents[$i],
'coefficient' => $coefficients[$i],
];
}
$key = ASN1::encodeDER($key, Maps\RSAPrivateKey::MAP);
return self::wrapPrivateKey($key, 'RSA', $password, $options);
}
/**
* Convert a public key to the appropriate format
*/
public static function savePublicKey(BigInteger $n, BigInteger $e): string
{
$key = [
'modulus' => $n,
'publicExponent' => $e,
];
$key = ASN1::encodeDER($key, Maps\RSAPublicKey::MAP);
return self::wrapPublicKey($key, 'RSA');
}
}

View File

@@ -0,0 +1,109 @@
<?php
/**
* PKCS#8 Formatted RSA Key Handler
*
* PHP version 5
*
* Used by PHP's openssl_public_encrypt() and openssl's rsautl (when -pubin is set)
*
* Processes keys with the following headers:
*
* -----BEGIN ENCRYPTED PRIVATE KEY-----
* -----BEGIN PRIVATE KEY-----
* -----BEGIN PUBLIC KEY-----
*
* Analogous to ssh-keygen's pkcs8 format (as specified by -m). Although PKCS8
* is specific to private keys it's basically creating a DER-encoded wrapper
* for keys. This just extends that same concept to public keys (much like ssh-keygen)
*
* @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
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\RSA\Formats\Keys;
use phpseclib3\Crypt\Common\Formats\Keys\PKCS8 as Progenitor;
use phpseclib3\File\ASN1;
use phpseclib3\Math\BigInteger;
/**
* PKCS#8 Formatted RSA Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class PKCS8 extends Progenitor
{
/**
* OID Name
*
* @var string
*/
public const OID_NAME = 'rsaEncryption';
/**
* OID Value
*
* @var string
*/
public const OID_VALUE = '1.2.840.113549.1.1.1';
/**
* Child OIDs loaded
*
* @var bool
*/
protected static $childOIDsLoaded = false;
/**
* Break a public or private key down into its constituent components
*
* @param string|array $key
*/
public static function load($key, ?string $password = null): array
{
$key = parent::load($key, $password);
if (isset($key['privateKey'])) {
$components['isPublicKey'] = false;
$type = 'private';
} else {
$components['isPublicKey'] = true;
$type = 'public';
}
$result = $components + PKCS1::load($key[$type . 'Key']);
if (isset($key['meta'])) {
$result['meta'] = $key['meta'];
}
return $result;
}
/**
* Convert a private key to the appropriate format.
*/
public static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, array $primes, array $exponents, array $coefficients, ?string $password = null, array $options = []): string
{
$key = PKCS1::savePrivateKey($n, $e, $d, $primes, $exponents, $coefficients);
$key = ASN1::extractBER($key);
return self::wrapPrivateKey($key, [], null, $password, null, '', $options);
}
/**
* Convert a public key to the appropriate format
*
* @param array $options optional
*/
public static function savePublicKey(BigInteger $n, BigInteger $e, array $options = []): string
{
$key = PKCS1::savePublicKey($n, $e);
$key = ASN1::extractBER($key);
return self::wrapPublicKey($key, null);
}
}

View File

@@ -0,0 +1,225 @@
<?php
/**
* PKCS#8 Formatted RSA-PSS Key Handler
*
* PHP version 5
*
* Used by PHP's openssl_public_encrypt() and openssl's rsautl (when -pubin is set)
*
* Processes keys with the following headers:
*
* -----BEGIN ENCRYPTED PRIVATE KEY-----
* -----BEGIN PRIVATE KEY-----
* -----BEGIN PUBLIC KEY-----
*
* Analogous to "openssl genpkey -algorithm rsa-pss".
*
* @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
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\RSA\Formats\Keys;
use phpseclib3\Common\Functions\Strings;
use phpseclib3\Crypt\Common\Formats\Keys\PKCS8 as Progenitor;
use phpseclib3\Exception\UnexpectedValueException;
use phpseclib3\File\ASN1;
use phpseclib3\File\ASN1\Maps;
use phpseclib3\Math\BigInteger;
/**
* PKCS#8 Formatted RSA-PSS Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class PSS extends Progenitor
{
/**
* OID Name
*
* @var string
*/
public const OID_NAME = 'id-RSASSA-PSS';
/**
* OID Value
*
* @var string
*/
public const OID_VALUE = '1.2.840.113549.1.1.10';
/**
* OIDs loaded
*
* @var bool
*/
private static $oidsLoaded = false;
/**
* Child OIDs loaded
*
* @var bool
*/
protected static $childOIDsLoaded = false;
/**
* Initialize static variables
*/
private static function initialize_static_variables(): void
{
if (!self::$oidsLoaded) {
ASN1::loadOIDs([
'md2' => '1.2.840.113549.2.2',
'md4' => '1.2.840.113549.2.4',
'md5' => '1.2.840.113549.2.5',
'id-sha1' => '1.3.14.3.2.26',
'id-sha256' => '2.16.840.1.101.3.4.2.1',
'id-sha384' => '2.16.840.1.101.3.4.2.2',
'id-sha512' => '2.16.840.1.101.3.4.2.3',
'id-sha224' => '2.16.840.1.101.3.4.2.4',
'id-sha512/224' => '2.16.840.1.101.3.4.2.5',
'id-sha512/256' => '2.16.840.1.101.3.4.2.6',
'id-mgf1' => '1.2.840.113549.1.1.8',
]);
self::$oidsLoaded = true;
}
}
/**
* Break a public or private key down into its constituent components
*
* @param string|array $key
*/
public static function load($key, ?string $password = null): array
{
self::initialize_static_variables();
if (!Strings::is_stringable($key)) {
throw new UnexpectedValueException('Key should be a string - not a ' . gettype($key));
}
$components = ['isPublicKey' => str_contains($key, 'PUBLIC')];
$key = parent::load($key, $password);
$type = isset($key['privateKey']) ? 'private' : 'public';
$result = $components + PKCS1::load($key[$type . 'Key']);
if (isset($key[$type . 'KeyAlgorithm']['parameters'])) {
$decoded = ASN1::decodeBER($key[$type . 'KeyAlgorithm']['parameters']);
if ($decoded === false) {
throw new UnexpectedValueException('Unable to decode parameters');
}
$params = ASN1::asn1map($decoded[0], Maps\RSASSA_PSS_params::MAP);
} else {
$params = [];
}
if (isset($params['maskGenAlgorithm']['parameters'])) {
$decoded = ASN1::decodeBER($params['maskGenAlgorithm']['parameters']);
if ($decoded === false) {
throw new UnexpectedValueException('Unable to decode parameters');
}
$params['maskGenAlgorithm']['parameters'] = ASN1::asn1map($decoded[0], Maps\HashAlgorithm::MAP);
} else {
$params['maskGenAlgorithm'] = [
'algorithm' => 'id-mgf1',
'parameters' => ['algorithm' => 'id-sha1'],
];
}
if (!isset($params['hashAlgorithm']['algorithm'])) {
$params['hashAlgorithm']['algorithm'] = 'id-sha1';
}
$result['hash'] = str_replace('id-', '', $params['hashAlgorithm']['algorithm']);
$result['MGFHash'] = str_replace('id-', '', $params['maskGenAlgorithm']['parameters']['algorithm']);
if (isset($params['saltLength'])) {
$result['saltLength'] = (int) $params['saltLength']->toString();
}
if (isset($key['meta'])) {
$result['meta'] = $key['meta'];
}
return $result;
}
/**
* Convert a private key to the appropriate format.
*/
public static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, array $primes, array $exponents, array $coefficients, ?string $password = null, array $options = []): string
{
self::initialize_static_variables();
$key = PKCS1::savePrivateKey($n, $e, $d, $primes, $exponents, $coefficients);
$key = ASN1::extractBER($key);
$params = self::savePSSParams($options);
return self::wrapPrivateKey($key, [], $params, $password, null, '', $options);
}
/**
* Convert a public key to the appropriate format
*
* @param array $options optional
*/
public static function savePublicKey(BigInteger $n, BigInteger $e, array $options = []): string
{
self::initialize_static_variables();
$key = PKCS1::savePublicKey($n, $e);
$key = ASN1::extractBER($key);
$params = self::savePSSParams($options);
return self::wrapPublicKey($key, $params);
}
/**
* Encodes PSS parameters
*
* @return string
*/
public static function savePSSParams(array $options)
{
/*
The trailerField field is an integer. It provides
compatibility with IEEE Std 1363a-2004 [P1363A]. The value
MUST be 1, which represents the trailer field with hexadecimal
value 0xBC. Other trailer fields, including the trailer field
composed of HashID concatenated with 0xCC that is specified in
IEEE Std 1363a, are not supported. Implementations that
perform signature generation MUST omit the trailerField field,
indicating that the default trailer field value was used.
Implementations that perform signature validation MUST
recognize both a present trailerField field with value 1 and an
absent trailerField field.
source: https://tools.ietf.org/html/rfc4055#page-9
*/
$params = [
'trailerField' => new BigInteger(1),
];
if (isset($options['hash'])) {
$params['hashAlgorithm']['algorithm'] = 'id-' . $options['hash'];
}
if (isset($options['MGFHash'])) {
$temp = ['algorithm' => 'id-' . $options['MGFHash']];
$temp = ASN1::encodeDER($temp, Maps\HashAlgorithm::MAP);
$params['maskGenAlgorithm'] = [
'algorithm' => 'id-mgf1',
'parameters' => new ASN1\Element($temp),
];
}
if (isset($options['saltLength'])) {
$params['saltLength'] = new BigInteger($options['saltLength']);
}
return new ASN1\Element(ASN1::encodeDER($params, Maps\RSASSA_PSS_params::MAP));
}
}

View File

@@ -0,0 +1,111 @@
<?php
/**
* PuTTY Formatted RSA Key Handler
*
* PHP version 5
*
* @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
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\RSA\Formats\Keys;
use phpseclib3\Common\Functions\Strings;
use phpseclib3\Crypt\Common\Formats\Keys\PuTTY as Progenitor;
use phpseclib3\Exception\InvalidArgumentException;
use phpseclib3\Exception\UnexpectedValueException;
use phpseclib3\Math\BigInteger;
/**
* PuTTY Formatted RSA Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class PuTTY extends Progenitor
{
/**
* Public Handler
*
* @var string
*/
public const PUBLIC_HANDLER = 'phpseclib3\Crypt\RSA\Formats\Keys\OpenSSH';
/**
* Algorithm Identifier
*
* @var array
*/
protected static $types = ['ssh-rsa'];
/**
* Break a public or private key down into its constituent components
*
* @param array|string $key
* @param string|false $password
* @return array|false
*/
public static function load($key, $password)
{
static $one;
if (!isset($one)) {
$one = new BigInteger(1);
}
$components = parent::load($key, $password);
if (!isset($components['private'])) {
return $components;
}
extract($components);
unset($components['public'], $components['private']);
$isPublicKey = false;
$result = Strings::unpackSSH2('ii', $public);
if ($result === false) {
throw new UnexpectedValueException('Key appears to be malformed');
}
[$publicExponent, $modulus] = $result;
$result = Strings::unpackSSH2('iiii', $private);
if ($result === false) {
throw new UnexpectedValueException('Key appears to be malformed');
}
$primes = $coefficients = [];
[$privateExponent, $primes[1], $primes[2], $coefficients[2]] = $result;
$temp = $primes[1]->subtract($one);
$exponents = [1 => $publicExponent->modInverse($temp)];
$temp = $primes[2]->subtract($one);
$exponents[] = $publicExponent->modInverse($temp);
return compact('publicExponent', 'modulus', 'privateExponent', 'primes', 'coefficients', 'exponents', 'comment', 'isPublicKey');
}
/**
* Convert a private key to the appropriate format.
*/
public static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, array $primes, array $exponents, array $coefficients, ?string $password = null, array $options = []): string
{
if (count($primes) != 2) {
throw new InvalidArgumentException('PuTTY does not support multi-prime RSA keys');
}
$public = Strings::packSSH2('ii', $e, $n);
$private = Strings::packSSH2('iiii', $d, $primes[1], $primes[2], $coefficients[2]);
return self::wrapPrivateKey($public, $private, 'ssh-rsa', $password, $options);
}
/**
* Convert a public key to the appropriate format
*/
public static function savePublicKey(BigInteger $n, BigInteger $e): string
{
return self::wrapPublicKey(Strings::packSSH2('ii', $e, $n), 'ssh-rsa');
}
}

View File

@@ -0,0 +1,166 @@
<?php
/**
* Raw RSA Key Handler
*
* PHP version 5
*
* An array containing two \phpseclib3\Math\BigInteger objects.
*
* The exponent can be indexed with any of the following:
*
* 0, e, exponent, publicExponent
*
* The modulus can be indexed with any of the following:
*
* 1, n, modulo, modulus
*
* @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
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\RSA\Formats\Keys;
use phpseclib3\Exception\UnexpectedValueException;
use phpseclib3\Exception\UnsupportedFormatException;
use phpseclib3\Math\BigInteger;
/**
* Raw RSA Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class Raw
{
/**
* Break a public or private key down into its constituent components
*
* @param string|array $key
*/
public static function load($key, ?string $password = null): array
{
if (!is_array($key)) {
throw new UnexpectedValueException('Key should be a array - not a ' . gettype($key));
}
$key = array_change_key_case($key, CASE_LOWER);
$components = ['isPublicKey' => false];
foreach (['e', 'exponent', 'publicexponent', 0, 'privateexponent', 'd'] as $index) {
if (isset($key[$index])) {
$components['publicExponent'] = $key[$index];
break;
}
}
foreach (['n', 'modulo', 'modulus', 1] as $index) {
if (isset($key[$index])) {
$components['modulus'] = $key[$index];
break;
}
}
if (!isset($components['publicExponent']) || !isset($components['modulus'])) {
throw new UnexpectedValueException('Modulus / exponent not present');
}
if (isset($key['primes'])) {
$components['primes'] = $key['primes'];
} elseif (isset($key['p']) && isset($key['q'])) {
$indices = [
['p', 'q'],
['prime1', 'prime2'],
];
foreach ($indices as $index) {
[$i0, $i1] = $index;
if (isset($key[$i0]) && isset($key[$i1])) {
$components['primes'] = [1 => $key[$i0], $key[$i1]];
}
}
}
if (isset($key['exponents'])) {
$components['exponents'] = $key['exponents'];
} else {
$indices = [
['dp', 'dq'],
['exponent1', 'exponent2'],
];
foreach ($indices as $index) {
[$i0, $i1] = $index;
if (isset($key[$i0]) && isset($key[$i1])) {
$components['exponents'] = [1 => $key[$i0], $key[$i1]];
}
}
}
if (isset($key['coefficients'])) {
$components['coefficients'] = $key['coefficients'];
} else {
foreach (['inverseq', 'q\'', 'coefficient'] as $index) {
if (isset($key[$index])) {
$components['coefficients'] = [2 => $key[$index]];
}
}
}
if (!isset($components['primes'])) {
$components['isPublicKey'] = true;
return $components;
}
if (!isset($components['exponents'])) {
$one = new BigInteger(1);
$temp = $components['primes'][1]->subtract($one);
$exponents = [1 => $components['publicExponent']->modInverse($temp)];
$temp = $components['primes'][2]->subtract($one);
$exponents[] = $components['publicExponent']->modInverse($temp);
$components['exponents'] = $exponents;
}
if (!isset($components['coefficients'])) {
$components['coefficients'] = [2 => $components['primes'][2]->modInverse($components['primes'][1])];
}
foreach (['privateexponent', 'd'] as $index) {
if (isset($key[$index])) {
$components['privateExponent'] = $key[$index];
break;
}
}
return $components;
}
/**
* Convert a private key to the appropriate format.
*/
public static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, array $primes, array $exponents, array $coefficients, ?string $password = null, array $options = []): string
{
if (!empty($password) && is_string($password)) {
throw new UnsupportedFormatException('Raw private keys do not support encryption');
}
return serialize([
'e' => clone $e,
'n' => clone $n,
'd' => clone $d,
'primes' => array_map(fn ($var) => clone $var, $primes),
'exponents' => array_map(fn ($var) => clone $var, $exponents),
'coefficients' => array_map(fn ($var) => clone $var, $coefficients),
]);
}
/**
* Convert a public key to the appropriate format
*/
public static function savePublicKey(BigInteger $n, BigInteger $e): array
{
return ['e' => clone $e, 'n' => clone $n];
}
}

View File

@@ -0,0 +1,162 @@
<?php
/**
* XML Formatted RSA Key Handler
*
* More info:
*
* http://www.w3.org/TR/xmldsig-core/#sec-RSAKeyValue
* http://www.w3.org/TR/xkms2/#XKMS_2_0_Paragraph_269
* http://en.wikipedia.org/wiki/XML_Signature
* http://en.wikipedia.org/wiki/XKMS
*
* PHP version 5
*
* @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
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\RSA\Formats\Keys;
use phpseclib3\Common\Functions\Strings;
use phpseclib3\Exception\BadConfigurationException;
use phpseclib3\Exception\InvalidArgumentException;
use phpseclib3\Exception\UnexpectedValueException;
use phpseclib3\Exception\UnsupportedFormatException;
use phpseclib3\Math\BigInteger;
/**
* XML Formatted RSA Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class XML
{
/**
* Break a public or private key down into its constituent components
*
* @param string|array $key
*/
public static function load($key): array
{
if (!Strings::is_stringable($key)) {
throw new UnexpectedValueException('Key should be a string - not a ' . gettype($key));
}
if (!class_exists('DOMDocument')) {
throw new BadConfigurationException('The dom extension is not setup correctly on this system');
}
$components = [
'isPublicKey' => false,
'primes' => [],
'exponents' => [],
'coefficients' => [],
];
$use_errors = libxml_use_internal_errors(true);
$dom = new \DOMDocument();
if (substr($key, 0, 5) != '<?xml') {
$key = '<xml>' . $key . '</xml>';
}
if (!$dom->loadXML($key)) {
libxml_use_internal_errors($use_errors);
throw new UnexpectedValueException('Key does not appear to contain XML');
}
$xpath = new \DOMXPath($dom);
$keys = ['modulus', 'exponent', 'p', 'q', 'dp', 'dq', 'inverseq', 'd'];
foreach ($keys as $key) {
// $dom->getElementsByTagName($key) is case-sensitive
$temp = $xpath->query("//*[translate(local-name(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')='$key']");
if (!$temp->length) {
continue;
}
$value = new BigInteger(Strings::base64_decode($temp->item(0)->nodeValue), 256);
switch ($key) {
case 'modulus':
$components['modulus'] = $value;
break;
case 'exponent':
$components['publicExponent'] = $value;
break;
case 'p':
$components['primes'][1] = $value;
break;
case 'q':
$components['primes'][2] = $value;
break;
case 'dp':
$components['exponents'][1] = $value;
break;
case 'dq':
$components['exponents'][2] = $value;
break;
case 'inverseq':
$components['coefficients'][2] = $value;
break;
case 'd':
$components['privateExponent'] = $value;
}
}
libxml_use_internal_errors($use_errors);
foreach ($components as $key => $value) {
if (is_array($value) && !count($value)) {
unset($components[$key]);
}
}
if (isset($components['modulus']) && isset($components['publicExponent'])) {
if (count($components) == 3) {
$components['isPublicKey'] = true;
}
return $components;
}
throw new UnexpectedValueException('Modulus / exponent not present');
}
/**
* Convert a private key to the appropriate format.
*
* @param string $password optional
*/
public static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, array $primes, array $exponents, array $coefficients, string $password = ''): string
{
if (count($primes) != 2) {
throw new InvalidArgumentException('XML does not support multi-prime RSA keys');
}
if (!empty($password) && is_string($password)) {
throw new UnsupportedFormatException('XML private keys do not support encryption');
}
return "<RSAKeyPair>\r\n" .
' <Modulus>' . Strings::base64_encode($n->toBytes()) . "</Modulus>\r\n" .
' <Exponent>' . Strings::base64_encode($e->toBytes()) . "</Exponent>\r\n" .
' <P>' . Strings::base64_encode($primes[1]->toBytes()) . "</P>\r\n" .
' <Q>' . Strings::base64_encode($primes[2]->toBytes()) . "</Q>\r\n" .
' <DP>' . Strings::base64_encode($exponents[1]->toBytes()) . "</DP>\r\n" .
' <DQ>' . Strings::base64_encode($exponents[2]->toBytes()) . "</DQ>\r\n" .
' <InverseQ>' . Strings::base64_encode($coefficients[2]->toBytes()) . "</InverseQ>\r\n" .
' <D>' . Strings::base64_encode($d->toBytes()) . "</D>\r\n" .
'</RSAKeyPair>';
}
/**
* Convert a public key to the appropriate format
*/
public static function savePublicKey(BigInteger $n, BigInteger $e): string
{
return "<RSAKeyValue>\r\n" .
' <Modulus>' . Strings::base64_encode($n->toBytes()) . "</Modulus>\r\n" .
' <Exponent>' . Strings::base64_encode($e->toBytes()) . "</Exponent>\r\n" .
'</RSAKeyValue>';
}
}

View File

@@ -0,0 +1,514 @@
<?php
/**
* RSA Private Key
*
* @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
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\RSA;
use phpseclib3\Crypt\Common;
use phpseclib3\Crypt\Random;
use phpseclib3\Crypt\RSA;
use phpseclib3\Crypt\RSA\Formats\Keys\PSS;
use phpseclib3\Exception\LengthException;
use phpseclib3\Exception\OutOfRangeException;
use phpseclib3\Exception\RuntimeException;
use phpseclib3\Exception\UnsupportedFormatException;
use phpseclib3\Math\BigInteger;
/**
* Raw RSA Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
final class PrivateKey extends RSA implements Common\PrivateKey
{
use Common\Traits\PasswordProtected;
/**
* Primes for Chinese Remainder Theorem (ie. p and q)
*
* @var array
*/
protected $primes;
/**
* Exponents for Chinese Remainder Theorem (ie. dP and dQ)
*
* @var array
*/
protected $exponents;
/**
* Coefficients for Chinese Remainder Theorem (ie. qInv)
*
* @var array
*/
protected $coefficients;
/**
* Private Exponent
*
* @var BigInteger
*/
protected $privateExponent;
/**
* RSADP
*
* See {@link http://tools.ietf.org/html/rfc3447#section-5.1.2 RFC3447#section-5.1.2}.
*
* @return bool|BigInteger
*/
private function rsadp(BigInteger $c)
{
if ($c->compare(self::$zero) < 0 || $c->compare($this->modulus) > 0) {
throw new OutOfRangeException('Ciphertext representative out of range');
}
return $this->exponentiate($c);
}
/**
* RSASP1
*
* See {@link http://tools.ietf.org/html/rfc3447#section-5.2.1 RFC3447#section-5.2.1}.
*
* @return bool|BigInteger
*/
private function rsasp1(BigInteger $m)
{
if ($m->compare(self::$zero) < 0 || $m->compare($this->modulus) > 0) {
throw new OutOfRangeException('Signature representative out of range');
}
return $this->exponentiate($m);
}
/**
* Exponentiate
*/
protected function exponentiate(BigInteger $x): BigInteger
{
switch (true) {
case empty($this->primes):
case $this->primes[1]->equals(self::$zero):
case empty($this->coefficients):
case $this->coefficients[2]->equals(self::$zero):
case empty($this->exponents):
case $this->exponents[1]->equals(self::$zero):
return $x->modPow($this->exponent, $this->modulus);
}
$num_primes = count($this->primes);
if (!static::$enableBlinding) {
$m_i = [
1 => $x->modPow($this->exponents[1], $this->primes[1]),
2 => $x->modPow($this->exponents[2], $this->primes[2]),
];
$h = $m_i[1]->subtract($m_i[2]);
$h = $h->multiply($this->coefficients[2]);
[, $h] = $h->divide($this->primes[1]);
$m = $m_i[2]->add($h->multiply($this->primes[2]));
$r = $this->primes[1];
for ($i = 3; $i <= $num_primes; $i++) {
$m_i = $x->modPow($this->exponents[$i], $this->primes[$i]);
$r = $r->multiply($this->primes[$i - 1]);
$h = $m_i->subtract($m);
$h = $h->multiply($this->coefficients[$i]);
[, $h] = $h->divide($this->primes[$i]);
$m = $m->add($r->multiply($h));
}
} else {
$smallest = $this->primes[1];
for ($i = 2; $i <= $num_primes; $i++) {
if ($smallest->compare($this->primes[$i]) > 0) {
$smallest = $this->primes[$i];
}
}
$r = BigInteger::randomRange(self::$one, $smallest->subtract(self::$one));
$m_i = [
1 => $this->blind($x, $r, 1),
2 => $this->blind($x, $r, 2),
];
$h = $m_i[1]->subtract($m_i[2]);
$h = $h->multiply($this->coefficients[2]);
[, $h] = $h->divide($this->primes[1]);
$m = $m_i[2]->add($h->multiply($this->primes[2]));
$r = $this->primes[1];
for ($i = 3; $i <= $num_primes; $i++) {
$m_i = $this->blind($x, $r, $i);
$r = $r->multiply($this->primes[$i - 1]);
$h = $m_i->subtract($m);
$h = $h->multiply($this->coefficients[$i]);
[, $h] = $h->divide($this->primes[$i]);
$m = $m->add($r->multiply($h));
}
}
return $m;
}
/**
* Performs RSA Blinding
*
* Protects against timing attacks by employing RSA Blinding.
* Returns $x->modPow($this->exponents[$i], $this->primes[$i])
*/
private function blind(BigInteger $x, BigInteger $r, int $i): BigInteger
{
$x = $x->multiply($r->modPow($this->publicExponent, $this->primes[$i]));
$x = $x->modPow($this->exponents[$i], $this->primes[$i]);
$r = $r->modInverse($this->primes[$i]);
$x = $x->multiply($r);
[, $x] = $x->divide($this->primes[$i]);
return $x;
}
/**
* EMSA-PSS-ENCODE
*
* See {@link http://tools.ietf.org/html/rfc3447#section-9.1.1 RFC3447#section-9.1.1}.
*
* @throws RuntimeException on encoding error
*/
private function emsa_pss_encode(string $m, int $emBits): string
{
// if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
// be output.
$emLen = ($emBits + 1) >> 3; // ie. ceil($emBits / 8)
$sLen = $this->sLen !== null ? $this->sLen : $this->hLen;
$mHash = $this->hash->hash($m);
if ($emLen < $this->hLen + $sLen + 2) {
throw new LengthException('RSA modulus too short');
}
$salt = Random::string($sLen);
$m2 = "\0\0\0\0\0\0\0\0" . $mHash . $salt;
$h = $this->hash->hash($m2);
$ps = str_repeat(chr(0), $emLen - $sLen - $this->hLen - 2);
$db = $ps . chr(1) . $salt;
$dbMask = $this->mgf1($h, $emLen - $this->hLen - 1); // ie. stlren($db)
$maskedDB = $db ^ $dbMask;
$maskedDB[0] = ~chr(0xFF << ($emBits & 7)) & $maskedDB[0];
$em = $maskedDB . $h . chr(0xBC);
return $em;
}
/**
* RSASSA-PSS-SIGN
*
* See {@link http://tools.ietf.org/html/rfc3447#section-8.1.1 RFC3447#section-8.1.1}.
*
* @return bool|string
*/
private function rsassa_pss_sign(string $m)
{
// EMSA-PSS encoding
$em = $this->emsa_pss_encode($m, 8 * $this->k - 1);
// RSA signature
$m = $this->os2ip($em);
$s = $this->rsasp1($m);
$s = $this->i2osp($s, $this->k);
// Output the signature S
return $s;
}
/**
* RSASSA-PKCS1-V1_5-SIGN
*
* See {@link http://tools.ietf.org/html/rfc3447#section-8.2.1 RFC3447#section-8.2.1}.
*
* @return bool|string
* @throws LengthException if the RSA modulus is too short
*/
private function rsassa_pkcs1_v1_5_sign(string $m)
{
// EMSA-PKCS1-v1_5 encoding
// If the encoding operation outputs "intended encoded message length too short," output "RSA modulus
// too short" and stop.
try {
$em = $this->emsa_pkcs1_v1_5_encode($m, $this->k);
} catch (\LengthException $e) {
throw new LengthException('RSA modulus too short');
}
// RSA signature
$m = $this->os2ip($em);
$s = $this->rsasp1($m);
$s = $this->i2osp($s, $this->k);
// Output the signature S
return $s;
}
/**
* Create a signature
*
* @see self::verify()
* @param string $message
* @return string
*/
public function sign($message)
{
switch ($this->signaturePadding) {
case self::SIGNATURE_PKCS1:
case self::SIGNATURE_RELAXED_PKCS1:
return $this->rsassa_pkcs1_v1_5_sign($message);
//case self::SIGNATURE_PSS:
default:
return $this->rsassa_pss_sign($message);
}
}
/**
* RSAES-PKCS1-V1_5-DECRYPT
*
* See {@link http://tools.ietf.org/html/rfc3447#section-7.2.2 RFC3447#section-7.2.2}.
*
* @return bool|string
*/
private function rsaes_pkcs1_v1_5_decrypt(string $c)
{
// Length checking
if (strlen($c) != $this->k) { // or if k < 11
throw new LengthException('Ciphertext representative too long');
}
// RSA decryption
$c = $this->os2ip($c);
$m = $this->rsadp($c);
$em = $this->i2osp($m, $this->k);
// EME-PKCS1-v1_5 decoding
if (ord($em[0]) != 0 || ord($em[1]) > 2) {
throw new RuntimeException('Decryption error');
}
$ps = substr($em, 2, strpos($em, chr(0), 2) - 2);
$m = substr($em, strlen($ps) + 3);
if (strlen($ps) < 8) {
throw new RuntimeException('Decryption error');
}
// Output M
return $m;
}
/**
* RSAES-OAEP-DECRYPT
*
* See {@link http://tools.ietf.org/html/rfc3447#section-7.1.2 RFC3447#section-7.1.2}. The fact that the error
* messages aren't distinguishable from one another hinders debugging, but, to quote from RFC3447#section-7.1.2:
*
* Note. Care must be taken to ensure that an opponent cannot
* distinguish the different error conditions in Step 3.g, whether by
* error message or timing, or, more generally, learn partial
* information about the encoded message EM. Otherwise an opponent may
* be able to obtain useful information about the decryption of the
* ciphertext C, leading to a chosen-ciphertext attack such as the one
* observed by Manger [36].
*
* @return bool|string
*/
private function rsaes_oaep_decrypt(string $c)
{
// Length checking
// if $l is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
// be output.
if (strlen($c) != $this->k || $this->k < 2 * $this->hLen + 2) {
throw new LengthException('Ciphertext representative too long');
}
// RSA decryption
$c = $this->os2ip($c);
$m = $this->rsadp($c);
$em = $this->i2osp($m, $this->k);
// EME-OAEP decoding
$lHash = $this->hash->hash($this->label);
$y = ord($em[0]);
$maskedSeed = substr($em, 1, $this->hLen);
$maskedDB = substr($em, $this->hLen + 1);
$seedMask = $this->mgf1($maskedDB, $this->hLen);
$seed = $maskedSeed ^ $seedMask;
$dbMask = $this->mgf1($seed, $this->k - $this->hLen - 1);
$db = $maskedDB ^ $dbMask;
$lHash2 = substr($db, 0, $this->hLen);
$m = substr($db, $this->hLen);
$hashesMatch = hash_equals($lHash, $lHash2);
$leadingZeros = 1;
$patternMatch = 0;
$offset = 0;
for ($i = 0; $i < strlen($m); $i++) {
$patternMatch |= $leadingZeros & ($m[$i] === "\1");
$leadingZeros &= $m[$i] === "\0";
$offset += $patternMatch ? 0 : 1;
}
// we do | instead of || to avoid https://en.wikipedia.org/wiki/Short-circuit_evaluation
// to protect against timing attacks
if (!$hashesMatch | !$patternMatch) {
throw new RuntimeException('Decryption error');
}
// Output the message M
return substr($m, $offset + 1);
}
/**
* Raw Encryption / Decryption
*
* Doesn't use padding and is not recommended.
*
* @return bool|string
* @throws LengthException if strlen($m) > $this->k
*/
private function raw_encrypt(string $m)
{
if (strlen($m) > $this->k) {
throw new LengthException('Ciphertext representative too long');
}
$temp = $this->os2ip($m);
$temp = $this->rsadp($temp);
return $this->i2osp($temp, $this->k);
}
/**
* Decryption
*
* @return bool|string
* @see self::encrypt()
*/
public function decrypt(string $ciphertext)
{
switch ($this->encryptionPadding) {
case self::ENCRYPTION_NONE:
return $this->raw_encrypt($ciphertext);
case self::ENCRYPTION_PKCS1:
return $this->rsaes_pkcs1_v1_5_decrypt($ciphertext);
//case self::ENCRYPTION_OAEP:
default:
return $this->rsaes_oaep_decrypt($ciphertext);
}
}
/**
* Returns the public key
*/
public function getPublicKey(): RSA
{
$type = self::validatePlugin('Keys', 'PKCS8', 'savePublicKey');
if (empty($this->modulus) || empty($this->publicExponent)) {
throw new RuntimeException('Public key components not found');
}
$key = $type::savePublicKey($this->modulus, $this->publicExponent);
return RSA::loadFormat('PKCS8', $key)
->withHash($this->hash->getHash())
->withMGFHash($this->mgfHash->getHash())
->withSaltLength($this->sLen)
->withLabel($this->label)
->withPadding($this->signaturePadding | $this->encryptionPadding);
}
/**
* Returns the private key
*
* @param array $options optional
*/
public function toString(string $type, array $options = []): string
{
$type = self::validatePlugin(
'Keys',
$type,
empty($this->primes) ? 'savePublicKey' : 'savePrivateKey'
);
if ($type == PSS::class) {
if ($this->signaturePadding == self::SIGNATURE_PSS) {
$options += [
'hash' => $this->hash->getHash(),
'MGFHash' => $this->mgfHash->getHash(),
'saltLength' => $this->getSaltLength(),
];
} else {
throw new UnsupportedFormatException('The PSS format can only be used when the signature method has been explicitly set to PSS');
}
}
if (empty($this->primes)) {
return $type::savePublicKey($this->modulus, $this->exponent, $options);
}
return $type::savePrivateKey($this->modulus, $this->publicExponent, $this->exponent, $this->primes, $this->exponents, $this->coefficients, $this->password, $options);
/*
$key = $type::savePrivateKey($this->modulus, $this->publicExponent, $this->exponent, $this->primes, $this->exponents, $this->coefficients, $this->password, $options);
if ($key !== false || count($this->primes) == 2) {
return $key;
}
$nSize = $this->getSize() >> 1;
$primes = [1 => clone self::$one, clone self::$one];
$i = 1;
foreach ($this->primes as $prime) {
$primes[$i] = $primes[$i]->multiply($prime);
if ($primes[$i]->getLength() >= $nSize) {
$i++;
}
}
$exponents = [];
$coefficients = [2 => $primes[2]->modInverse($primes[1])];
foreach ($primes as $i => $prime) {
$temp = $prime->subtract(self::$one);
$exponents[$i] = $this->modulus->modInverse($temp);
}
return $type::savePrivateKey($this->modulus, $this->publicExponent, $this->exponent, $primes, $exponents, $coefficients, $this->password, $options);
*/
}
}

View File

@@ -0,0 +1,491 @@
<?php
/**
* RSA Public Key
*
* @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
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\RSA;
use phpseclib3\Common\Functions\Strings;
use phpseclib3\Crypt\Common;
use phpseclib3\Crypt\Hash;
use phpseclib3\Crypt\Random;
use phpseclib3\Crypt\RSA;
use phpseclib3\Crypt\RSA\Formats\Keys\PSS;
use phpseclib3\Exception\LengthException;
use phpseclib3\Exception\OutOfRangeException;
use phpseclib3\Exception\UnsupportedAlgorithmException;
use phpseclib3\Exception\UnsupportedFormatException;
use phpseclib3\File\ASN1;
use phpseclib3\File\ASN1\Maps\DigestInfo;
use phpseclib3\Math\BigInteger;
/**
* Raw RSA Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
final class PublicKey extends RSA implements Common\PublicKey
{
use Common\Traits\Fingerprint;
/**
* Exponentiate
*/
private function exponentiate(BigInteger $x): BigInteger
{
return $x->modPow($this->exponent, $this->modulus);
}
/**
* RSAVP1
*
* See {@link http://tools.ietf.org/html/rfc3447#section-5.2.2 RFC3447#section-5.2.2}.
*
* @return bool|BigInteger
*/
private function rsavp1(BigInteger $s)
{
if ($s->compare(self::$zero) < 0 || $s->compare($this->modulus) > 0) {
return false;
}
return $this->exponentiate($s);
}
/**
* RSASSA-PKCS1-V1_5-VERIFY
*
* See {@link http://tools.ietf.org/html/rfc3447#section-8.2.2 RFC3447#section-8.2.2}.
*
* @throws LengthException if the RSA modulus is too short
*/
private function rsassa_pkcs1_v1_5_verify(string $m, string $s): bool
{
// Length checking
if (strlen($s) != $this->k) {
return false;
}
// RSA verification
$s = $this->os2ip($s);
$m2 = $this->rsavp1($s);
if ($m2 === false) {
return false;
}
$em = $this->i2osp($m2, $this->k);
if ($em === false) {
return false;
}
// EMSA-PKCS1-v1_5 encoding
$exception = false;
// If the encoding operation outputs "intended encoded message length too short," output "RSA modulus
// too short" and stop.
try {
$em2 = $this->emsa_pkcs1_v1_5_encode($m, $this->k);
$r1 = hash_equals($em, $em2);
} catch (\LengthException $e) {
$exception = true;
}
try {
$em3 = $this->emsa_pkcs1_v1_5_encode_without_null($m, $this->k);
$r2 = hash_equals($em, $em3);
} catch (\LengthException $e) {
$exception = true;
} catch (UnsupportedAlgorithmException $e) {
$r2 = false;
}
if ($exception) {
throw new LengthException('RSA modulus too short');
}
// Compare
return $r1 || $r2;
}
/**
* RSASSA-PKCS1-V1_5-VERIFY (relaxed matching)
*
* Per {@link http://tools.ietf.org/html/rfc3447#page-43 RFC3447#page-43} PKCS1 v1.5
* specified the use BER encoding rather than DER encoding that PKCS1 v2.0 specified.
* This means that under rare conditions you can have a perfectly valid v1.5 signature
* that fails to validate with _rsassa_pkcs1_v1_5_verify(). PKCS1 v2.1 also recommends
* that if you're going to validate these types of signatures you "should indicate
* whether the underlying BER encoding is a DER encoding and hence whether the signature
* is valid with respect to the specification given in [PKCS1 v2.0+]". so if you do
* $rsa->getLastPadding() and get RSA::PADDING_RELAXED_PKCS1 back instead of
* RSA::PADDING_PKCS1... that means BER encoding was used.
*/
private function rsassa_pkcs1_v1_5_relaxed_verify(string $m, string $s): bool
{
// Length checking
if (strlen($s) != $this->k) {
return false;
}
// RSA verification
$s = $this->os2ip($s);
$m2 = $this->rsavp1($s);
if ($m2 === false) {
return false;
}
$em = $this->i2osp($m2, $this->k);
if ($em === false) {
return false;
}
if (Strings::shift($em, 2) != "\0\1") {
return false;
}
$em = ltrim($em, "\xFF");
if (Strings::shift($em) != "\0") {
return false;
}
$decoded = ASN1::decodeBER($em);
if (!is_array($decoded) || empty($decoded[0]) || strlen($em) > $decoded[0]['length']) {
return false;
}
static $oids;
if (!isset($oids)) {
$oids = [
'md2' => '1.2.840.113549.2.2',
'md4' => '1.2.840.113549.2.4', // from PKCS1 v1.5
'md5' => '1.2.840.113549.2.5',
'id-sha1' => '1.3.14.3.2.26',
'id-sha256' => '2.16.840.1.101.3.4.2.1',
'id-sha384' => '2.16.840.1.101.3.4.2.2',
'id-sha512' => '2.16.840.1.101.3.4.2.3',
// from PKCS1 v2.2
'id-sha224' => '2.16.840.1.101.3.4.2.4',
'id-sha512/224' => '2.16.840.1.101.3.4.2.5',
'id-sha512/256' => '2.16.840.1.101.3.4.2.6',
];
ASN1::loadOIDs($oids);
}
$decoded = ASN1::asn1map($decoded[0], DigestInfo::MAP);
if (!isset($decoded) || $decoded === false) {
return false;
}
if (!isset($oids[$decoded['digestAlgorithm']['algorithm']])) {
return false;
}
if (isset($decoded['digestAlgorithm']['parameters']) && $decoded['digestAlgorithm']['parameters'] !== ['null' => '']) {
return false;
}
$hash = $decoded['digestAlgorithm']['algorithm'];
$hash = substr($hash, 0, 3) == 'id-' ?
substr($hash, 3) :
$hash;
$hash = new Hash($hash);
$em = $hash->hash($m);
$em2 = $decoded['digest'];
return hash_equals($em, $em2);
}
/**
* EMSA-PSS-VERIFY
*
* See {@link http://tools.ietf.org/html/rfc3447#section-9.1.2 RFC3447#section-9.1.2}.
*
* @return string
*/
private function emsa_pss_verify(string $m, string $em, int $emBits)
{
// if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
// be output.
$emLen = ($emBits + 7) >> 3; // ie. ceil($emBits / 8);
$sLen = $this->sLen !== null ? $this->sLen : $this->hLen;
$mHash = $this->hash->hash($m);
if ($emLen < $this->hLen + $sLen + 2) {
return false;
}
if ($em[-1] != chr(0xBC)) {
return false;
}
$maskedDB = substr($em, 0, -$this->hLen - 1);
$h = substr($em, -$this->hLen - 1, $this->hLen);
$temp = chr(0xFF << ($emBits & 7));
if ((~$maskedDB[0] & $temp) != $temp) {
return false;
}
$dbMask = $this->mgf1($h, $emLen - $this->hLen - 1);
$db = $maskedDB ^ $dbMask;
$db[0] = ~chr(0xFF << ($emBits & 7)) & $db[0];
$temp = $emLen - $this->hLen - $sLen - 2;
if (substr($db, 0, $temp) != str_repeat(chr(0), $temp) || ord($db[$temp]) != 1) {
return false;
}
$salt = substr($db, $temp + 1); // should be $sLen long
$m2 = "\0\0\0\0\0\0\0\0" . $mHash . $salt;
$h2 = $this->hash->hash($m2);
return hash_equals($h, $h2);
}
/**
* RSASSA-PSS-VERIFY
*
* See {@link http://tools.ietf.org/html/rfc3447#section-8.1.2 RFC3447#section-8.1.2}.
*
* @return bool|string
*/
private function rsassa_pss_verify(string $m, string $s)
{
// Length checking
if (strlen($s) != $this->k) {
return false;
}
// RSA verification
$modBits = strlen($this->modulus->toBits());
$s2 = $this->os2ip($s);
$m2 = $this->rsavp1($s2);
$em = $this->i2osp($m2, $this->k);
if ($em === false) {
return false;
}
// EMSA-PSS verification
return $this->emsa_pss_verify($m, $em, $modBits - 1);
}
/**
* Verifies a signature
*
* @see self::sign()
* @param string $message
* @param string $signature
* @return bool
*/
public function verify($message, $signature)
{
switch ($this->signaturePadding) {
case self::SIGNATURE_RELAXED_PKCS1:
return $this->rsassa_pkcs1_v1_5_relaxed_verify($message, $signature);
case self::SIGNATURE_PKCS1:
return $this->rsassa_pkcs1_v1_5_verify($message, $signature);
//case self::SIGNATURE_PSS:
default:
return $this->rsassa_pss_verify($message, $signature);
}
}
/**
* RSAES-PKCS1-V1_5-ENCRYPT
*
* See {@link http://tools.ietf.org/html/rfc3447#section-7.2.1 RFC3447#section-7.2.1}.
*
* @param bool $pkcs15_compat optional
* @return bool|string
* @throws LengthException if strlen($m) > $this->k - 11
*/
private function rsaes_pkcs1_v1_5_encrypt(string $m, bool $pkcs15_compat = false)
{
$mLen = strlen($m);
// Length checking
if ($mLen > $this->k - 11) {
throw new LengthException('Message too long');
}
// EME-PKCS1-v1_5 encoding
$psLen = $this->k - $mLen - 3;
$ps = '';
while (strlen($ps) != $psLen) {
$temp = Random::string($psLen - strlen($ps));
$temp = str_replace("\x00", '', $temp);
$ps .= $temp;
}
$type = 2;
$em = chr(0) . chr($type) . $ps . chr(0) . $m;
// RSA encryption
$m = $this->os2ip($em);
$c = $this->rsaep($m);
$c = $this->i2osp($c, $this->k);
// Output the ciphertext C
return $c;
}
/**
* RSAES-OAEP-ENCRYPT
*
* See {@link http://tools.ietf.org/html/rfc3447#section-7.1.1 RFC3447#section-7.1.1} and
* {http://en.wikipedia.org/wiki/Optimal_Asymmetric_Encryption_Padding OAES}.
*
* @throws LengthException if strlen($m) > $this->k - 2 * $this->hLen - 2
*/
private function rsaes_oaep_encrypt(string $m): string
{
$mLen = strlen($m);
// Length checking
// if $l is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
// be output.
if ($mLen > $this->k - 2 * $this->hLen - 2) {
throw new LengthException('Message too long');
}
// EME-OAEP encoding
$lHash = $this->hash->hash($this->label);
$ps = str_repeat(chr(0), $this->k - $mLen - 2 * $this->hLen - 2);
$db = $lHash . $ps . chr(1) . $m;
$seed = Random::string($this->hLen);
$dbMask = $this->mgf1($seed, $this->k - $this->hLen - 1);
$maskedDB = $db ^ $dbMask;
$seedMask = $this->mgf1($maskedDB, $this->hLen);
$maskedSeed = $seed ^ $seedMask;
$em = chr(0) . $maskedSeed . $maskedDB;
// RSA encryption
$m = $this->os2ip($em);
$c = $this->rsaep($m);
$c = $this->i2osp($c, $this->k);
// Output the ciphertext C
return $c;
}
/**
* RSAEP
*
* See {@link http://tools.ietf.org/html/rfc3447#section-5.1.1 RFC3447#section-5.1.1}.
*
* @return bool|BigInteger
*/
private function rsaep(BigInteger $m)
{
if ($m->compare(self::$zero) < 0 || $m->compare($this->modulus) > 0) {
throw new OutOfRangeException('Message representative out of range');
}
return $this->exponentiate($m);
}
/**
* Raw Encryption / Decryption
*
* Doesn't use padding and is not recommended.
*
* @return bool|string
* @throws LengthException if strlen($m) > $this->k
*/
private function raw_encrypt(string $m)
{
if (strlen($m) > $this->k) {
throw new LengthException('Message too long');
}
$temp = $this->os2ip($m);
$temp = $this->rsaep($temp);
return $this->i2osp($temp, $this->k);
}
/**
* Encryption
*
* Both self::PADDING_OAEP and self::PADDING_PKCS1 both place limits on how long $plaintext can be.
* If $plaintext exceeds those limits it will be broken up so that it does and the resultant ciphertext's will
* be concatenated together.
*
* @return bool|string
* @throws LengthException if the RSA modulus is too short
* @see self::decrypt()
*/
public function encrypt(string $plaintext)
{
switch ($this->encryptionPadding) {
case self::ENCRYPTION_NONE:
return $this->raw_encrypt($plaintext);
case self::ENCRYPTION_PKCS1:
return $this->rsaes_pkcs1_v1_5_encrypt($plaintext);
//case self::ENCRYPTION_OAEP:
default:
return $this->rsaes_oaep_encrypt($plaintext);
}
}
/**
* Returns the public key
*
* The public key is only returned under two circumstances - if the private key had the public key embedded within it
* or if the public key was set via setPublicKey(). If the currently loaded key is supposed to be the public key this
* function won't return it since this library, for the most part, doesn't distinguish between public and private keys.
*
* @param array $options optional
*/
public function toString(string $type, array $options = []): string
{
$type = self::validatePlugin('Keys', $type, 'savePublicKey');
if ($type == PSS::class) {
if ($this->signaturePadding == self::SIGNATURE_PSS) {
$options += [
'hash' => $this->hash->getHash(),
'MGFHash' => $this->mgfHash->getHash(),
'saltLength' => $this->getSaltLength(),
];
} else {
throw new UnsupportedFormatException('The PSS format can only be used when the signature method has been explicitly set to PSS');
}
}
return $type::savePublicKey($this->modulus, $this->publicExponent, $options);
}
/**
* Converts a public key to a private key
*/
public function asPrivateKey(): RSA
{
$new = new PrivateKey();
$new->exponent = $this->exponent;
$new->modulus = $this->modulus;
$new->k = $this->k;
$new->format = $this->format;
return $new
->withHash($this->hash->getHash())
->withMGFHash($this->mgfHash->getHash())
->withSaltLength($this->sLen)
->withLabel($this->label)
->withPadding($this->signaturePadding | $this->encryptionPadding);
}
}