diff --git a/phpseclib/Crypt/Base.php b/phpseclib/Crypt/Base.php index 458ba198..0892796d 100644 --- a/phpseclib/Crypt/Base.php +++ b/phpseclib/Crypt/Base.php @@ -541,7 +541,7 @@ class Crypt_Base * Sets the password. * * Depending on what $method is set to, setPassword()'s (optional) parameters are as follows: - * {@link http://en.wikipedia.org/wiki/PBKDF2 pbkdf2}: + * {@link http://en.wikipedia.org/wiki/PBKDF2 pbkdf2} or pbkdf1: * $hash, $salt, $count, $dkLen * * Where $hash (default = sha1) currently supports the following hashes: see: Crypt/Hash.php @@ -551,6 +551,7 @@ class Crypt_Base * @see Crypt/Hash.php * @param String $password * @param optional String $method + * @return Boolean * @access public */ function setPassword($password, $method = 'pbkdf2') @@ -558,7 +559,7 @@ class Crypt_Base $key = ''; switch ($method) { - default: // 'pbkdf2' + default: // 'pbkdf2' or 'pbkdf1' $func_args = func_get_args(); // Hash function @@ -572,10 +573,34 @@ class Crypt_Base $count = isset($func_args[4]) ? $func_args[4] : 1000; // Keylength - $dkLen = isset($func_args[5]) ? $func_args[5] : $this->password_key_size; + if (isset($func_args[5])) { + $dkLen = $func_args[5]; + } else { + $dkLen = $method == 'pbkdf1' ? 2 * $this->password_key_size : $this->password_key_size; + } - // Determining if php[>=5.5.0]'s hash_pbkdf2() function avail- and useable switch (true) { + case $method == 'pbkdf1': + if (!class_exists('Crypt_Hash')) { + include_once 'Crypt/Hash.php'; + } + $hashObj = new Crypt_Hash(); + $hashObj->setHash($hash); + if ($dkLen > $hashObj->getLength()) { + user_error('Derived key too long'); + return false; + } + $t = $password . $salt; + for ($i = 0; $i < $count; ++$i) { + $t = $hashObj->hash($t); + } + $key = substr($t, 0, $dkLen); + + $this->setKey(substr($key, 0, $dkLen >> 1)); + $this->setIV(substr($key, $dkLen >> 1)); + + return true; + // Determining if php[>=5.5.0]'s hash_pbkdf2() function avail- and useable case !function_exists('hash_pbkdf2'): case !function_exists('hash_algos'): case !in_array($hash, hash_algos()): @@ -602,6 +627,8 @@ class Crypt_Base } $this->setKey($key); + + return true; } /** diff --git a/phpseclib/Crypt/RSA.php b/phpseclib/Crypt/RSA.php index ff5eb17d..16e14101 100644 --- a/phpseclib/Crypt/RSA.php +++ b/phpseclib/Crypt/RSA.php @@ -142,15 +142,23 @@ define('CRYPT_RSA_SIGNATURE_PKCS1', 2); /** * ASN1 Integer */ -define('CRYPT_RSA_ASN1_INTEGER', 2); +define('CRYPT_RSA_ASN1_INTEGER', 2); /** * ASN1 Bit String */ -define('CRYPT_RSA_ASN1_BITSTRING', 3); +define('CRYPT_RSA_ASN1_BITSTRING', 3); +/** + * ASN1 Octet String + */ +define('CRYPT_RSA_ASN1_OCTETSTRING', 4); +/** + * ASN1 Object Identifier + */ +define('CRYPT_RSA_ASN1_OBJECT', 6); /** * ASN1 Sequence (with the constucted bit set) */ -define('CRYPT_RSA_ASN1_SEQUENCE', 48); +define('CRYPT_RSA_ASN1_SEQUENCE', 48); /**#@-*/ /**#@+ @@ -193,6 +201,10 @@ define('CRYPT_RSA_PRIVATE_FORMAT_PUTTY', 1); * XML formatted private key */ define('CRYPT_RSA_PRIVATE_FORMAT_XML', 2); +/** + * PKCS#8 formatted private key + */ +define('CRYPT_RSA_PRIVATE_FORMAT_PKCS8', 3); /**#@-*/ /**#@+ @@ -225,6 +237,7 @@ define('CRYPT_RSA_PUBLIC_FORMAT_RAW', 3); * * Analogous to ssh-keygen's pem format (as specified by -m) */ +define('CRYPT_RSA_PUBLIC_FORMAT_PKCS1', 4); define('CRYPT_RSA_PUBLIC_FORMAT_PKCS1_RAW', 4); /** * XML formatted public key @@ -245,11 +258,11 @@ define('CRYPT_RSA_PUBLIC_FORMAT_OPENSSH', 6); * * -----BEGIN PUBLIC KEY----- * - * Analogous to ssh-keygen's pkcs8 format (as specified by -m) - * (the applicability of PKCS8 is dubious since PKCS8 is talking about - * private keys but whatever) + * 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) */ -define('CRYPT_RSA_PUBLIC_FORMAT_PKCS1', 7); +define('CRYPT_RSA_PUBLIC_FORMAT_PKCS8', 7); /**#@-*/ /** @@ -291,7 +304,7 @@ class Crypt_RSA * @var Integer * @access public */ - var $publicKeyFormat = CRYPT_RSA_PUBLIC_FORMAT_PKCS1; + var $publicKeyFormat = CRYPT_RSA_PUBLIC_FORMAT_PKCS8; /** * Modulus (ie. n) @@ -844,6 +857,52 @@ class Crypt_RSA $RSAPrivateKey = pack('Ca*a*', CRYPT_RSA_ASN1_SEQUENCE, $this->_encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey); + if ($this->privateKeyFormat == CRYPT_RSA_PRIVATE_FORMAT_PKCS8) { + $rsaOID = pack('H*', '300d06092a864886f70d0101010500'); // hex version of MA0GCSqGSIb3DQEBAQUA + $RSAPrivateKey = pack('Ca*a*Ca*a*', + CRYPT_RSA_ASN1_INTEGER, "\01\00", $rsaOID, 4, $this->_encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey + ); + $RSAPrivateKey = pack('Ca*a*', CRYPT_RSA_ASN1_SEQUENCE, $this->_encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey); + if (!empty($this->password) || is_string($this->password)) { + $salt = crypt_random_string(8); + $iterationCount = 2048; + + if (!class_exists('Crypt_DES')) { + include_once 'Crypt/DES.php'; + } + $crypto = new Crypt_DES(); + $crypto->setPassword($this->password, 'pbkdf1', 'md5', $salt, $iterationCount); + $RSAPrivateKey = $crypto->encrypt($RSAPrivateKey); + + $parameters = pack('Ca*a*Ca*N', + CRYPT_RSA_ASN1_OCTETSTRING, $this->_encodeLength(strlen($salt)), $salt, + CRYPT_RSA_ASN1_INTEGER, $this->_encodeLength(4), $iterationCount + ); + $pbeWithMD5AndDES_CBC = "\x2a\x86\x48\x86\xf7\x0d\x01\x05\x03"; + + $encryptionAlgorithm = pack('Ca*a*Ca*a*', + CRYPT_RSA_ASN1_OBJECT, $this->_encodeLength(strlen($pbeWithMD5AndDES_CBC)), $pbeWithMD5AndDES_CBC, + CRYPT_RSA_ASN1_SEQUENCE, $this->_encodeLength(strlen($parameters)), $parameters + ); + + $RSAPrivateKey = pack('Ca*a*Ca*a*', + CRYPT_RSA_ASN1_SEQUENCE, $this->_encodeLength(strlen($encryptionAlgorithm)), $encryptionAlgorithm, + CRYPT_RSA_ASN1_OCTETSTRING, $this->_encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey + ); + + $RSAPrivateKey = pack('Ca*a*', CRYPT_RSA_ASN1_SEQUENCE, $this->_encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey); + + $RSAPrivateKey = "-----BEGIN ENCRYPTED PRIVATE KEY-----\r\n" . + chunk_split(base64_encode($RSAPrivateKey), 64) . + '-----END ENCRYPTED PRIVATE KEY-----'; + } else { + $RSAPrivateKey = "-----BEGIN PRIVATE KEY-----\r\n" . + chunk_split(base64_encode($RSAPrivateKey), 64) . + '-----END PRIVATE KEY-----'; + } + return $RSAPrivateKey; + } + if (!empty($this->password) || is_string($this->password)) { $iv = crypt_random_string(8); $symkey = pack('H*', md5($this->password . $iv)); // symkey is short for symmetric key @@ -991,6 +1050,7 @@ class Crypt_RSA } return isset($components['modulus']) && isset($components['publicExponent']) ? $components : false; case CRYPT_RSA_PRIVATE_FORMAT_PKCS1: + case CRYPT_RSA_PRIVATE_FORMAT_PKCS8: case CRYPT_RSA_PUBLIC_FORMAT_PKCS1: /* Although PKCS#1 proposes a format that public and private keys can use, encrypting them is "outside the scope" of PKCS#1. PKCS#1 then refers you to PKCS#12 and PKCS#15 if you're wanting to @@ -1081,7 +1141,9 @@ class Crypt_RSA 7:d=1 hl=2 l= 13 cons: SEQUENCE 9:d=2 hl=2 l= 9 prim: OBJECT :rsaEncryption 20:d=2 hl=2 l= 0 prim: NULL - 22:d=1 hl=4 l= 609 prim: OCTET STRING */ + 22:d=1 hl=4 l= 609 prim: OCTET STRING + + ie. PKCS8 keys*/ if ($tag == CRYPT_RSA_ASN1_INTEGER && substr($key, 0, 3) == "\x01\x00\x30") { $this->_string_shift($key, 3); @@ -1089,6 +1151,52 @@ class Crypt_RSA } if ($tag == CRYPT_RSA_ASN1_SEQUENCE) { + $temp = $this->_string_shift($key, $this->_decodeLength($key)); + if (ord($this->_string_shift($temp)) != CRYPT_RSA_ASN1_OBJECT) { + return false; + } + $length = $this->_decodeLength($temp); + switch ($this->_string_shift($temp, $length)) { + case "\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01": // rsaEncryption + break; + case "\x2a\x86\x48\x86\xf7\x0d\x01\x05\x03": // pbeWithMD5AndDES-CBC + /* + PBEParameter ::= SEQUENCE { + salt OCTET STRING (SIZE(8)), + iterationCount INTEGER } + */ + if (ord($this->_string_shift($temp)) != CRYPT_RSA_ASN1_SEQUENCE) { + return false; + } + if ($this->_decodeLength($temp) != strlen($temp)) { + return false; + } + $this->_string_shift($temp); // assume it's an octet string + $salt = $this->_string_shift($temp, $this->_decodeLength($temp)); + if (ord($this->_string_shift($temp)) != CRYPT_RSA_ASN1_INTEGER) { + return false; + } + $this->_decodeLength($temp); + list(, $iterationCount) = unpack('N', str_pad($temp, 4, chr(0), STR_PAD_LEFT)); + $this->_string_shift($key); // assume it's an octet string + $length = $this->_decodeLength($key); + if (strlen($key) != $length) { + return false; + } + + if (!class_exists('Crypt_DES')) { + include_once 'Crypt/DES.php'; + } + $crypto = new Crypt_DES(); + $crypto->setPassword($this->password, 'pbkdf1', 'md5', $salt, $iterationCount); + $key = $crypto->decrypt($key); + if ($key === false) { + return false; + } + return $this->_parseKey($key, CRYPT_RSA_PRIVATE_FORMAT_PKCS1); + default: + return false; + } /* intended for keys for which OpenSSL's asn1parse returns the following: 0:d=0 hl=4 l= 290 cons: SEQUENCE @@ -1096,7 +1204,6 @@ class Crypt_RSA 6:d=2 hl=2 l= 9 prim: OBJECT :rsaEncryption 17:d=2 hl=2 l= 0 prim: NULL 19:d=1 hl=4 l= 271 prim: BIT STRING */ - $this->_string_shift($key, $this->_decodeLength($key)); $tag = ord($this->_string_shift($key)); // skip over the BIT STRING / OCTET STRING tag $this->_decodeLength($key); // skip over the BIT STRING / OCTET STRING length // "The initial octet shall encode, as an unsigned binary integer wtih bit 1 as the least significant bit, the number of @@ -1641,7 +1748,7 @@ class Crypt_RSA * @param String $key * @param Integer $type optional */ - function getPublicKey($type = CRYPT_RSA_PUBLIC_FORMAT_PKCS1) + function getPublicKey($type = CRYPT_RSA_PUBLIC_FORMAT_PKCS8) { if (empty($this->modulus) || empty($this->publicExponent)) { return false; @@ -1688,7 +1795,7 @@ class Crypt_RSA * @param String $key * @param Integer $type optional */ - function _getPrivatePublicKey($mode = CRYPT_RSA_PUBLIC_FORMAT_PKCS1) + function _getPrivatePublicKey($mode = CRYPT_RSA_PUBLIC_FORMAT_PKCS8) { if (empty($this->modulus) || empty($this->exponent)) { return false; diff --git a/phpseclib/File/X509.php b/phpseclib/File/X509.php index 9cac4eb6..172e3661 100644 --- a/phpseclib/File/X509.php +++ b/phpseclib/File/X509.php @@ -4233,7 +4233,7 @@ class File_X509 } return false; default: // Should be a key object (i.e.: Crypt_RSA). - $key = $key->getPublicKey(CRYPT_RSA_PUBLIC_FORMAT_PKCS1_RAW); + $key = $key->getPublicKey(CRYPT_RSA_PUBLIC_FORMAT_PKCS1); break; } @@ -4274,7 +4274,7 @@ class File_X509 //return new File_ASN1_Element(base64_decode(preg_replace('#-.+-|[\r\n]#', '', $this->publicKey->getPublicKey()))); return array( 'algorithm' => array('algorithm' => 'rsaEncryption'), - 'subjectPublicKey' => $this->publicKey->getPublicKey(CRYPT_RSA_PUBLIC_FORMAT_PKCS1_RAW) + 'subjectPublicKey' => $this->publicKey->getPublicKey(CRYPT_RSA_PUBLIC_FORMAT_PKCS1) ); default: return false; diff --git a/tests/Unit/Crypt/RSA/LoadKeyTest.php b/tests/Unit/Crypt/RSA/LoadKeyTest.php index cf80d82e..9402ab42 100644 --- a/tests/Unit/Crypt/RSA/LoadKeyTest.php +++ b/tests/Unit/Crypt/RSA/LoadKeyTest.php @@ -124,6 +124,72 @@ U9VQQSQzY1oZMVX8i1m5WUTLPz2yLJIBQVdXqhMCQBGoiuSoSjafUhV7i1cEGpb88h5NBYZzWXGZ $this->assertInternalType('string', $rsa->getPrivateKey()); } + public function testLoadPKCS8PrivateKey() + { + $rsa = new Crypt_RSA(); + $rsa->setPassword('password'); + + $key = '-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIE6TAbBgkqhkiG9w0BBQMwDgQIcWWgZeQYPTcCAggABIIEyLoa5b3ktcPmy4VB +hHkpHzVSEsKJPmQTUaQvUwIp6+hYZeuOk78EPehrYJ/QezwJRdyBoD51oOxqWCE2 +fZ5Wf6Mi/9NIuPyqQccP2ouErcMAcDLaAx9C0Ot37yoG0S6hOZgaxqwnCdGYKHgS +7cYUv40kLOJmTOJlHJbatfXHocrHcHkCBJ1q8wApA1KVQIZsqmyBUBuwbrfFwpC9 +d/R674XxCWJpXvU63VNZRFYUvd7YEWCrdSeleb99p0Vn1kxI5463PXurgs/7GPiO +SLSdX44DESP9l7lXenC4gbuT8P0xQRDzGrB5l9HHoV3KMXFODWTMnLcp1nuhA0OT +fPS2yzT9zJgqHiVKWgcUUJ5uDelVfnsmDhnh428p0GBFbniH07qREC9kq78UqQNI +Kybp4jQ4sPs64zdYm/VyLWtAYz8QNAKHLcnPwmTPr/XlJmox8rlQhuSQTK8E+lDr +TOKpydrijN3lF+pgyUuUj6Ha8TLMcOOwqcrpBig4SGYoB56gjAO0yTE9uCPdBakj +yxi3ksn51ErigGM2pGMNcVdwkpJ/x+DEBBO0auy3t9xqM6LK8pwNcOT1EWO+16zY +79LVSavc49t+XxMc3Xasz/G5xQgD1FBp6pEnsg5JhTTG/ih6Y/DQD8z3prjC3qKc +rpL4NA9KBI/IF1iIXlrfmN/zCKbBuEOEGqwcHBDHPySZbhL2XLSpGcK/NBl1bo1Z +G+2nUTauoC67Qb0+fnzTcvOiMNAbHMiqkirs4anHX33MKL2gR/3dp8ca9hhWWXZz +Mkk2FK9sC/ord9F6mTtvTiOSDzpiEhb94uTxXqBhIbsrGXCUUd0QQN5s2dmW2MfS +M35KeSv2rwDGzC1+Qf3MhHGIZDqoQwuZEzM5yHHafCatAbZd2sjaFWegg0r2ca7a +eZkZFj3ZuDYXJFnL82guOASh7rElWO2Ys7ncXAKnaV3WkkF+JDv/CUHr+Q/h6Ae5 +qEvgubTCVSYHzRP37XJItlcdywTIcTY+t6jymmyEBJ66LmUoD47gt/vDUSbhT6Oa +GlcZ+MZGlUnPOSq4YknOgwKH8izboY4UgVCrmXvlaZYQhZemNDkVbpYVDf+s6cPf +tJwVoZf+qf2SsRTUsI10isoIzCyGw2ie8kmipdP434Z/99uVU3zxD6raNDlyp33q +FWMgpr2JU6NVAla7N51g7Jk8VjIIn7SvCYyWkmvv4kLB1UHl3NFqYb9YuIZUaDyt +j/NMcKMLLOaEorRZ2N2mDNoihMxMf8J3J9APnzUigAtaalGKNOrd2Fom5OVADePv +Tb5sg1uVQzfcpFrjIlLVh+2cekX0JM84phbMpHmm5vCjjfYvUvcMy0clCf0x3jz6 +LZf5Fzc8xbZmpse5OnOrsDLCNh+SlcYOzsagSZq4TgvSeI9Tr4lv48dLJHCCcYKL +eymS9nhlCFuuHbi7zI7edcI49wKUW1Sj+kvKq3LMIEkMlgzqGKA6JqSVxHP51VH5 +FqV4aKq70H6dNJ43bLVRPhtF5Bip5P7k/6KIsGTPUd54PHey+DuWRjitfheL0G2w +GF/qoZyC1mbqdtyyeWgHtVbJVUORmpbNnXOII9duEqBUNDiO9VSZNn/8h/VsYeAB +xryZaRDVmtMuf/OZBQ== +-----END ENCRYPTED PRIVATE KEY-----'; + + $this->assertTrue($rsa->loadKey($key)); + $this->assertInternalType('string', $rsa->getPrivateKey()); + } + + public function testSavePKCS8PrivateKey() + { + $rsa = new Crypt_RSA(); + + $key = '-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0FPqri0cb2JZfXJ/DgYSF6vUp +wmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RKNUSesmQRMSGkVb1/3j+skZ6UtW+5u09lHNsj6tQ5 +1s1SPrCBkedbNf0Tp0GbMJDyR4e9T04ZZwIDAQABAoGAFijko56+qGyN8M0RVyaRAXz++xTqHBLh +3tx4VgMtrQ+WEgCjhoTwo23KMBAuJGSYnRmoBZM3lMfTKevIkAidPExvYCdm5dYq3XToLkkLv5L2 +pIIVOFMDG+KESnAFV7l2c+cnzRMW0+b6f8mR1CJzZuxVLL6Q02fvLi55/mbSYxECQQDeAw6fiIQX +GukBI4eMZZt4nscy2o12KyYner3VpoeE+Np2q+Z3pvAMd/aNzQ/W9WaI+NRfcxUJrmfPwIGm63il +AkEAxCL5HQb2bQr4ByorcMWm/hEP2MZzROV73yF41hPsRC9m66KrheO9HPTJuo3/9s5p+sqGxOlF +L0NDt4SkosjgGwJAFklyR1uZ/wPJjj611cdBcztlPdqoxssQGnh85BzCj/u3WqBpE2vjvyyvyI5k +X6zk7S0ljKtt2jny2+00VsBerQJBAJGC1Mg5Oydo5NwD6BiROrPxGo2bpTbu/fhrT8ebHkTz2epl +U9VQQSQzY1oZMVX8i1m5WUTLPz2yLJIBQVdXqhMCQBGoiuSoSjafUhV7i1cEGpb88h5NBYZzWXGZ +37sJ5QsW+sJyoNde3xH8vdXhzU7eT82D6X/scw9RZz+/6rCJ4p0= +-----END RSA PRIVATE KEY-----'; + $rsa->setPassword('password'); + + $this->assertTrue($rsa->loadKey($key)); + + $key = $rsa->getPrivateKey(CRYPT_RSA_PRIVATE_FORMAT_PKCS8); + $this->assertInternalType('string', $key); + + $this->assertTrue($rsa->loadKey($key)); + } + public function testPubKey1() { $rsa = new Crypt_RSA();