RSA: changes to how encryption / signatures work

This commit is contained in:
terrafrost 2015-12-24 10:58:06 -06:00
parent 94fdbba2aa
commit 806249c7e9
7 changed files with 219 additions and 149 deletions

View File

@ -48,6 +48,7 @@ namespace phpseclib\Crypt;
use phpseclib\Crypt\Hash; use phpseclib\Crypt\Hash;
use phpseclib\Crypt\Random; use phpseclib\Crypt\Random;
use phpseclib\Math\BigInteger; use phpseclib\Math\BigInteger;
use phpseclib\File\ASN1;
/** /**
* Pure-PHP PKCS#1 compliant implementation of RSA. * Pure-PHP PKCS#1 compliant implementation of RSA.
@ -67,26 +68,32 @@ class RSA
* Use {@link http://en.wikipedia.org/wiki/Optimal_Asymmetric_Encryption_Padding Optimal Asymmetric Encryption Padding} * Use {@link http://en.wikipedia.org/wiki/Optimal_Asymmetric_Encryption_Padding Optimal Asymmetric Encryption Padding}
* (OAEP) for encryption / decryption. * (OAEP) for encryption / decryption.
* *
* Uses sha1 by default. * Uses sha256 by default
* *
* @see self::setHash() * @see self::setHash()
* @see self::setMGFHash() * @see self::setMGFHash()
*/ */
const ENCRYPTION_OAEP = 1; const PADDING_OAEP = 1;
/** /**
* Use PKCS#1 padding. * Use PKCS#1 padding.
* *
* Although self::ENCRYPTION_OAEP offers more security, including PKCS#1 padding is necessary for purposes of backwards * 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. * compatibility with protocols (like SSH-1) written before OAEP's introduction.
*/ */
const ENCRYPTION_PKCS1 = 2; const PADDING_PKCS1 = 2;
/** /**
* Do not use any padding * 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 * 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. * stuff, if you're trying to diagnose why an encrypted message isn't decrypting, etc.
*/ */
const ENCRYPTION_NONE = 3; const PADDING_NONE = 3;
/**
* Use PKCS#1 padding with PKCS1 v1.5 compatability
*
* A PKCS1 v2.1 encrypted message may not successfully decrypt with a PKCS1 v1.5 implementation (such as OpenSSL).
*/
const PADDING_PKCS15_COMPAT = 6;
/**#@-*/ /**#@-*/
/**#@+ /**#@+
@ -98,19 +105,17 @@ class RSA
/** /**
* Use the Probabilistic Signature Scheme for signing * Use the Probabilistic Signature Scheme for signing
* *
* Uses sha1 by default. * Uses sha256 and 0 as the salt length
* *
* @see self::setSaltLength() * @see self::setSaltLength()
* @see self::setMGFHash() * @see self::setMGFHash()
* @see self::setHash()
*/ */
const SIGNATURE_PSS = 1; const PADDING_PSS = 4;
/** /**
* Use the PKCS#1 scheme by default. * Use a relaxed version of PKCS#1 padding for signature verification
*
* Although self::SIGNATURE_PSS offers more security, including PKCS#1 signing is necessary for purposes of backwards
* compatibility with protocols (like SSH-2) written before PSS's introduction.
*/ */
const SIGNATURE_PKCS1 = 2; const PADDING_RELAXED_PKCS1 = 5;
/**#@-*/ /**#@-*/
/**#@+ /**#@+
@ -283,22 +288,6 @@ class RSA
*/ */
var $mgfHLen; var $mgfHLen;
/**
* Encryption mode
*
* @var int
* @access private
*/
var $encryptionMode = self::ENCRYPTION_OAEP;
/**
* Signature mode
*
* @var int
* @access private
*/
var $signatureMode = self::SIGNATURE_PSS;
/** /**
* Public Exponent * Public Exponent
* *
@ -394,10 +383,10 @@ class RSA
{ {
self::_initialize_static_variables(); self::_initialize_static_variables();
$this->hash = new Hash('sha1'); $this->hash = new Hash('sha256');
$this->hLen = $this->hash->getLength(); $this->hLen = $this->hash->getLength();
$this->hashName = 'sha1'; $this->hashName = 'sha256';
$this->mgfHash = new Hash('sha1'); $this->mgfHash = new Hash('sha256');
$this->mgfHLen = $this->mgfHash->getLength(); $this->mgfHLen = $this->mgfHash->getLength();
} }
@ -706,8 +695,6 @@ class RSA
$this->hLen = $key->hLen; $this->hLen = $key->hLen;
$this->sLen = $key->sLen; $this->sLen = $key->sLen;
$this->mgfHLen = $key->mgfHLen; $this->mgfHLen = $key->mgfHLen;
$this->encryptionMode = $key->encryptionMode;
$this->signatureMode = $key->signatureMode;
$this->password = $key->password; $this->password = $key->password;
if (is_object($key->hash)) { if (is_object($key->hash)) {
@ -1233,7 +1220,7 @@ class RSA
/** /**
* Determines which hashing function should be used * Determines which hashing function should be used
* *
* Used with signature production / verification and (if the encryption mode is self::ENCRYPTION_OAEP) encryption and * Used with signature production / verification and (if the encryption mode is self::PADDING_OAEP) encryption and
* decryption. If $hash isn't supported, sha1 is used. * decryption. If $hash isn't supported, sha1 is used.
* *
* @access public * @access public
@ -1253,8 +1240,8 @@ class RSA
$this->hashName = $hash; $this->hashName = $hash;
break; break;
default: default:
$this->hash = new Hash('sha1'); $this->hash = new Hash('sha256');
$this->hashName = 'sha1'; $this->hashName = 'sha256';
} }
$this->hLen = $this->hash->getLength(); $this->hLen = $this->hash->getLength();
} }
@ -1262,7 +1249,7 @@ class RSA
/** /**
* Determines which hashing function should be used for the mask generation function * Determines which hashing function should be used for the mask generation function
* *
* The mask generation function is used by self::ENCRYPTION_OAEP and self::SIGNATURE_PSS and although it's * 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. * best if Hash and MGFHash are set to the same thing this is not a requirement.
* *
* @access public * @access public
@ -1310,14 +1297,13 @@ class RSA
* @access private * @access private
* @param \phpseclib\Math\BigInteger $x * @param \phpseclib\Math\BigInteger $x
* @param int $xLen * @param int $xLen
* @throws \OutOfBoundsException if strlen($x) > $xLen * @return bool|string
* @return string
*/ */
function _i2osp($x, $xLen) function _i2osp($x, $xLen)
{ {
$x = $x->toBytes(); $x = $x->toBytes();
if (strlen($x) > $xLen) { if (strlen($x) > $xLen) {
throw new \OutOfBoundsException('Integer too large'); return false;
} }
return str_pad($x, $xLen, chr(0), STR_PAD_LEFT); return str_pad($x, $xLen, chr(0), STR_PAD_LEFT);
} }
@ -1470,13 +1456,12 @@ class RSA
* *
* @access private * @access private
* @param \phpseclib\Math\BigInteger $m * @param \phpseclib\Math\BigInteger $m
* @throws \OutOfRangeException if $m < 0 or $m > $this->modulus * @return bool|\phpseclib\Math\BigInteger
* @return \phpseclib\Math\BigInteger
*/ */
function _rsaep($m) function _rsaep($m)
{ {
if ($m->compare(self::$zero) < 0 || $m->compare($this->modulus) > 0) { if ($m->compare(self::$zero) < 0 || $m->compare($this->modulus) > 0) {
throw new \OutOfRangeException('Message representative out of range'); return false;
} }
return $this->_exponentiate($m); return $this->_exponentiate($m);
} }
@ -1488,13 +1473,12 @@ class RSA
* *
* @access private * @access private
* @param \phpseclib\Math\BigInteger $c * @param \phpseclib\Math\BigInteger $c
* @throws \OutOfRangeException if $c < 0 or $c > $this->modulus * @return bool|\phpseclib\Math\BigInteger
* @return \phpseclib\Math\BigInteger
*/ */
function _rsadp($c) function _rsadp($c)
{ {
if ($c->compare(self::$zero) < 0 || $c->compare($this->modulus) > 0) { if ($c->compare(self::$zero) < 0 || $c->compare($this->modulus) > 0) {
throw new \OutOfRangeException('Ciphertext representative out of range'); return false;
} }
return $this->_exponentiate($c); return $this->_exponentiate($c);
} }
@ -1506,13 +1490,12 @@ class RSA
* *
* @access private * @access private
* @param \phpseclib\Math\BigInteger $m * @param \phpseclib\Math\BigInteger $m
* @throws \OutOfRangeException if $m < 0 or $m > $this->modulus * @return bool|\phpseclib\Math\BigInteger
* @return \phpseclib\Math\BigInteger
*/ */
function _rsasp1($m) function _rsasp1($m)
{ {
if ($m->compare(self::$zero) < 0 || $m->compare($this->modulus) > 0) { if ($m->compare(self::$zero) < 0 || $m->compare($this->modulus) > 0) {
throw new \OutOfRangeException('Message representative out of range'); return false;
} }
return $this->_exponentiate($m); return $this->_exponentiate($m);
} }
@ -1524,13 +1507,12 @@ class RSA
* *
* @access private * @access private
* @param \phpseclib\Math\BigInteger $s * @param \phpseclib\Math\BigInteger $s
* @throws \OutOfRangeException if $s < 0 or $s > $this->modulus * @return bool|\phpseclib\Math\BigInteger
* @return \phpseclib\Math\BigInteger
*/ */
function _rsavp1($s) function _rsavp1($s)
{ {
if ($s->compare(self::$zero) < 0 || $s->compare($this->modulus) > 0) { if ($s->compare(self::$zero) < 0 || $s->compare($this->modulus) > 0) {
throw new \OutOfRangeException('Signature representative out of range'); return false;
} }
return $this->_exponentiate($s); return $this->_exponentiate($s);
} }
@ -1631,8 +1613,7 @@ class RSA
* @access private * @access private
* @param string $c * @param string $c
* @param string $l * @param string $l
* @throws \RuntimeException on decryption error * @return bool|string
* @return string
*/ */
function _rsaes_oaep_decrypt($c, $l = '') function _rsaes_oaep_decrypt($c, $l = '')
{ {
@ -1642,7 +1623,7 @@ class RSA
// be output. // be output.
if (strlen($c) != $this->k || $this->k < 2 * $this->hLen + 2) { if (strlen($c) != $this->k || $this->k < 2 * $this->hLen + 2) {
throw new \RuntimeException('Decryption error'); return false;
} }
// RSA decryption // RSA decryption
@ -1650,7 +1631,7 @@ class RSA
$c = $this->_os2ip($c); $c = $this->_os2ip($c);
$m = $this->_rsadp($c); $m = $this->_rsadp($c);
if ($m === false) { if ($m === false) {
throw new \RuntimeException('Decryption error'); return false;
} }
$em = $this->_i2osp($m, $this->k); $em = $this->_i2osp($m, $this->k);
@ -1667,11 +1648,11 @@ class RSA
$lHash2 = substr($db, 0, $this->hLen); $lHash2 = substr($db, 0, $this->hLen);
$m = substr($db, $this->hLen); $m = substr($db, $this->hLen);
if ($lHash != $lHash2) { if ($lHash != $lHash2) {
throw new \RuntimeException('Decryption error'); return false;
} }
$m = ltrim($m, chr(0)); $m = ltrim($m, chr(0));
if (ord($m[0]) != 1) { if (ord($m[0]) != 1) {
throw new \RuntimeException('Decryption error'); return false;
} }
// Output the message M // Output the message M
@ -1705,7 +1686,7 @@ class RSA
* @throws \OutOfBoundsException if strlen($m) > $this->k - 11 * @throws \OutOfBoundsException if strlen($m) > $this->k - 11
* @return string * @return string
*/ */
function _rsaes_pkcs1_v1_5_encrypt($m) function _rsaes_pkcs1_v1_5_encrypt($m, $pkcs15_compat = false)
{ {
$mLen = strlen($m); $mLen = strlen($m);
@ -1726,7 +1707,7 @@ class RSA
} }
$type = 2; $type = 2;
// see the comments of _rsaes_pkcs1_v1_5_decrypt() to understand why this is being done // see the comments of _rsaes_pkcs1_v1_5_decrypt() to understand why this is being done
if (defined('CRYPT_RSA_PKCS15_COMPAT') && (!isset($this->publicExponent) || $this->exponent !== $this->publicExponent)) { if ($pkcs15_compat && (!isset($this->publicExponent) || $this->exponent !== $this->publicExponent)) {
$type = 1; $type = 1;
// "The padding string PS shall consist of k-3-||D|| octets. ... for block type 01, they shall have value FF" // "The padding string PS shall consist of k-3-||D|| octets. ... for block type 01, they shall have value FF"
$ps = str_repeat("\xFF", $psLen); $ps = str_repeat("\xFF", $psLen);
@ -1761,15 +1742,14 @@ class RSA
* *
* @access private * @access private
* @param string $c * @param string $c
* @throws \RuntimeException on decryption error * @return bool|string
* @return string
*/ */
function _rsaes_pkcs1_v1_5_decrypt($c) function _rsaes_pkcs1_v1_5_decrypt($c)
{ {
// Length checking // Length checking
if (strlen($c) != $this->k) { // or if k < 11 if (strlen($c) != $this->k) { // or if k < 11
throw new \RuntimeException('Decryption error'); return false;
} }
// RSA decryption // RSA decryption
@ -1778,21 +1758,21 @@ class RSA
$m = $this->_rsadp($c); $m = $this->_rsadp($c);
if ($m === false) { if ($m === false) {
throw new \RuntimeException('Decryption error'); return false;
} }
$em = $this->_i2osp($m, $this->k); $em = $this->_i2osp($m, $this->k);
// EME-PKCS1-v1_5 decoding // EME-PKCS1-v1_5 decoding
if (ord($em[0]) != 0 || ord($em[1]) > 2) { if (ord($em[0]) != 0 || ord($em[1]) > 2) {
throw new \RuntimeException('Decryption error'); return false;
} }
$ps = substr($em, 2, strpos($em, chr(0), 2) - 2); $ps = substr($em, 2, strpos($em, chr(0), 2) - 2);
$m = substr($em, strlen($ps) + 3); $m = substr($em, strlen($ps) + 3);
if (strlen($ps) < 8) { if (strlen($ps) < 8) {
throw new \RuntimeException('Decryption error'); return false;
} }
// Output M // Output M
@ -1820,7 +1800,7 @@ class RSA
$mHash = $this->hash->hash($m); $mHash = $this->hash->hash($m);
if ($emLen < $this->hLen + $sLen + 2) { if ($emLen < $this->hLen + $sLen + 2) {
throw new \RuntimeException('Encoding error'); return false;
} }
$salt = Random::string($sLen); $salt = Random::string($sLen);
@ -1917,7 +1897,6 @@ class RSA
* @access private * @access private
* @param string $m * @param string $m
* @param string $s * @param string $s
* @throws \RuntimeException on invalid signature
* @return string * @return string
*/ */
function _rsassa_pss_verify($m, $s) function _rsassa_pss_verify($m, $s)
@ -1925,7 +1904,7 @@ class RSA
// Length checking // Length checking
if (strlen($s) != $this->k) { if (strlen($s) != $this->k) {
throw new \RuntimeException('Invalid signature'); return false;
} }
// RSA verification // RSA verification
@ -1935,11 +1914,11 @@ class RSA
$s2 = $this->_os2ip($s); $s2 = $this->_os2ip($s);
$m2 = $this->_rsavp1($s2); $m2 = $this->_rsavp1($s2);
if ($m2 === false) { if ($m2 === false) {
throw new \RuntimeException('Invalid signature'); return false;
} }
$em = $this->_i2osp($m2, $modBits >> 3); $em = $this->_i2osp($m2, $modBits >> 3);
if ($em === false) { if ($em === false) {
throw new \RuntimeException('Invalid signature'); return false;
} }
// EMSA-PSS verification // EMSA-PSS verification
@ -2013,9 +1992,11 @@ class RSA
{ {
// EMSA-PKCS1-v1_5 encoding // EMSA-PKCS1-v1_5 encoding
// If the encoding operation outputs "intended encoded message length too short," output "RSA modulus
// too short" and stop.
$em = $this->_emsa_pkcs1_v1_5_encode($m, $this->k); $em = $this->_emsa_pkcs1_v1_5_encode($m, $this->k);
if ($em === false) { if ($em === false) {
throw new \LengthException('RSA modulus too short'); return false;
} }
// RSA signature // RSA signature
@ -2036,16 +2017,15 @@ class RSA
* *
* @access private * @access private
* @param string $m * @param string $m
* @throws \RuntimeException if the signature is invalid
* @throws \LengthException if the RSA modulus is too short * @throws \LengthException if the RSA modulus is too short
* @return string * @return bool|string
*/ */
function _rsassa_pkcs1_v1_5_verify($m, $s) function _rsassa_pkcs1_v1_5_verify($m, $s)
{ {
// Length checking // Length checking
if (strlen($s) != $this->k) { if (strlen($s) != $this->k) {
throw new \RuntimeException('Invalid signature'); return false;
} }
// RSA verification // RSA verification
@ -2053,17 +2033,20 @@ class RSA
$s = $this->_os2ip($s); $s = $this->_os2ip($s);
$m2 = $this->_rsavp1($s); $m2 = $this->_rsavp1($s);
if ($m2 === false) { if ($m2 === false) {
throw new \RuntimeException('Invalid signature'); return false;
} }
$em = $this->_i2osp($m2, $this->k); $em = $this->_i2osp($m2, $this->k);
if ($em === false) { if ($em === false) {
throw new \RuntimeException('Invalid signature'); return false;
} }
// EMSA-PKCS1-v1_5 encoding // EMSA-PKCS1-v1_5 encoding
// 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); $em2 = $this->_emsa_pkcs1_v1_5_encode($m, $this->k);
if ($em2 === false) { } catch (\LengthException $e) {
throw new \LengthException('RSA modulus too short'); throw new \LengthException('RSA modulus too short');
} }
@ -2072,55 +2055,133 @@ class RSA
} }
/** /**
* Set Encryption Mode * RSASSA-PKCS1-V1_5-VERIFY (relaxed matching)
* *
* Valid values include self::ENCRYPTION_OAEP and self::ENCRYPTION_PKCS1. * 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.
* *
* @access public * @access private
* @param int $mode * @param string $m
* @return bool|string
*/ */
function setEncryptionMode($mode) function _rsassa_pkcs1_v1_5_relaxed_verify($m, $s)
{ {
$this->encryptionMode = $mode; // Length checking
if (strlen($s) != $this->k) {
return false;
} }
/** // RSA verification
* Set Signature Mode
* $s = $this->_os2ip($s);
* Valid values include self::SIGNATURE_PSS and self::SIGNATURE_PKCS1 $m2 = $this->_rsavp1($s);
* if ($m2 === false) {
* @access public return false;
* @param int $mode }
*/ $em = $this->_i2osp($m2, $this->k);
function setSignatureMode($mode) if ($em === false) {
{ return false;
$this->signatureMode = $mode; }
if ($this->_string_shift($em, 2) != "\0\1") {
return false;
}
$em = ltrim($em, "\xFF");
if ($this->_string_shift($em) != "\0") {
return false;
}
$asn1 = new ASN1();
$decoded = $asn1->decodeBER($em);
if (!is_array($decoded) || empty($decoded[0]) || strlen($em) > $decoded[0]['length']) {
return false;
}
$AlgorithmIdentifier = array(
'type' => ASN1::TYPE_SEQUENCE,
'children' => array(
'algorithm' => array('type' => ASN1::TYPE_OBJECT_IDENTIFIER),
'parameters' => array(
'type' => ASN1::TYPE_ANY,
'optional' => true
)
)
);
$DigestInfo = array(
'type' => ASN1::TYPE_SEQUENCE,
'children' => array(
'digestAlgorithm' => $AlgorithmIdentifier,
'digest' => array('type' => ASN1::TYPE_OCTET_STRING)
)
);
$oids = array(
'1.2.840.113549.2.2' => 'md2',
'1.2.840.113549.2.4' => 'md4', // from PKCS1 v1.5
'1.2.840.113549.2.5' => 'md5',
'1.3.14.3.2.26' => 'sha1',
'2.16.840.1.101.3.4.2.1' => 'sha256',
'2.16.840.1.101.3.4.2.2' => 'sha384',
'2.16.840.1.101.3.4.2.3' => 'sha512',
// from PKCS1 v2.2
//'2.16.840.1.101.3.4.2.5' => 'sha512/224',
//'2.16.840.1.101.3.4.2.6' => 'sha512/256',
);
$asn1->loadOIDs($oids);
$decoded = $asn1->asn1map($decoded[0], $DigestInfo);
if (!isset($decoded) || $decoded === false) {
return false;
}
if (!in_array($decoded['digestAlgorithm']['algorithm'], $oids)) {
return false;
}
$hash = new Hash($decoded['digestAlgorithm']['algorithm']);
$em = $hash->hash($m);
$em2 = base64_decode($decoded['digest']);
return $this->_equals($em, $em2);
} }
/** /**
* Encryption * Encryption
* *
* Both self::ENCRYPTION_OAEP and self::ENCRYPTION_PKCS1 both place limits on how long $plaintext can be. * 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 * If $plaintext exceeds those limits it will be broken up so that it does and the resultant ciphertext's will
* be concatenated together. * be concatenated together.
* *
* @see self::decrypt() * @see self::decrypt()
* @access public * @access public
* @param string $plaintext * @param string $plaintext
* @param int $padding
* @return string * @return string
* @throws \LengthException if the RSA modulus is too short * @throws \LengthException if the RSA modulus is too short
*/ */
function encrypt($plaintext) function encrypt($plaintext, $padding = self::PADDING_OAEP)
{ {
switch ($this->encryptionMode) { switch ($padding) {
case self::ENCRYPTION_NONE: case self::PADDING_NONE:
$plaintext = str_split($plaintext, $this->k); $plaintext = str_split($plaintext, $this->k);
$ciphertext = ''; $ciphertext = '';
foreach ($plaintext as $m) { foreach ($plaintext as $m) {
$ciphertext.= $this->_raw_encrypt($m); $ciphertext.= $this->_raw_encrypt($m);
} }
return $ciphertext; return $ciphertext;
case self::ENCRYPTION_PKCS1: case self::PADDING_PKCS15_COMPAT:
case self::PADDING_PKCS1:
$length = $this->k - 11; $length = $this->k - 11;
if ($length <= 0) { if ($length <= 0) {
throw new \LengthException('RSA modulus too short (' . $this->k . ' bytes long; should be more than 11 bytes with PKCS1)'); throw new \LengthException('RSA modulus too short (' . $this->k . ' bytes long; should be more than 11 bytes with PKCS1)');
@ -2129,14 +2190,14 @@ class RSA
$plaintext = str_split($plaintext, $length); $plaintext = str_split($plaintext, $length);
$ciphertext = ''; $ciphertext = '';
foreach ($plaintext as $m) { foreach ($plaintext as $m) {
$ciphertext.= $this->_rsaes_pkcs1_v1_5_encrypt($m); $ciphertext.= $this->_rsaes_pkcs1_v1_5_encrypt($m, $padding == self::PADDING_PKCS15_COMPAT);
} }
return $ciphertext; return $ciphertext;
//case self::ENCRYPTION_OAEP: //case self::PADDING_OAEP:
default: default:
$length = $this->k - 2 * $this->hLen - 2; $length = $this->k - 2 * $this->hLen - 2;
if ($length <= 0) { if ($length <= 0) {
throw new \LengthException('RSA modulus too short (' . $this->k . ' bytes long; should be more than ' . (2 * $this->hLen - 2) . ' bytes with OAEP / ' . $this->hashName . ')'); throw new \LengthException('RSA modulus too short (' . $this->k . ' bytes long; should be more than ' . (2 * $this->hLen + 2) . ' bytes with OAEP / ' . $this->hashName . ')');
} }
$plaintext = str_split($plaintext, $length); $plaintext = str_split($plaintext, $length);
@ -2154,9 +2215,10 @@ class RSA
* @see self::encrypt() * @see self::encrypt()
* @access public * @access public
* @param string $plaintext * @param string $plaintext
* @param int|bool $padding
* @return string * @return string
*/ */
function decrypt($ciphertext) function decrypt($ciphertext, $padding = self::PADDING_OAEP)
{ {
if ($this->k <= 0) { if ($this->k <= 0) {
return false; return false;
@ -2167,14 +2229,14 @@ class RSA
$plaintext = ''; $plaintext = '';
switch ($this->encryptionMode) { switch ($padding) {
case self::ENCRYPTION_NONE: case self::PADDING_NONE:
$decrypt = '_raw_encrypt'; $decrypt = '_raw_encrypt';
break; break;
case self::ENCRYPTION_PKCS1: case self::PADDING_PKCS1:
$decrypt = '_rsaes_pkcs1_v1_5_decrypt'; $decrypt = '_rsaes_pkcs1_v1_5_decrypt';
break; break;
//case self::ENCRYPTION_OAEP: //case self::PADDING_OAEP:
default: default:
$decrypt = '_rsaes_oaep_decrypt'; $decrypt = '_rsaes_oaep_decrypt';
} }
@ -2196,18 +2258,20 @@ class RSA
* @see self::verify() * @see self::verify()
* @access public * @access public
* @param string $message * @param string $message
* @param int $padding
* @return string * @return string
*/ */
function sign($message) function sign($message, $padding = self::PADDING_PSS)
{ {
if (empty($this->modulus) || empty($this->exponent)) { if (empty($this->modulus) || empty($this->exponent)) {
return false; return false;
} }
switch ($this->signatureMode) { switch ($padding) {
case self::SIGNATURE_PKCS1: case self::PADDING_PKCS1:
case self::PADDING_RELAXED_PKCS1:
return $this->_rsassa_pkcs1_v1_5_sign($message); return $this->_rsassa_pkcs1_v1_5_sign($message);
//case self::SIGNATURE_PSS: //case self::PADDING_PSS:
default: default:
return $this->_rsassa_pss_sign($message); return $this->_rsassa_pss_sign($message);
} }
@ -2220,18 +2284,21 @@ class RSA
* @access public * @access public
* @param string $message * @param string $message
* @param string $signature * @param string $signature
* @param int|bool $padding
* @return bool * @return bool
*/ */
function verify($message, $signature) function verify($message, $signature, $padding = self::PADDING_PSS)
{ {
if (empty($this->modulus) || empty($this->exponent)) { if (empty($this->modulus) || empty($this->exponent)) {
return false; return false;
} }
switch ($this->signatureMode) { switch ($padding) {
case self::SIGNATURE_PKCS1: case self::PADDING_RELAXED_PKCS1:
return $this->_rsassa_pkcs1_v1_5_relaxed_verify($message, $signature);
case self::PADDING_PKCS1:
return $this->_rsassa_pkcs1_v1_5_verify($message, $signature); return $this->_rsassa_pkcs1_v1_5_verify($message, $signature);
//case self::SIGNATURE_PSS: //case self::PADDING_PSS:
default: default:
return $this->_rsassa_pss_verify($message, $signature); return $this->_rsassa_pss_verify($message, $signature);
} }

View File

@ -2149,8 +2149,7 @@ class X509
case 'sha384WithRSAEncryption': case 'sha384WithRSAEncryption':
case 'sha512WithRSAEncryption': case 'sha512WithRSAEncryption':
$rsa->setHash(preg_replace('#WithRSAEncryption$#', '', $signatureAlgorithm)); $rsa->setHash(preg_replace('#WithRSAEncryption$#', '', $signatureAlgorithm));
$rsa->setSignatureMode(RSA::SIGNATURE_PKCS1); if (!@$rsa->verify($signatureSubject, $signature, RSA::PADDING_PKCS1)) {
if (!@$rsa->verify($signatureSubject, $signature)) {
return false; return false;
} }
break; break;
@ -3671,9 +3670,8 @@ class X509
case 'sha384WithRSAEncryption': case 'sha384WithRSAEncryption':
case 'sha512WithRSAEncryption': case 'sha512WithRSAEncryption':
$key->setHash(preg_replace('#WithRSAEncryption$#', '', $signatureAlgorithm)); $key->setHash(preg_replace('#WithRSAEncryption$#', '', $signatureAlgorithm));
$key->setSignatureMode(RSA::SIGNATURE_PKCS1);
$this->currentCert['signature'] = base64_encode("\0" . $key->sign($this->signatureSubject)); $this->currentCert['signature'] = base64_encode("\0" . $key->sign($this->signatureSubject, RSA::PADDING_PKCS1));
return $this->currentCert; return $this->currentCert;
default: default:
throw new UnsupportedAlgorithmException('Signature algorithm unsupported'); throw new UnsupportedAlgorithmException('Signature algorithm unsupported');

View File

@ -1302,8 +1302,7 @@ class SSH1
/* /*
$rsa = new RSA(); $rsa = new RSA();
$rsa->load($key, 'raw'); $rsa->load($key, 'raw');
$rsa->setEncryptionMode(RSA::ENCRYPTION_PKCS1); return $rsa->encrypt($m, RSA::PADDING_PKCS1);
return $rsa->encrypt($m);
*/ */
// To quote from protocol-1.5.txt: // To quote from protocol-1.5.txt:

View File

@ -2306,8 +2306,7 @@ class SSH2
} }
$packet = $part1 . chr(1) . $part2; $packet = $part1 . chr(1) . $part2;
$privatekey->setSignatureMode(RSA::SIGNATURE_PKCS1); $signature = $privatekey->sign(pack('Na*a*', strlen($this->session_id), $this->session_id, $packet), RSA::PADDING_PKCS1);
$signature = $privatekey->sign(pack('Na*a*', strlen($this->session_id), $this->session_id, $packet));
$signature = pack('Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($signature), $signature); $signature = pack('Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($signature), $signature);
$packet.= pack('Na*', strlen($signature), $signature); $packet.= pack('Na*', strlen($signature), $signature);
@ -4054,9 +4053,8 @@ class SSH2
$signature = $this->_string_shift($signature, $temp['length']); $signature = $this->_string_shift($signature, $temp['length']);
$rsa = new RSA(); $rsa = new RSA();
$rsa->setSignatureMode(RSA::SIGNATURE_PKCS1);
$rsa->load(array('e' => $e, 'n' => $n), 'raw'); $rsa->load(array('e' => $e, 'n' => $n), 'raw');
if (!$rsa->verify($this->exchange_hash, $signature)) { if (!$rsa->verify($this->exchange_hash, $signature, RSA::PADDING_PKCS1)) {
//user_error('Bad server signature'); //user_error('Bad server signature');
return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);
} }

View File

@ -23,9 +23,8 @@ use phpseclib\System\SSH\Agent;
* Instantiation should only be performed by \phpseclib\System\SSH\Agent class. * Instantiation should only be performed by \phpseclib\System\SSH\Agent class.
* This could be thought of as implementing an interface that phpseclib\Crypt\RSA * This could be thought of as implementing an interface that phpseclib\Crypt\RSA
* implements. ie. maybe a Net_SSH_Auth_PublicKey interface or something. * implements. ie. maybe a Net_SSH_Auth_PublicKey interface or something.
* The methods in this interface would be getPublicKey, setSignatureMode * The methods in this interface would be getPublicKey and sign since those are the
* and sign since those are the methods phpseclib looks for to perform * methods phpseclib looks for to perform public key authentication.
* public key authentication.
* *
* @package SSH\Agent * @package SSH\Agent
* @author Jim Wigginton <terrafrost@php.net> * @author Jim Wigginton <terrafrost@php.net>
@ -114,30 +113,18 @@ class Identity
return !isset($format) ? $this->key->getPublicKey() : $this->key->getPublicKey($format); return !isset($format) ? $this->key->getPublicKey() : $this->key->getPublicKey($format);
} }
/**
* Set Signature Mode
*
* Doesn't do anything as ssh-agent doesn't let you pick and choose the signature mode. ie.
* ssh-agent's only supported mode is \phpseclib\Crypt\RSA::SIGNATURE_PKCS1
*
* @param int $mode
* @access public
*/
function setSignatureMode($mode)
{
}
/** /**
* Create a signature * Create a signature
* *
* See "2.6.2 Protocol 2 private key signature request" * See "2.6.2 Protocol 2 private key signature request"
* *
* @param string $message * @param string $message
* @param int|bool $padding
* @return string * @return string
* @throws \RuntimeException on connection errors * @throws \RuntimeException on connection errors
* @access public * @access public
*/ */
function sign($message) function sign($message, $padding = false)
{ {
// the last parameter (currently 0) is for flags and ssh-agent only defines one flag (for ssh-dss): SSH_AGENT_OLD_SIGNATURE // the last parameter (currently 0) is for flags and ssh-agent only defines one flag (for ssh-dss): SSH_AGENT_OLD_SIGNATURE
$packet = pack('CNa*Na*N', Agent::SSH_AGENTC_SIGN_REQUEST, strlen($this->key_blob), $this->key_blob, strlen($message), $message, 0); $packet = pack('CNa*Na*N', Agent::SSH_AGENTC_SIGN_REQUEST, strlen($this->key_blob), $this->key_blob, strlen($message), $message, 0);

View File

@ -11,7 +11,7 @@ class Unit_Crypt_RSA_CreateKeyTest extends PhpseclibTestCase
{ {
public function testCreateKey() public function testCreateKey()
{ {
extract(RSA::createKey(512)); extract(RSA::createKey(768));
$this->assertInstanceOf('\phpseclib\Crypt\RSA', $privatekey); $this->assertInstanceOf('\phpseclib\Crypt\RSA', $privatekey);
$this->assertInstanceOf('\phpseclib\Crypt\RSA', $publickey); $this->assertInstanceOf('\phpseclib\Crypt\RSA', $publickey);
$this->assertNotEmpty("$privatekey"); $this->assertNotEmpty("$privatekey");

View File

@ -32,17 +32,16 @@ U9VQQSQzY1oZMVX8i1m5WUTLPz2yLJIBQVdXqhMCQBGoiuSoSjafUhV7i1cEGpb88h5NBYZzWXGZ
$rsa->load($privatekey); $rsa->load($privatekey);
$rsa->load($rsa->getPublicKey()); $rsa->load($rsa->getPublicKey());
$rsa->setEncryptionMode(RSA::ENCRYPTION_NONE);
$expected = '105b92f59a87a8ad4da52c128b8c99491790ef5a54770119e0819060032fb9e772ed6772828329567f3d7e9472154c1530f8156ba7fd732f52ca1c06' . $expected = '105b92f59a87a8ad4da52c128b8c99491790ef5a54770119e0819060032fb9e772ed6772828329567f3d7e9472154c1530f8156ba7fd732f52ca1c06' .
'5a3f5ed8a96c442e4662e0464c97f133aed31262170201993085a589565d67cc9e727e0d087e3b225c8965203b271e38a499c92fc0d6502297eca712' . '5a3f5ed8a96c442e4662e0464c97f133aed31262170201993085a589565d67cc9e727e0d087e3b225c8965203b271e38a499c92fc0d6502297eca712' .
'4d04bd467f6f1e7c'; '4d04bd467f6f1e7c';
$expected = pack('H*', $expected); $expected = pack('H*', $expected);
$result = $rsa->encrypt($plaintext); $result = $rsa->encrypt($plaintext, RSA::PADDING_NONE);
$this->assertEquals($result, $expected); $this->assertEquals($result, $expected);
$rsa->load($privatekey); $rsa->load($privatekey);
$this->assertEquals(trim($rsa->decrypt($result), "\0"), $plaintext); $this->assertEquals(trim($rsa->decrypt($result, RSA::PADDING_NONE), "\0"), $plaintext);
} }
/** /**
@ -51,6 +50,8 @@ U9VQQSQzY1oZMVX8i1m5WUTLPz2yLJIBQVdXqhMCQBGoiuSoSjafUhV7i1cEGpb88h5NBYZzWXGZ
public function testPSSSigs() public function testPSSSigs()
{ {
$rsa = new RSA(); $rsa = new RSA();
$rsa->setHash('sha1');
$rsa->setMGFHash('sha1');
$rsa->load('-----BEGIN PUBLIC KEY----- $rsa->load('-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCqGKukO1De7zhZj6+H0qtjTkVx MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCqGKukO1De7zhZj6+H0qtjTkVx
wTCpvKe4eCZ0FPqri0cb2JZfXJ/DgYSF6vUpwmJG8wVQZKjeGcjDOL5UlsuusFnc wTCpvKe4eCZ0FPqri0cb2JZfXJ/DgYSF6vUpwmJG8wVQZKjeGcjDOL5UlsuusFnc
@ -78,4 +79,24 @@ p0GbMJDyR4e9T04ZZwIDAQAB
$rsa->load(array('n' => $n, 'e' => $e)); $rsa->load(array('n' => $n, 'e' => $e));
$rsa->encrypt($plaintext); $rsa->encrypt($plaintext);
} }
public function testPKCS1LooseVerify()
{
$rsa = new RSA();
$rsa->load('-----BEGIN RSA PUBLIC KEY-----
MIGJAoGBAMuqkz8ij+ESAaNvgocVGmapjlrIldmhRo4h2NX4e6IXiCLTSxASQtY4
iqRnmyxqQSfaan2okTfQ6sP95bl8Qz8lgneW3ClC6RXG/wpJgsx7TXQ2kodlcKBF
m4k72G75QXhZ+I40ZG7cjBf1/9egakR0a0X0MpeOrKCzMBLv9+mpAgMBAAE=
-----END RSA PUBLIC KEY-----');
$message = base64_decode('MYIBLjAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0xNDA1MTUxNDM4MzRaMC8GCSqGSIb3DQEJBDEiBCBLzLIBGdOf0L2WRrIY' .
'9KTwiHnReBW48S9C7LNRaPp5mDCBwgYLKoZIhvcNAQkQAi8xgbIwga8wgawwgakEIJDB9ZGwihf+TaiwrHQNkNHkqbN8Nuws0e77QNObkvFZMIGEMHCkbjBs' .
'MQswCQYDVQQGEwJJVDEYMBYGA1UECgwPQXJ1YmFQRUMgUy5wLkEuMSEwHwYDVQQLDBhDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eUMxIDAeBgNVBAMMF0FydWJh' .
'UEVDIFMucC5BLiBORyBDQSAzAhAv4L3QcFssQNLDYN/Vu40R');
$sig = base64_decode('XDSZWw6IcUj8ICxRJf04HzF8stzoiFAZSR2a0Rw3ziZxTOT0/NVUYJO5+9TaaREXEgxuCLpgmA+6W2SWrrGoxbbNfaI90ZoKeOAws4IX+9RfiWuooibjKcvt' .
'GJYVVOCcjvQYxUUNbQ4EjCUonk3h7ECXfCCmWqbeq2LsyXeeYGE=');
$this->assertTrue($rsa->verify($message, $sig, RSA::PADDING_RELAXED_PKCS1));
}
} }