1
0
mirror of https://github.com/danog/tgseclib.git synced 2024-11-27 04:34:45 +01:00
tgseclib/phpseclib/Crypt/RSA.php

765 lines
21 KiB
PHP

<?php
/**
* Pure-PHP PKCS#1 (v2.1) compliant implementation of RSA.
*
* PHP version 5
*
* Here's an example of how to encrypt and decrypt text with this library:
* <code>
* <?php
* include 'vendor/autoload.php';
*
* $private = \phpseclib\Crypt\RSA::createKey();
* $public = $private->getPublicKey();
*
* $plaintext = 'terrafrost';
*
* $ciphertext = $public->encrypt($plaintext);
*
* echo $private->decrypt($ciphertext);
* ?>
* </code>
*
* Here's an example of how to create signatures and verify signatures with this library:
* <code>
* <?php
* include 'vendor/autoload.php';
*
* $private = \phpseclib\Crypt\RSA::createKey();
* $public = $private->getPublicKey();
*
* $plaintext = 'terrafrost';
*
* $signature = $private->sign($plaintext);
*
* echo $public->verify($plaintext, $signature) ? 'verified' : 'unverified';
* ?>
* </code>
*
* @category Crypt
* @package RSA
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2009 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib\Crypt;
use phpseclib\Crypt\Common\AsymmetricKey;
use phpseclib\Crypt\RSA\PrivateKey;
use phpseclib\Crypt\RSA\PublicKey;
use phpseclib\Math\BigInteger;
use phpseclib\Exceptions\UnsupportedAlgorithmException;
use phpseclib\Exceptions\InconsistentSetupException;
/**
* Pure-PHP PKCS#1 compliant implementation of RSA.
*
* @package RSA
* @author Jim Wigginton <terrafrost@php.net>
* @access public
*/
abstract class RSA extends AsymmetricKey
{
/**
* Algorithm Name
*
* @var string
* @access private
*/
const ALGORITHM = 'RSA';
/**#@+
* @access public
* @see self::encrypt()
* @see self::decrypt()
*/
/**
* Use {@link http://en.wikipedia.org/wiki/Optimal_Asymmetric_Encryption_Padding Optimal Asymmetric Encryption Padding}
* (OAEP) for encryption / decryption.
*
* Uses sha256 by default
*
* @see self::setHash()
* @see self::setMGFHash()
*/
const ENCRYPTION_OAEP = 1;
/**
* Use PKCS#1 padding.
*
* Although self::PADDING_OAEP / self::PADDING_PSS offers more security, including PKCS#1 padding is necessary for purposes of backwards
* compatibility with protocols (like SSH-1) written before OAEP's introduction.
*/
const ENCRYPTION_PKCS1 = 2;
/**
* Do not use any padding
*
* Although this method is not recommended it can none-the-less sometimes be useful if you're trying to decrypt some legacy
* stuff, if you're trying to diagnose why an encrypted message isn't decrypting, etc.
*/
const ENCRYPTION_NONE = 4;
/**
* Use PKCS#1 padding with PKCS1 v1.5 compatibility
*
* A PKCS1 v2.1 encrypted message may not successfully decrypt with a PKCS1 v1.5 implementation (such as OpenSSL).
*/
const ENCRYPTION_PKCS15_COMPAT = 8;
/**#@-*/
/**#@+
* @access public
* @see self::sign()
* @see self::verify()
* @see self::setHash()
*/
/**
* Use the Probabilistic Signature Scheme for signing
*
* Uses sha256 and 0 as the salt length
*
* @see self::setSaltLength()
* @see self::setMGFHash()
* @see self::setHash()
*/
const SIGNATURE_PSS = 16;
/**
* Use a relaxed version of PKCS#1 padding for signature verification
*/
const SIGNATURE_RELAXED_PKCS1 = 32;
/**
* Use PKCS#1 padding for signature verification
*/
const SIGNATURE_PKCS1 = 64;
/**#@-*/
/**
* Encryption padding mode
*
* @var int
* @access private
*/
protected $encryptionPadding = self::ENCRYPTION_OAEP;
/**
* Signature padding mode
*
* @var int
* @access private
*/
protected $signaturePadding = self::SIGNATURE_PSS;
/**
* Length of hash function output
*
* @var int
* @access private
*/
protected $hLen;
/**
* Length of salt
*
* @var int
* @access private
*/
protected $sLen;
/**
* Label
*
* @var string
* @access private
*/
protected $label = '';
/**
* Hash function for the Mask Generation Function
*
* @var \phpseclib\Crypt\Hash
* @access private
*/
protected $mgfHash;
/**
* Length of MGF hash function output
*
* @var int
* @access private
*/
protected $mgfHLen;
/**
* Modulus (ie. n)
*
* @var \phpseclib\Math\BigInteger
* @access private
*/
protected $modulus;
/**
* Modulus length
*
* @var \phpseclib\Math\BigInteger
* @access private
*/
protected $k;
/**
* Exponent (ie. e or d)
*
* @var \phpseclib\Math\BigInteger
* @access private
*/
protected $exponent;
/**
* Default public exponent
*
* @var int
* @link http://en.wikipedia.org/wiki/65537_%28number%29
* @access private
*/
private static $defaultExponent = 65537;
/**
* Smallest Prime
*
* Per <http://cseweb.ucsd.edu/~hovav/dist/survey.pdf#page=5>, this number ought not result in primes smaller
* than 256 bits. As a consequence if the key you're trying to create is 1024 bits and you've set smallestPrime
* to 384 bits then you're going to get a 384 bit prime and a 640 bit prime (384 + 1024 % 384). At least if
* engine is set to self::ENGINE_INTERNAL. If Engine is set to self::ENGINE_OPENSSL then smallest Prime is
* ignored (ie. multi-prime RSA support is more intended as a way to speed up RSA key generation when there's
* a chance neither gmp nor OpenSSL are installed)
*
* @var int
* @access private
*/
private static $smallestPrime = 4096;
/**
* Sets the public exponent for key generation
*
* This will be 65537 unless changed.
*
* @access public
* @param int $val
*/
public static function setExponent($val)
{
self::$defaultExponent = $val;
}
/**
* Sets the smallest prime number in bits. Used for key generation
*
* This will be 4096 unless changed.
*
* @access public
* @param int $val
*/
public static function setSmallestPrime($val)
{
self::$smallestPrime = $val;
}
/**
* Create a private key
*
* The public key can be extracted from the private key
*
* @return RSA
* @access public
* @param int $bits
*/
public static function createKey($bits = 2048)
{
self::initialize_static_variables();
static $e;
if (!isset($e)) {
$e = new BigInteger(self::$defaultExponent);
}
$regSize = $bits >> 1; // divide by two to see how many bits P and Q would be
if ($regSize > self::$smallestPrime) {
$num_primes = floor($bits / self::$smallestPrime);
$regSize = self::$smallestPrime;
} else {
$num_primes = 2;
}
$n = clone self::$one;
$exponents = $coefficients = $primes = [];
$lcm = [
'top' => clone self::$one,
'bottom' => false
];
do {
for ($i = 1; $i <= $num_primes; $i++) {
if ($i != $num_primes) {
$primes[$i] = BigInteger::randomPrime($regSize);
} else {
extract(BigInteger::minMaxBits($bits));
/** @var BigInteger $min
* @var BigInteger $max
*/
list($min) = $min->divide($n);
$min = $min->add(self::$one);
list($max) = $max->divide($n);
$primes[$i] = BigInteger::randomRangePrime($min, $max);
}
// the first coefficient is calculated differently from the rest
// ie. instead of being $primes[1]->modInverse($primes[2]), it's $primes[2]->modInverse($primes[1])
if ($i > 2) {
$coefficients[$i] = $n->modInverse($primes[$i]);
}
$n = $n->multiply($primes[$i]);
$temp = $primes[$i]->subtract(self::$one);
// textbook RSA implementations use Euler's totient function instead of the least common multiple.
// see http://en.wikipedia.org/wiki/Euler%27s_totient_function
$lcm['top'] = $lcm['top']->multiply($temp);
$lcm['bottom'] = $lcm['bottom'] === false ? $temp : $lcm['bottom']->gcd($temp);
}
list($temp) = $lcm['top']->divide($lcm['bottom']);
$gcd = $temp->gcd($e);
$i0 = 1;
} while (!$gcd->equals(self::$one));
$coefficients[2] = $primes[2]->modInverse($primes[1]);
$d = $e->modInverse($temp);
foreach ($primes as $i => $prime) {
$temp = $prime->subtract(self::$one);
$exponents[$i] = $e->modInverse($temp);
}
// from <http://tools.ietf.org/html/rfc3447#appendix-A.1.2>:
// RSAPrivateKey ::= SEQUENCE {
// version Version,
// modulus INTEGER, -- n
// publicExponent INTEGER, -- e
// privateExponent INTEGER, -- d
// prime1 INTEGER, -- p
// prime2 INTEGER, -- q
// exponent1 INTEGER, -- d mod (p-1)
// exponent2 INTEGER, -- d mod (q-1)
// coefficient INTEGER, -- (inverse of q) mod p
// otherPrimeInfos OtherPrimeInfos OPTIONAL
// }
$privatekey = new PrivateKey;
$privatekey->modulus = $n;
$privatekey->k = $bits >> 3;
$privatekey->publicExponent = $e;
$privatekey->exponent = $d;
$privatekey->privateExponent = $e;
$privatekey->primes = $primes;
$privatekey->exponents = $exponents;
$privatekey->coefficients = $coefficients;
/*
$publickey = new PublicKey;
$publickey->modulus = $n;
$publickey->k = $bits >> 3;
$publickey->exponent = $e;
$publickey->publicExponent = $e;
$publickey->isPublic = true;
*/
return $privatekey;
}
/**
* Loads a public or private key
*
* Returns true on success and false on failure (ie. an incorrect password was provided or the key was malformed)
*
* @return bool
* @access public
* @param string $key
* @param string $type optional
* @param string $password optional
*/
public static function load($key, $type = false, $password = false)
{
self::initialize_static_variables();
$components = parent::load($key, $type, $password);
$key = $components['isPublicKey'] ?
new PublicKey :
new PrivateKey;
$key->format = $components['format'];
$key->modulus = $components['modulus'];
$key->publicExponent = $components['publicExponent'];
$key->k = $key->modulus->getLengthInBytes();
if ($components['isPublicKey']) {
$key->exponent = $key->publicExponent;
return $key;
}
$key->privateExponent = $components['privateExponent'];
$key->exponent = $key->privateExponent;
$key->primes = $components['primes'];
$key->exponents = $components['exponents'];
$key->coefficients = $components['coefficients'];
return $key;
}
/**
* Constructor
*
* PublicKey and PrivateKey objects can only be created from abstract RSA class
*/
protected function __construct()
{
parent::__construct();
$this->hLen = $this->hash->getLengthInBytes();
$this->mgfHash = new Hash('sha256');
$this->mgfHLen = $this->mgfHash->getLengthInBytes();
}
/**
* Integer-to-Octet-String primitive
*
* See {@link http://tools.ietf.org/html/rfc3447#section-4.1 RFC3447#section-4.1}.
*
* @access private
* @param bool|\phpseclib\Math\BigInteger $x
* @param int $xLen
* @return bool|string
*/
protected function i2osp($x, $xLen)
{
if ($x === false) {
return false;
}
$x = $x->toBytes();
if (strlen($x) > $xLen) {
return false;
}
return str_pad($x, $xLen, chr(0), STR_PAD_LEFT);
}
/**
* Octet-String-to-Integer primitive
*
* See {@link http://tools.ietf.org/html/rfc3447#section-4.2 RFC3447#section-4.2}.
*
* @access private
* @param string $x
* @return \phpseclib\Math\BigInteger
*/
protected function os2ip($x)
{
return new BigInteger($x, 256);
}
/**
* EMSA-PKCS1-V1_5-ENCODE
*
* See {@link http://tools.ietf.org/html/rfc3447#section-9.2 RFC3447#section-9.2}.
*
* @access private
* @param string $m
* @param int $emLen
* @throws \LengthException if the intended encoded message length is too short
* @return string
*/
protected function emsa_pkcs1_v1_5_encode($m, $emLen)
{
$h = $this->hash->hash($m);
// see http://tools.ietf.org/html/rfc3447#page-43
switch ($this->hash->getHash()) {
case 'md2':
$t = "\x30\x20\x30\x0c\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x02\x05\x00\x04\x10";
break;
case 'md5':
$t = "\x30\x20\x30\x0c\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x05\x05\x00\x04\x10";
break;
case 'sha1':
$t = "\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14";
break;
case 'sha256':
$t = "\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20";
break;
case 'sha384':
$t = "\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02\x05\x00\x04\x30";
break;
case 'sha512':
$t = "\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03\x05\x00\x04\x40";
break;
// from https://www.emc.com/collateral/white-papers/h11300-pkcs-1v2-2-rsa-cryptography-standard-wp.pdf#page=40
case 'sha224':
$t = "\x30\x2d\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x04\x05\x00\x04\x1c";
break;
case 'sha512/224':
$t = "\x30\x2d\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x05\x05\x00\x04\x1c";
break;
case 'sha512/256':
$t = "\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x06\x05\x00\x04\x20";
}
$t.= $h;
$tLen = strlen($t);
if ($emLen < $tLen + 11) {
throw new \LengthException('Intended encoded message length too short');
}
$ps = str_repeat(chr(0xFF), $emLen - $tLen - 3);
$em = "\0\1$ps\0$t";
return $em;
}
/**
* MGF1
*
* See {@link http://tools.ietf.org/html/rfc3447#appendix-B.2.1 RFC3447#appendix-B.2.1}.
*
* @access private
* @param string $mgfSeed
* @param int $maskLen
* @return string
*/
protected function mgf1($mgfSeed, $maskLen)
{
// if $maskLen would yield strings larger than 4GB, PKCS#1 suggests a "Mask too long" error be output.
$t = '';
$count = ceil($maskLen / $this->mgfHLen);
for ($i = 0; $i < $count; $i++) {
$c = pack('N', $i);
$t.= $this->mgfHash->hash($mgfSeed . $c);
}
return substr($t, 0, $maskLen);
}
/**
* Returns the key size
*
* More specifically, this returns the size of the modulo in bits.
*
* @access public
* @return int
*/
public function getLength()
{
return !isset($this->modulus) ? 0 : $this->modulus->getLength();
}
/**
* Determines which hashing function should be used
*
* Used with signature production / verification and (if the encryption mode is self::PADDING_OAEP) encryption and
* decryption.
*
* @access public
* @param string $hash
*/
public function withHash($hash)
{
$new = clone $this;
// \phpseclib\Crypt\Hash supports algorithms that PKCS#1 doesn't support. md5-96 and sha1-96, for example.
switch (strtolower($hash)) {
case 'md2':
case 'md5':
case 'sha1':
case 'sha256':
case 'sha384':
case 'sha512':
case 'sha224':
case 'sha512/224':
case 'sha512/256':
$new->hash = new Hash($hash);
break;
default:
throw new UnsupportedAlgorithmException(
'The only supported hash algorithms are: md2, md5, sha1, sha256, sha384, sha512, sha224, sha512/224, sha512/256'
);
}
$new->hLen = $new->hash->getLengthInBytes();
return $new;
}
/**
* Determines which hashing function should be used for the mask generation function
*
* The mask generation function is used by self::PADDING_OAEP and self::PADDING_PSS and although it's
* best if Hash and MGFHash are set to the same thing this is not a requirement.
*
* @access public
* @param string $hash
*/
public function withMGFHash($hash)
{
$new = clone $this;
// \phpseclib\Crypt\Hash supports algorithms that PKCS#1 doesn't support. md5-96 and sha1-96, for example.
switch (strtolower($hash)) {
case 'md2':
case 'md5':
case 'sha1':
case 'sha256':
case 'sha384':
case 'sha512':
case 'sha224':
case 'sha512/224':
case 'sha512/256':
$new->mgfHash = new Hash($hash);
break;
default:
throw new UnsupportedAlgorithmException(
'The only supported hash algorithms are: md2, md5, sha1, sha256, sha384, sha512, sha224, sha512/224, sha512/256'
);
}
$new->mgfHLen = $new->mgfHash->getLengthInBytes();
return $new;
}
/**
* Determines the salt length
*
* Used by RSA::PADDING_PSS
*
* To quote from {@link http://tools.ietf.org/html/rfc3447#page-38 RFC3447#page-38}:
*
* Typical salt lengths in octets are hLen (the length of the output
* of the hash function Hash) and 0.
*
* @access public
* @param int $sLen
*/
public function withSaltLength($sLen)
{
$new = clone $this;
$new->sLen = $sLen;
return $new;
}
/**
* Determines the label
*
* Used by RSA::PADDING_OAEP
*
* To quote from {@link http://tools.ietf.org/html/rfc3447#page-17 RFC3447#page-17}:
*
* Both the encryption and the decryption operations of RSAES-OAEP take
* the value of a label L as input. In this version of PKCS #1, L is
* the empty string; other uses of the label are outside the scope of
* this document.
*
* @access public
* @param string $label
*/
public function withLabel($label)
{
$new = clone $this;
$new->label = $label;
return $new;
}
/**
* Determines the padding modes
*
* Example: $key->withPadding(RSA::ENCRYPTION_PKCS1 | RSA::SIGNATURE_PKCS1);
*
* @access public
* @param string $label
*/
public function withPadding($padding)
{
$masks = [
self::ENCRYPTION_OAEP,
self::ENCRYPTION_PKCS1,
self::ENCRYPTION_NONE,
self::ENCRYPTION_PKCS15_COMPAT
];
$numSelected = 0;
$selected = 0;
foreach ($masks as $mask) {
if ($padding & $mask) {
$selected = $mask;
$numSelected++;
}
}
if ($numSelected > 1) {
throw new InconsistentSetupException('Multiple encryption padding modes have been selected; at most only one should be selected');
}
$encryptionPadding = $selected;
$masks = [
self::SIGNATURE_PSS,
self::SIGNATURE_RELAXED_PKCS1,
self::SIGNATURE_PKCS1
];
$numSelected = 0;
$selected = 0;
foreach ($masks as $mask) {
if ($padding & $mask) {
$selected = $mask;
$numSelected++;
}
}
if ($numSelected > 1) {
throw new InconsistentSetupException('Multiple signature padding modes have been selected; at most only one should be selected');
}
$signaturePadding = $selected;
$new = clone $this;
$new->encryptionPadding = $encryptionPadding;
$new->signaturePadding = $signaturePadding;
return $new;
}
/**
* Returns the current engine being used
*
* @see self::useInternalEngine()
* @see self::useBestEngine()
* @access public
* @return string
*/
public function getEngine()
{
return 'PHP';
}
/**
* Enable RSA Blinding
*
* @access public
*/
public static function enableBlinding()
{
static::$enableBlinding = true;
}
/**
* Disable RSA Blinding
*
* @access public
*/
public static function disableBlinding()
{
static::$enableBlinding = false;
}
}