diff --git a/.travis.yml b/.travis.yml index eff9de1d..b27e484c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,8 @@ language: php php: - - 5.5.9 - - 5.5 - - 5.6 - 7.0 - 7.1 - - hhvm env: global: diff --git a/composer.json b/composer.json index 9f728103..872574a2 100644 --- a/composer.json +++ b/composer.json @@ -53,7 +53,7 @@ "require": { "paragonie/constant_time_encoding": "^1|^2", "paragonie/random_compat": "^1.4|^2.0", - "php": ">=5.3.3" + "php": ">=7.0" }, "require-dev": { "phing/phing": "~2.7", diff --git a/phpseclib/Crypt/Common/PKCS.php b/phpseclib/Crypt/Common/PKCS.php new file mode 100644 index 00000000..d3b2de85 --- /dev/null +++ b/phpseclib/Crypt/Common/PKCS.php @@ -0,0 +1,80 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib\Crypt\Common; + +/** + * PKCS1 Formatted Key Handler + * + * @package RSA + * @author Jim Wigginton + * @access public + */ +abstract class PKCS +{ + /** + * Auto-detect the format + */ + const MODE_ANY = 0; + /** + * Require base64-encoded PEM's be supplied + */ + const MODE_PEM = 1; + /** + * Require raw DER's be supplied + */ + const MODE_DER = 2; + /**#@-*/ + + /** + * Is the key a base-64 encoded PEM, DER or should it be auto-detected? + * + * @access private + * @param int + */ + static $format = self::MODE_ANY; + + /** + * Require base64-encoded PEM's be supplied + * + * @access public + */ + static function requirePEM() + { + self::$format = self::MODE_PEM; + } + + /** + * Require raw DER's be supplied + * + * @access public + */ + static function requireDER() + { + self::$format = self::MODE_DER; + } + + /** + * Accept any format and auto detect the format + * + * This is the default setting + * + * @access public + */ + static function requireAny() + { + self::$format = self::MODE_ANY; + } +} \ No newline at end of file diff --git a/phpseclib/Crypt/Common/PKCS1.php b/phpseclib/Crypt/Common/PKCS1.php new file mode 100644 index 00000000..e5b3c756 --- /dev/null +++ b/phpseclib/Crypt/Common/PKCS1.php @@ -0,0 +1,224 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib\Crypt\Common; + +use ParagonIE\ConstantTime\Base64; +use ParagonIE\ConstantTime\Hex; +use phpseclib\Crypt\Common\BlockCipher; +use phpseclib\Crypt\Random; +use phpseclib\Crypt\AES; +use phpseclib\Crypt\Base; +use phpseclib\Crypt\DES; +use phpseclib\Crypt\TripleDES; +use phpseclib\File\ASN1; + +/** + * PKCS1 Formatted Key Handler + * + * @package RSA + * @author Jim Wigginton + * @access public + */ +abstract class PKCS1 extends PKCS +{ + /** + * Default encryption algorithm + * + * @var string + * @access private + */ + static $defaultEncryptionAlgorithm = 'AES-128-CBC'; + + /** + * Sets the default encryption algorithm + * + * @access public + * @param string $algo + */ + static function setEncryptionAlgorithm($algo) + { + self::$defaultEncryptionAlgorithm = $algo; + } + + /** + * Returns the mode constant corresponding to the mode string + * + * @access public + * @param string $mode + * @return int + * @throws \UnexpectedValueException if the block cipher mode is unsupported + */ + static function getEncryptionMode($mode) + { + switch ($mode) { + case 'CBC': + return BlockCipher::MODE_CBC; + case 'ECB': + return BlockCipher::MODE_ECB; + case 'CFB': + return BlockCipher::MODE_CFB; + case 'OFB': + return BlockCipher::MODE_OFB; + case 'CTR': + return BlockCipher::MODE_CTR; + } + throw new \UnexpectedValueException('Unsupported block cipher mode of operation'); + } + + /** + * Returns a cipher object corresponding to a string + * + * @access public + * @param string $algo + * @return string + * @throws \UnexpectedValueException if the encryption algorithm is unsupported + */ + static function getEncryptionObject($algo) + { + $modes = '(CBC|ECB|CFB|OFB|CTR)'; + switch (true) { + case preg_match("#^AES-(128|192|256)-$modes$#", $algo, $matches): + $cipher = new AES(self::getEncryptionMode($matches[2])); + $cipher->setKeyLength($matches[1]); + return $cipher; + case preg_match("#^DES-EDE3-$modes$#", $algo, $matches): + return new TripleDES(self::getEncryptionMode($matches[1])); + case preg_match("#^DES-$modes$#", $algo, $matches): + return new DES(self::getEncryptionMode($matches[1])); + default: + throw new \UnexpectedValueException('Unsupported encryption algorithmn'); + } + } + + /** + * Generate a symmetric key for PKCS#1 keys + * + * @access public + * @param string $password + * @param string $iv + * @param int $length + * @return string + */ + static function generateSymmetricKey($password, $iv, $length) + { + $symkey = ''; + $iv = substr($iv, 0, 8); + while (strlen($symkey) < $length) { + $symkey.= md5($symkey . $password . $iv, true); + } + return substr($symkey, 0, $length); + } + + /** + * Break a public or private key down into its constituent components + * + * @access public + * @param string $key + * @param string $password optional + * @return array + */ + static function load($key, $password) + { + if (!is_string($key)) { + return false; + } + + /* 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 + protect private keys, however, that's not what OpenSSL* does. OpenSSL protects private keys by adding + two new "fields" to the key - DEK-Info and Proc-Type. These fields are discussed here: + + http://tools.ietf.org/html/rfc1421#section-4.6.1.1 + http://tools.ietf.org/html/rfc1421#section-4.6.1.3 + + DES-EDE3-CBC as an algorithm, however, is not discussed anywhere, near as I can tell. + DES-CBC and DES-EDE are discussed in RFC1423, however, DES-EDE3-CBC isn't, nor is its key derivation + function. As is, the definitive authority on this encoding scheme isn't the IETF but rather OpenSSL's + own implementation. ie. the implementation *is* the standard and any bugs that may exist in that + implementation are part of the standard, as well. + + * OpenSSL is the de facto standard. It's utilized by OpenSSH and other projects */ + if (preg_match('#DEK-Info: (.+),(.+)#', $key, $matches)) { + $iv = Hex::decode(trim($matches[2])); + // remove the Proc-Type / DEK-Info sections as they're no longer needed + $key = preg_replace('#^(?:Proc-Type|DEK-Info): .*#m', '', $key); + $ciphertext = ASN1::extractBER($key); + if ($ciphertext === false) { + $ciphertext = $key; + } + $crypto = self::getEncryptionObject($matches[1]); + $crypto->setKey(self::generateSymmetricKey($password, $iv, $crypto->getKeyLength() >> 3)); + $crypto->setIV($iv); + $key = $crypto->decrypt($ciphertext); + } else { + if (self::$format != self::MODE_DER) { + $decoded = ASN1::extractBER($key); + if ($decoded !== false) { + $key = $decoded; + } elseif (self::$format == self::MODE_PEM) { + return false; + } + } + } + + return $key; + } + + /** + * Wrap a private key appropriately + * + * @access public + * @param string $key + * @param string $type + * @param string $password + * @return string + */ + static function wrapPrivateKey($key, $type, $password) + { + if (empty($password) || !is_string($password)) { + return "-----BEGIN $type PRIVATE KEY-----\r\n" . + chunk_split(Base64::encode($key), 64) . + "-----END $type PRIVATE KEY-----"; + } + + $cipher = self::getEncryptionObject(self::$defaultEncryptionAlgorithm); + $iv = Random::string($cipher->getBlockLength() >> 3); + $cipher->setKey(self::generateSymmetricKey($password, $iv, $cipher->getKeyLength() >> 3)); + $cipher->setIV($iv); + $iv = strtoupper(Hex::encode($iv)); + return "-----BEGIN $type PRIVATE KEY-----\r\n" . + "Proc-Type: 4,ENCRYPTED\r\n" . + "DEK-Info: " . self::$defaultEncryptionAlgorithm . ",$iv\r\n" . + "\r\n" . + chunk_split(Base64::encode($cipher->encrypt($key)), 64) . + "-----END $type PRIVATE KEY-----"; + } + + /** + * Wrap a public key appropriately + * + * @access public + * @param string $key + * @param string $type + * @return string + */ + static function wrapPublicKey($key, $type) + { + return "-----BEGIN $type PUBLIC KEY-----\r\n" . + chunk_split(Base64::encode($key), 64) . + "-----END $type PUBLIC KEY-----"; + } +} \ No newline at end of file diff --git a/phpseclib/Crypt/Common/PKCS8.php b/phpseclib/Crypt/Common/PKCS8.php new file mode 100644 index 00000000..08095e0b --- /dev/null +++ b/phpseclib/Crypt/Common/PKCS8.php @@ -0,0 +1,713 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib\Crypt\Common; + +use ParagonIE\ConstantTime\Base64; +use phpseclib\Crypt\DES; +use phpseclib\Crypt\RC2; +use phpseclib\Crypt\RC4; +use phpseclib\Crypt\AES; +use phpseclib\Crypt\TripleDES; +use phpseclib\Crypt\Common\BlockCipher; +use phpseclib\Crypt\Random; +use phpseclib\Math\BigInteger; +use phpseclib\File\ASN1; +use phpseclib\Exception\UnsupportedAlgorithmException; + +// version is the syntax version number, for compatibility with +// future revisions of this document. It shall be 0 for this version +// of the document. +define(__NAMESPACE__ . '\Version', [ + 'type' => ASN1::TYPE_INTEGER, + 'mapping' => ['v1'] +]); + +// we can replace this later once the X509 definitions are rewritten +define(__NAMESPACE__ . '\AlgorithmIdentifier', [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'algorithm' => ['type' => ASN1::TYPE_OBJECT_IDENTIFIER], + 'parameters' => [ + 'type' => ASN1::TYPE_ANY, + 'optional' => true + ] + ] +]); + +define(__NAMESPACE__ . '\PrivateKey', ['type' => ASN1::TYPE_OCTET_STRING]); + +// we can replace this later once the X509 definitions are rewritten +define(__NAMESPACE__ . '\AttributeType', ['type' => ASN1::TYPE_OBJECT_IDENTIFIER]); + +// we can replace this later once the X509 definitions are rewritten +define(__NAMESPACE__ . '\AttributeValue', ['type' => ASN1::TYPE_ANY]); + +// we can replace this later once the X509 definitions are rewritten +define(__NAMESPACE__ . '\Attribute', [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'type' => AttributeType, + 'value'=> [ + 'type' => ASN1::TYPE_SET, + 'min' => 1, + 'max' => -1, + 'children' => AttributeValue + ] + ] +]); + +define(__NAMESPACE__ . '\Attributes', [ + 'type' => ASN1::TYPE_SET, + 'children' => Attribute +]); + +define(__NAMESPACE__ . '\PrivateKeyInfo', [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'version' => Version, + 'privateKeyAlgorithm'=> AlgorithmIdentifier, + 'privateKey' => PrivateKey, + 'attributes' => [ + 'constant' => 0, + 'optional' => true, + 'implicit' => true + ] + Attributes + ] +]); + +define(__NAMESPACE__ . '\EncryptedData', ['type' => ASN1::TYPE_OCTET_STRING]); + +define(__NAMESPACE__ . '\EncryptedPrivateKeyInfo', [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'encryptionAlgorithm' => AlgorithmIdentifier, + 'encryptedData' => EncryptedData + ] +]); + +// this format is not formally defined anywhere but is none-the-less the form you +// get when you do "openssl rsa -in private.pem -outform PEM -pubout" +define(__NAMESPACE__ . '\PublicKeyInfo', [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'publicKeyAlgorithm'=> AlgorithmIdentifier, + 'publicKey' => ['type' => ASN1::TYPE_BIT_STRING] + ] +]); + +// from https://tools.ietf.org/html/rfc2898#appendix-A.3 +define(__NAMESPACE__ . '\PBEParameter', [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'salt' => ['type' => ASN1::TYPE_OCTET_STRING], + 'iterationCount' => ['type' => ASN1::TYPE_INTEGER] + ] +]); + +define(__NAMESPACE__ . '\PBES2params', [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'keyDerivationFunc'=> AlgorithmIdentifier, + 'encryptionScheme' => AlgorithmIdentifier + ] +]); + +define(__NAMESPACE__ . '\PBMAC1params', [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'keyDerivationFunc'=> AlgorithmIdentifier, + 'messageAuthScheme'=> AlgorithmIdentifier + ] +]); + +define(__NAMESPACE__ . '\RC2CBCParameter', [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'rc2ParametersVersion'=> [ + 'type' => ASN1::TYPE_INTEGER, + 'optional' => true + ], + 'iv'=> ['type' => ASN1::TYPE_OCTET_STRING] + ] +]); + +define(__NAMESPACE__ . '\PBKDF2params', [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + // technically, this is a CHOICE in RFC2898 but the other "choice" is, currently, more of a placeholder + // in the RFC + 'salt'=> ['type' => ASN1::TYPE_OCTET_STRING], + 'iterationCount'=> ['type' => ASN1::TYPE_INTEGER], + 'keyLength' => [ + 'type' => ASN1::TYPE_INTEGER, + 'optional' => true + ], + 'prf' => AlgorithmIdentifier + ['optional' => true] + ] +]); + +// from https://tools.ietf.org/html/rfc2898 +define(__NAMESPACE__ . '\oids', [ + // PBES1 encryption schemes + '1.2.840.113549.1.5.1' => 'pbeWithMD2AndDES-CBC', + '1.2.840.113549.1.5.4' => 'pbeWithMD2AndRC2-CBC', + '1.2.840.113549.1.5.3' => 'pbeWithMD5AndDES-CBC', + '1.2.840.113549.1.5.6' => 'pbeWithMD5AndRC2-CBC', + '1.2.840.113549.1.5.10'=> 'pbeWithSHA1AndDES-CBC', + '1.2.840.113549.1.5.11'=> 'pbeWithSHA1AndRC2-CBC', + + // from PKCS#12: + // https://tools.ietf.org/html/rfc7292 + '1.2.840.113549.1.12.1.1' => 'pbeWithSHAAnd128BitRC4', + '1.2.840.113549.1.12.1.2' => 'pbeWithSHAAnd40BitRC4', + '1.2.840.113549.1.12.1.3' => 'pbeWithSHAAnd3-KeyTripleDES-CBC', + '1.2.840.113549.1.12.1.4' => 'pbeWithSHAAnd2-KeyTripleDES-CBC', + '1.2.840.113549.1.12.1.5' => 'pbeWithSHAAnd128BitRC2-CBC', + '1.2.840.113549.1.12.1.6' => 'pbeWithSHAAnd40BitRC2-CBC', + + '1.2.840.113549.1.5.12' => 'id-PBKDF2', + '1.2.840.113549.1.5.13' => 'id-PBES2', + '1.2.840.113549.1.5.14' => 'id-PBMAC1', + + // from PKCS#5 v2.1: + // http://www.rsa.com/rsalabs/pkcs/files/h11302-wp-pkcs5v2-1-password-based-cryptography-standard.pdf + '1.2.840.113549.2.7' => 'id-hmacWithSHA1', + '1.2.840.113549.2.8' => 'id-hmacWithSHA224', + '1.2.840.113549.2.9' => 'id-hmacWithSHA256', + '1.2.840.113549.2.10'=> 'id-hmacWithSHA384', + '1.2.840.113549.2.11'=> 'id-hmacWithSHA512', + '1.2.840.113549.2.12'=> 'id-hmacWithSHA512-224', + '1.2.840.113549.2.13'=> 'id-hmacWithSHA512-256', + + '1.3.14.3.2.7' => 'desCBC', + '1.2.840.113549.3.7' => 'des-EDE3-CBC', + '1.2.840.113549.3.2' => 'rc2CBC', + '1.2.840.113549.3.9' => 'rc5-CBC-PAD', + + '2.16.840.1.101.3.4.1.2' => 'aes128-CBC-PAD', + '2.16.840.1.101.3.4.1.22'=> 'aes192-CBC-PAD', + '2.16.840.1.101.3.4.1.42'=> 'aes256-CBC-PAD' +]); + +/** + * PKCS#8 Formatted Key Handler + * + * @package Common + * @author Jim Wigginton + * @access public + */ +class PKCS8 extends PKCS +{ + /** + * Default encryption algorithm + * + * @var string + * @access private + */ + static $defaultEncryptionAlgorithm = 'id-PBES2'; + + /** + * Default encryption scheme + * + * Only used when defaultEncryptionAlgorithm is id-PBES2 + * + * @var string + * @access private + */ + static $defaultEncryptionScheme = 'aes128-CBC-PAD'; + + /** + * Default PRF + * + * Only used when defaultEncryptionAlgorithm is id-PBES2 + * + * @var string + * @access private + */ + static $defaultPRF = 'id-hmacWithSHA256'; + + /** + * Default Iteration Count + * + * @var int + * @access private + */ + static $defaultIterationCount = 2048; + + /** + * Sets the default encryption algorithm + * + * @access public + * @param string $algo + */ + static function setEncryptionAlgorithm($algo) + { + self::$defaultEncryptionAlgorithm = $algo; + } + + /** + * Sets the default encryption algorithm for PBES2 + * + * @access public + * @param string $algo + */ + static function setEncryptionScheme($algo) + { + self::$defaultEncryptionScheme = $algo; + } + + /** + * Sets the iteration count + * + * @access public + * @param int $count + */ + static function setIterationCount($count) + { + self::$defaultIterationCount = $count; + } + + /** + * Sets the PRF for PBES2 + * + * @access public + * @param string $algo + */ + static function setPRF($algo) + { + self::$defaultPRF = $algo; + } + + /** + * Returns a SymmetricKey object based on a PBES1 $algo + * + * @access public + * @param string $algo + */ + static function getPBES1EncryptionObject($algo) + { + $algo = preg_match('#^pbeWith(?:MD2|MD5|SHA1|SHA)And(.*?)-CBC$#', $algo, $matches) ? + $matches[1] : + substr($algo, 13); // strlen('pbeWithSHAAnd') == 13 + + switch ($algo) { + case 'DES': + $cipher = new DES(BlockCipher::MODE_CBC); + break; + case 'RC2': + $cipher = new RC2(BlockCipher::MODE_CBC); + break; + case '3-KeyTripleDES': + $cipher = new TripleDES(BlockCipher::MODE_CBC); + break; + case '2-KeyTripleDES': + $cipher = new TripleDES(BlockCipher::MODE_CBC); + $cipher->setKeyLength(128); + break; + case '128BitRC2': + $cipher = new RC2(BlockCipher::MODE_CBC); + $cipher->setKeyLength(128); + break; + case '40BitRC2': + $cipher = new RC2(BlockCipher::MODE_CBC); + $cipher->setKeyLength(40); + break; + case '128BitRC4': + $cipher = new RC4(); + $cipher->setKeyLength(128); + break; + case '40BitRC4': + $cipher = new RC4(); + $cipher->setKeyLength(40); + break; + default: + throw new UnsupportedAlgorithmException("$algo is not a supported algorithm"); + } + + return $cipher; + } + + /** + * Returns a hash based on a PBES1 $algo + * + * @access public + * @param string $algo + */ + static function getPBES1Hash($algo) + { + if (preg_match('#^pbeWith(MD2|MD5|SHA1|SHA)And.*?-CBC$#', $algo, $matches)) { + return $matches[1] == 'SHA' ? 'sha1' : $matches[1]; + } + + return 'sha1'; + } + + /** + * Returns a KDF baesd on a PBES1 $algo + * + * @access public + * @param string $algo + */ + static function getPBES1KDF($algo) + { + switch ($algo) { + case 'pbeWithMD2AndDES-CBC': + case 'pbeWithMD2AndRC2-CBC': + case 'pbeWithMD5AndDES-CBC': + case 'pbeWithMD5AndRC2-CBC': + case 'pbeWithSHA1AndDES-CBC': + case 'pbeWithSHA1AndRC2-CBC': + return 'pbkdf1'; + } + + return 'pkcs12'; + } + + /** + * Returns a SymmetricKey object baesd on a PBES2 $algo + * + * @access public + * @param string $algo + */ + static function getPBES2EncryptionObject($algo) + { + switch ($algo) { + case 'desCBC': + $cipher = new TripleDES(BlockCipher::MODE_CBC); + break; + case 'des-EDE3-CBC': + $cipher = new TripleDES(BlockCipher::MODE_CBC); + break; + case 'rc2CBC': + $cipher = new RC2(BlockCipher::MODE_CBC); + // in theory this can be changed + $cipher->setKeyLength(128); + break; + case 'rc5-CBC-PAD': + throw new UnsupportedAlgorithmException('rc5-CBC-PAD is not supported for PBES2 PKCS#8 keys'); + case 'aes128-CBC-PAD': + case 'aes192-CBC-PAD': + case 'aes256-CBC-PAD': + $cipher = new AES(BlockCipher::MODE_CBC); + $cipher->setKeyLength(substr($algo, 3, 3)); + break; + default: + throw new UnsupportedAlgorithmException("$algo is not supported"); + } + + return $cipher; + } + + /** + * Break a public or private key down into its constituent components + * + * @access public + * @param string $key + * @param string $password optional + * @return array + */ + static function load($key, $password = '') + { + if (!is_string($key)) { + return false; + } + + if (self::$format != self::MODE_DER) { + $decoded = ASN1::extractBER($key); + if ($decoded !== false) { + $key = $decoded; + } elseif (self::$format == self::MODE_PEM) { + return false; + } + } + + $asn1 = new ASN1(); + $decoded = $asn1->decodeBER($key); + if (empty($decoded)) { + return false; + } + + $meta = []; + + $asn1->loadOIDs(oids); + $decrypted = $asn1->asn1map($decoded[0], EncryptedPrivateKeyInfo); + if (strlen($password) && is_array($decrypted)) { + $algorithm = $decrypted['encryptionAlgorithm']['algorithm']; + switch ($algorithm) { + // PBES1 + case 'pbeWithMD2AndDES-CBC': + case 'pbeWithMD2AndRC2-CBC': + case 'pbeWithMD5AndDES-CBC': + case 'pbeWithMD5AndRC2-CBC': + case 'pbeWithSHA1AndDES-CBC': + case 'pbeWithSHA1AndRC2-CBC': + case 'pbeWithSHAAnd3-KeyTripleDES-CBC': + case 'pbeWithSHAAnd2-KeyTripleDES-CBC': + case 'pbeWithSHAAnd128BitRC2-CBC': + case 'pbeWithSHAAnd40BitRC2-CBC': + case 'pbeWithSHAAnd128BitRC4': + case 'pbeWithSHAAnd40BitRC4': + $cipher = self::getPBES1EncryptionObject($algorithm); + $hash = self::getPBES1Hash($algorithm); + $kdf = self::getPBES1KDF($algorithm); + + $meta['meta']['algorithm'] = $algorithm; + + $temp = $asn1->decodeBER($decrypted['encryptionAlgorithm']['parameters']); + extract($asn1->asn1map($temp[0], PBEParameter)); + $iterationCount = (int) $iterationCount->toString(); + $cipher->setPassword($password, $kdf, $hash, Base64::decode($salt), $iterationCount); + $key = $cipher->decrypt(Base64::decode($decrypted['encryptedData'])); + $decoded = $asn1->decodeBER($key); + if (empty($decoded)) { + return false; + } + + break; + case 'id-PBES2': + $meta['meta']['algorithm'] = $algorithm; + + $temp = $asn1->decodeBER($decrypted['encryptionAlgorithm']['parameters']); + $temp = $asn1->asn1map($temp[0], PBES2params); + extract($temp); + + $cipher = self::getPBES2EncryptionObject($encryptionScheme['algorithm']); + $meta['meta']['cipher'] = $encryptionScheme['algorithm']; + + $temp = $asn1->decodeBER($decrypted['encryptionAlgorithm']['parameters']); + $temp = $asn1->asn1map($temp[0], PBES2params); + extract($temp); + + if (!$cipher instanceof RC2) { + $cipher->setIV(Base64::decode($encryptionScheme['parameters']['octetString'])); + } else { + $temp = $asn1->decodeBER($encryptionScheme['parameters']); + extract($asn1->asn1map($temp[0], RC2CBCParameter)); + $effectiveKeyLength = (int) $rc2ParametersVersion->toString(); + switch ($effectiveKeyLength) { + case 160: + $effectiveKeyLength = 40; + break; + case 120: + $effectiveKeyLength = 64; + break; + case 58: + $effectiveKeyLength = 128; + break; + //default: // should be >= 256 + } + $cipher->setIV(Base64::decode($iv)); + $cipher->setKeyLength($effectiveKeyLength); + } + + $meta['meta']['keyDerivationFunc'] = $keyDerivationFunc['algorithm']; + switch ($keyDerivationFunc['algorithm']) { + case 'id-PBKDF2': + $temp = $asn1->decodeBER($keyDerivationFunc['parameters']); + $prf = ['algorithm' => 'id-hmacWithSHA1']; + $params = $asn1->asn1map($temp[0], PBKDF2params); + extract($params); + $meta['meta']['prf'] = $prf['algorithm']; + $hash = str_replace('-', '/', substr($prf['algorithm'], 11)); + $params = [ + $password, + 'pbkdf2', + $hash, + Base64::decode($salt), + (int) $iterationCount->toString() + ]; + if (isset($keyLength)) { + $params[] = (int) $keyLength->toString(); + } + call_user_func_array([$cipher, 'setPassword'], $params); + $key = $cipher->decrypt(Base64::decode($decrypted['encryptedData'])); + $decoded = $asn1->decodeBER($key); + if (empty($decoded)) { + return false; + } + break; + default: + throw new UnsupportedAlgorithmException('Only PBKDF2 is supported for PBES2 PKCS#8 keys'); + } + break; + case 'id-PBMAC1': + //$temp = $asn1->decodeBER($decrypted['encryptionAlgorithm']['parameters']); + //$value = $asn1->asn1map($temp[0], PBMAC1params); + // since i can't find any implementation that does PBMAC1 it is unsupported + throw new UnsupportedAlgorithmException('Only PBES1 and PBES2 PKCS#8 keys are supported.'); + // at this point we'll assume that the key conforms to PublicKeyInfo + } + } + + $private = $asn1->asn1map($decoded[0], PrivateKeyInfo); + if (is_array($private)) { + return $private + $meta; + } + + // EncryptedPrivateKeyInfo and PublicKeyInfo have largely identical "signatures". the only difference + // is that the former has an octet string and the later has a bit string. the first byte of a bit + // string represents the number of bits in the last byte that are to be ignored but, currently, + // bit strings wanting a non-zero amount of bits trimmed are not supported + $public = $asn1->asn1map($decoded[0], PublicKeyInfo); + if (is_array($public)) { + $public['publicKey'] = base64_decode($public['publicKey']); + if ($public['publicKey'][0] != "\0") { + return false; + } + $public['publicKey'] = base64_encode(substr($public['publicKey'], 1)); + return $public; + } + + return false; + } + + /** + * Wrap a private key appropriately + * + * @access public + * @param string $algorithm + * @param string $key + * @param string $attr + * @param string $password + * @return string + */ + static function wrapPrivateKey($key, $algorithm, $attr, $password) + { + $asn1 = new ASN1(); + $asn1->loadOIDs(oids); + + $key = [ + 'version' => 'v1', + 'privateKeyAlgorithm' => ['algorithm' => $algorithm], // parameters are not currently supported + 'privateKey' => Base64::encode($key) + ]; + if (!empty($attr)) { + $key['attributes'] = $attr; + } + $key = $asn1->encodeDER($key, PrivateKeyInfo); + if (!empty($password) && is_string($password)) { + $salt = Random::string(8); + $iterationCount = self::$defaultIterationCount; + + if (self::$defaultEncryptionAlgorithm == 'id-PBES2') { + $crypto = self::getPBES2EncryptionObject(self::$defaultEncryptionScheme); + $hash = str_replace('-', '/', substr(self::$defaultPRF, 11)); + $kdf = 'pbkdf2'; + $iv = Random::string($crypto->getBlockLength() >> 3); + + $PBKDF2params = [ + 'salt' => Base64::encode($salt), + 'iterationCount' => $iterationCount, + 'prf' => ['algorithm' => self::$defaultPRF, 'parameters' => null] + ]; + $PBKDF2params = $asn1->encodeDER($PBKDF2params, PBKDF2params); + + if (!$crypto instanceof RC2) { + $params = ['octetString' => Base64::encode($iv)]; + } else { + $params = [ + 'rc2ParametersVersion' => 58, + 'iv' => Base64::encode($iv) + ]; + $params = $asn1->encodeDER($params, RC2CBCParameter); + $params = new ASN1\Element($params); + } + + $params = [ + 'keyDerivationFunc' => [ + 'algorithm' => 'id-PBKDF2', + 'parameters' => new ASN1\Element($PBKDF2params) + ], + 'encryptionScheme' => [ + 'algorithm' => self::$defaultEncryptionScheme, + 'parameters' => $params + ] + ]; + $params = $asn1->encodeDER($params, PBES2params); + + $crypto->setIV($iv); + } else { + $crypto = self::getPBES1EncryptionObject(self::$defaultEncryptionAlgorithm); + $hash = self::getPBES1Hash(self::$defaultEncryptionAlgorithm); + $kdf = self::getPBES1KDF(self::$defaultEncryptionAlgorithm); + + $params = [ + 'salt' => Base64::encode($salt), + 'iterationCount' => $iterationCount + ]; + $params = $asn1->encodeDER($params, PBEParameter); + } + $crypto->setPassword($password, $kdf, $hash, $salt, $iterationCount); + $key = $crypto->encrypt($key); + + $key = [ + 'encryptionAlgorithm' => [ + 'algorithm' => self::$defaultEncryptionAlgorithm, + 'parameters' => new ASN1\Element($params) + ], + 'encryptedData' => Base64::encode($key) + ]; + + $key = $asn1->encodeDER($key, EncryptedPrivateKeyInfo); + + return "-----BEGIN ENCRYPTED PRIVATE KEY-----\r\n" . + chunk_split(Base64::encode($key), 64) . + "-----END ENCRYPTED PRIVATE KEY-----"; + } + + return "-----BEGIN PRIVATE KEY-----\r\n" . + chunk_split(Base64::encode($key), 64) . + "-----END PRIVATE KEY-----"; + } + + /** + * Wrap a public key appropriately + * + * @access public + * @param string $key + * @return string + */ + static function wrapPublicKey($key, $algorithm) + { + $asn1 = new ASN1(); + + $key = [ + 'publicKeyAlgorithm' => [ + 'algorithm' => $algorithm, + 'parameters' => null // parameters are not currently supported + ], + 'publicKey' => Base64::encode("\0" . $key) + ]; + + $key = $asn1->encodeDER($key, PublicKeyInfo); + + return "-----BEGIN PUBLIC KEY-----\r\n" . + chunk_split(Base64::encode($key), 64) . + "-----END PUBLIC KEY-----"; + } +} diff --git a/phpseclib/Crypt/Common/SymmetricKey.php b/phpseclib/Crypt/Common/SymmetricKey.php index b1dfca64..e2c775bc 100644 --- a/phpseclib/Crypt/Common/SymmetricKey.php +++ b/phpseclib/Crypt/Common/SymmetricKey.php @@ -38,6 +38,7 @@ namespace phpseclib\Crypt\Common; use phpseclib\Crypt\Hash; use phpseclib\Common\Functions\Strings; +use phpseclib\Math\BigInteger; /** * Base Class for all \phpseclib\Crypt\* cipher classes @@ -512,7 +513,7 @@ abstract class SymmetricKey throw new \InvalidArgumentException('This mode does not require an IV.'); } - if ($this->mode == self::MODE_STREAM && $this->usesIV()) { + if (!$this->usesIV()) { throw new \InvalidArgumentException('This algorithm does not use an IV.'); } @@ -622,12 +623,17 @@ abstract class SymmetricKey { $key = ''; + $method = strtolower($method); switch ($method) { - default: // 'pbkdf2' or 'pbkdf1' + case 'pkcs12': // from https://tools.ietf.org/html/rfc7292#appendix-B.2 + case 'pbkdf1': + case 'pbkdf2': $func_args = func_get_args(); // Hash function - $hash = isset($func_args[2]) ? $func_args[2] : 'sha1'; + $hash = isset($func_args[2]) ? strtolower($func_args[2]) : 'sha1'; + $hashObj = new Hash(); + $hashObj->setHash($hash); // WPA and WPA2 use the SSID as the salt $salt = isset($func_args[3]) ? $func_args[3] : $this->password_default_salt; @@ -645,10 +651,63 @@ abstract class SymmetricKey } switch (true) { + case $method == 'pkcs12': + /* + In this specification, however, all passwords are created from + BMPStrings with a NULL terminator. This means that each character in + the original BMPString is encoded in 2 bytes in big-endian format + (most-significant byte first). There are no Unicode byte order + marks. The 2 bytes produced from the last character in the BMPString + are followed by 2 additional bytes with the value 0x00. + + -- https://tools.ietf.org/html/rfc7292#appendix-B.1 + */ + $password = "\0". chunk_split($password, 1, "\0") . "\0"; + + /* + This standard specifies 3 different values for the ID byte mentioned + above: + + 1. If ID=1, then the pseudorandom bits being produced are to be used + as key material for performing encryption or decryption. + + 2. If ID=2, then the pseudorandom bits being produced are to be used + as an IV (Initial Value) for encryption or decryption. + + 3. If ID=3, then the pseudorandom bits being produced are to be used + as an integrity key for MACing. + */ + // Construct a string, D (the "diversifier"), by concatenating v/8 + // copies of ID. + $blockLength = $hashObj->getBlockLengthInBytes(); + $d1 = str_repeat(chr(1), $blockLength); + $d2 = str_repeat(chr(2), $blockLength); + $s = ''; + if (strlen($salt)) { + while (strlen($s) < $blockLength) { + $s.= $salt; + } + } + $s = substr($s, 0, $blockLength); + + $p = ''; + if (strlen($password)) { + while (strlen($p) < $blockLength) { + $p.= $password; + } + } + $p = substr($p, 0, $blockLength); + + $i = $s . $p; + + $this->setKey(self::_pkcs12helper($dkLen, $hashObj, $i, $d1, $count)); + if ($this->usesIV()) { + $this->setIV(self::_pkcs12helper($this->block_size, $hashObj, $i, $d2, $count)); + } + + return true; case $method == 'pbkdf1': - $hashObj = new Hash(); - $hashObj->setHash($hash); - if ($dkLen > $hashObj->getLength()) { + if ($dkLen > $hashObj->getLengthInBytes()) { throw new \LengthException('Derived key length cannot be longer than the hash length'); } $t = $password . $salt; @@ -658,21 +717,20 @@ abstract class SymmetricKey $key = substr($t, 0, $dkLen); $this->setKey(substr($key, 0, $dkLen >> 1)); - $this->setIV(substr($key, $dkLen >> 1)); + if ($this->usesIV()) { + $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()): $i = 1; + $hashObj->setKey($password); while (strlen($key) < $dkLen) { - $hmac = new Hash(); - $hmac->setHash($hash); - $hmac->setKey($password); - $f = $u = $hmac->hash($salt . pack('N', $i++)); + $f = $u = $hashObj->hash($salt . pack('N', $i++)); for ($j = 2; $j <= $count; ++$j) { - $u = $hmac->hash($u); + $u = $hashObj->hash($u); $f^= $u; } $key.= $f; @@ -682,6 +740,9 @@ abstract class SymmetricKey default: $key = hash_pbkdf2($hash, $password, $salt, $count, $dkLen, true); } + break; + default: + throw new \InvalidArgumentException($method . ' is not a supported password hashing method'); } $this->setKey($key); @@ -689,6 +750,60 @@ abstract class SymmetricKey return true; } + /** + * PKCS#12 KDF Helper Function + * + * As discussed here: + * + * {@link https://tools.ietf.org/html/rfc7292#appendix-B} + * + * @see self::setPassword() + * @access private + * @param int $n + * @param \phpseclib\Crypt\Hash $hashObj + * @param string $i + * @param string $d + * @param int $count + * @return string $a + */ + static function _pkcs12helper($n, $hashObj, $i, $d, $count) + { + static $one; + if (!isset($one)) { + $one = new BigInteger(1); + } + + $blockLength = $hashObj->getBlockLength() >> 3; + + $c = ceil($n / $hashObj->getLengthInBytes()); + $a = ''; + for ($j = 1; $j <= $c; $j++) { + $ai = $d . $i; + for ($k = 0; $k < $count; $k++) { + $ai = $hashObj->hash($ai); + } + $b = ''; + while (strlen($b) < $blockLength) { + $b.= $ai; + } + $b = substr($b, 0, $blockLength); + $b = new BigInteger($b, 256); + $newi = ''; + for ($k = 0; $k < strlen($i); $k+= $blockLength) { + $temp = substr($i, $k, $blockLength); + $temp = new BigInteger($temp, 256); + $temp->setPrecision($blockLength << 3); + $temp = $temp->add($b); + $temp = $temp->add($one); + $newi.= $temp->toBytes(false); + } + $i = $newi; + $a.= $ai; + } + + return substr($a, 0, $n); + } + /** * Encrypts a message. * diff --git a/phpseclib/Crypt/Hash.php b/phpseclib/Crypt/Hash.php index 0476ca8b..1ae4f231 100644 --- a/phpseclib/Crypt/Hash.php +++ b/phpseclib/Crypt/Hash.php @@ -203,6 +203,23 @@ class Hash ); } + switch ($hash) { + case 'md2-96': + case 'md5-96': + case 'sha1-96': + case 'sha224-96': + case 'sha256-96': + case 'md2': + case 'md5': + case 'sha1': + case 'sha224': + case 'sha256': + $this->blockSize = 512; + break; + default: + $this->blockSize = 1024; + } + if ($hash == 'sha512/224' || $hash == 'sha512/256') { // from http://csrc.nist.gov/publications/fips/fips180-4/fips-180-4.pdf#page=24 $this->initial = $hash == 'sha512/256' ? @@ -264,16 +281,49 @@ class Hash } /** - * Returns the hash length (in bytes) + * Returns the hash length (in bits) * * @access public * @return int */ function getLength() + { + return $this->length << 3; + } + + /** + * Returns the hash length (in bytes) + * + * @access public + * @return int + */ + function getLengthInBytes() { return $this->length; } + /** + * Returns the block length (in bits) + * + * @access public + * @return int + */ + function getBlockLength() + { + return $this->blockSize; + } + + /** + * Returns the block length (in bytes) + * + * @access public + * @return int + */ + function getBlockLengthInBytes() + { + return $this->blockSize >> 3; + } + /** * Pure-PHP implementation of SHA512 * diff --git a/phpseclib/Crypt/RC2.php b/phpseclib/Crypt/RC2.php index 5ccdf908..aaa63f06 100644 --- a/phpseclib/Crypt/RC2.php +++ b/phpseclib/Crypt/RC2.php @@ -319,6 +319,7 @@ class RC2 extends BlockCipher } $this->default_key_length = $this->current_key_length = $length; + $this->explicit_key_length = $length >> 3; } /** @@ -340,9 +341,6 @@ class RC2 extends BlockCipher * has more then 128 bytes in it, and set $key to a single null byte if * it is empty. * - * If the key is not explicitly set, it'll be assumed to be a single - * null byte. - * * @see \phpseclib\Crypt\Common\SymmetricKey::setKey() * @access public * @param string $key @@ -362,8 +360,10 @@ class RC2 extends BlockCipher } $this->current_key_length = $t1; - // Key byte count should be 1..128. - $key = strlen($key) ? substr($key, 0, 128) : "\x00"; + if (strlen($key) < 1 || strlen($key) > 128) { + throw new \LengthException('Key of size ' . strlen($key) . ' not supported by this algorithm. Only keys of sizes between 8 and 1024 bits, inclusive, are supported'); + } + $t = strlen($key); // The mcrypt RC2 implementation only supports effective key length @@ -392,7 +392,10 @@ class RC2 extends BlockCipher $l[0] = $this->invpitable[$l[0]]; array_unshift($l, 'C*'); - parent::setKey(call_user_func_array('pack', $l)); + $this->key = call_user_func_array('pack', $l); + $this->key_length = strlen($this->key); + $this->changed = true; + $this->_setEngine(); } /** diff --git a/phpseclib/Crypt/RC4.php b/phpseclib/Crypt/RC4.php index 8673d3db..e7ad0bae 100644 --- a/phpseclib/Crypt/RC4.php +++ b/phpseclib/Crypt/RC4.php @@ -109,7 +109,7 @@ class RC4 extends StreamCipher * @var string * @access private */ - var $key = "\0"; + var $key; /** * The Key Stream for decryption and encryption diff --git a/phpseclib/Crypt/RSA.php b/phpseclib/Crypt/RSA.php index 062bc981..fcafc7e6 100644 --- a/phpseclib/Crypt/RSA.php +++ b/phpseclib/Crypt/RSA.php @@ -118,32 +118,6 @@ class RSA const PADDING_RELAXED_PKCS1 = 5; /**#@-*/ - /**#@+ - * @access private - * @see self::createKey() - */ - /** - * ASN1 Integer - */ - const ASN1_INTEGER = 2; - /** - * ASN1 Bit String - */ - const ASN1_BITSTRING = 3; - /** - * ASN1 Octet String - */ - const ASN1_OCTETSTRING = 4; - /** - * ASN1 Object Identifier - */ - const ASN1_OBJECT = 6; - /** - * ASN1 Sequence (with the constucted bit set) - */ - const ASN1_SEQUENCE = 48; - /**#@-*/ - /**#@+ * @access private * @see self::__construct() @@ -151,13 +125,13 @@ class RSA /** * To use the pure-PHP implementation */ - const MODE_INTERNAL = 1; + const ENGINE_INTERNAL = 1; /** * To use the OpenSSL library * * (if enabled; otherwise, the internal implementation will be used) */ - const MODE_OPENSSL = 2; + const ENGINE_OPENSSL = 2; /**#@-*/ /** @@ -182,7 +156,7 @@ class RSA * @var string * @access private */ - var $privateKeyFormat = 'PKCS1'; + var $privateKeyFormat = 'PKCS8'; /** * Public Key Format @@ -341,6 +315,39 @@ class RSA */ static $origFileFormats = false; + /** + * Public exponent + * + * @var int + * @link http://en.wikipedia.org/wiki/65537_%28number%29 + * @access private + */ + static $defaultExponent = 65537; + + /** + * Smallest Prime + * + * Per , 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 + */ + static $smallestPrime = 4096; + + /** + * Engine + * + * This is only used for key generation. Valid values are RSA::ENGINE_INTERNAL and RSA::ENGINE_OPENSSL + * + * @var int + * @access private + */ + static $engine = NULL; /** * Initialize static variables @@ -384,10 +391,49 @@ class RSA self::_initialize_static_variables(); $this->hash = new Hash('sha256'); - $this->hLen = $this->hash->getLength(); + $this->hLen = $this->hash->getLengthInBytes(); $this->hashName = 'sha256'; $this->mgfHash = new Hash('sha256'); - $this->mgfHLen = $this->mgfHash->getLength(); + $this->mgfHLen = $this->mgfHash->getLengthInBytes(); + } + + /** + * Sets the public exponent + * + * This will be 65537 unless changed. + * + * @access public + * @param int $val + */ + static function setExponent($val) + { + self::$defaultExponent = $val; + } + + /** + * Sets the smallest prime number in bits + * + * This will be 4096 unless changed. + * + * @access public + * @param int $val + */ + static function setSmallestPrime($val) + { + self::$smallestPrime = $val; + } + + /** + * Sets the engine + * + * Only used in the constructor. Valid values are RSA::ENGINE_OPENSSL and RSA::ENGINE_INTERNAL + * + * @access public + * @param int $val + */ + static function setEngine($val) + { + self::$engine = $val; } /** @@ -404,42 +450,29 @@ class RSA * @param int $timeout * @param array $p */ - static function createKey($bits = 2048, $timeout = false, $partial = array()) + static function createKey($bits = 2048) { self::_initialize_static_variables(); - if (!defined('CRYPT_RSA_MODE')) { + if (!isset(self::$engine)) { switch (true) { // Math/BigInteger's openssl requirements are a little less stringent than Crypt/RSA's. in particular, // Math/BigInteger doesn't require an openssl.cfg file whereas Crypt/RSA does. so if Math/BigInteger // can't use OpenSSL it can be pretty trivially assumed, then, that Crypt/RSA can't either. case defined('MATH_BIGINTEGER_OPENSSL_DISABLE'): - define('CRYPT_RSA_MODE', self::MODE_INTERNAL); + self::$engine = self::ENGINE_INTERNAL; break; case extension_loaded('openssl') && file_exists(self::$configFile): - define('CRYPT_RSA_MODE', self::MODE_OPENSSL); + self::$engine = self::ENGINE_OPENSSL; break; default: - define('CRYPT_RSA_MODE', self::MODE_INTERNAL); + self::$engine = self::ENGINE_INTERNAL; } } - if (!defined('CRYPT_RSA_EXPONENT')) { - // http://en.wikipedia.org/wiki/65537_%28number%29 - define('CRYPT_RSA_EXPONENT', '65537'); - } - // per , 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 CRYPT_RSA_SMALLEST_PRIME - // to 384 bits then you're going to get a 384 bit prime and a 640 bit prime (384 + 1024 % 384). at least if - // CRYPT_RSA_MODE is set to self::MODE_INTERNAL. if CRYPT_RSA_MODE is set to self::MODE_OPENSSL then - // CRYPT_RSA_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) - if (!defined('CRYPT_RSA_SMALLEST_PRIME')) { - define('CRYPT_RSA_SMALLEST_PRIME', 4096); - } - // OpenSSL uses 65537 as the exponent and requires RSA keys be 384 bits minimum - if (CRYPT_RSA_MODE == self::MODE_OPENSSL && $bits >= 384 && CRYPT_RSA_EXPONENT == 65537) { + + if (self::$engine == self::ENGINE_OPENSSL && $bits >= 384 && self::$defaultExponent == 65537) { $config = array(); if (isset(self::$configFile)) { $config['config'] = self::$configFile; @@ -466,75 +499,34 @@ class RSA static $e; if (!isset($e)) { - $e = new BigInteger(CRYPT_RSA_EXPONENT); + $e = new BigInteger(self::$defaultExponent); } - extract(self::_generateMinMax($bits)); - $absoluteMin = $min; - $temp = $bits >> 1; // divide by two to see how many bits P and Q would be - if ($temp > CRYPT_RSA_SMALLEST_PRIME) { - $num_primes = floor($bits / CRYPT_RSA_SMALLEST_PRIME); - $temp = CRYPT_RSA_SMALLEST_PRIME; + $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; } - $regSize = $temp; - $finalSize = $temp + $bits % $temp; $n = clone self::$one; - if (!empty($partial)) { - extract(unserialize($partial)); - } else { - $exponents = $coefficients = $primes = array(); - $lcm = array( - 'top' => clone self::$one, - 'bottom' => false - ); - } - - $start = time(); - $i0 = count($primes) + 1; + $exponents = $coefficients = $primes = array(); + $lcm = array( + 'top' => clone self::$one, + 'bottom' => false + ); do { - for ($i = $i0; $i <= $num_primes; $i++) { - if ($timeout !== false) { - $timeout-= time() - $start; - $start = time(); - if ($timeout <= 0) { - return array( - 'privatekey' => '', - 'publickey' => '', - 'partialkey' => serialize(array( - 'primes' => $primes, - 'coefficients' => $coefficients, - 'lcm' => $lcm, - 'exponents' => $exponents - )) - ); - } - } - - $size = $i == $num_primes ? $finalSize : $regSize; - $primes[$i] = BigInteger::randomPrime($size, $timeout); - - if ($primes[$i] === false) { // if we've reached the timeout - if (count($primes) > 1) { - $partialkey = ''; - } else { - array_pop($primes); - $partialkey = serialize(array( - 'primes' => $primes, - 'coefficients' => $coefficients, - 'lcm' => $lcm, - 'exponents' => $exponents - )); - } - - return array( - 'privatekey' => false, - 'publickey' => false, - 'partialkey' => $partialkey - ); + for ($i = 1; $i <= $num_primes; $i++) { + if ($i != $num_primes) { + $primes[$i] = BigInteger::randomPrime($regSize); + } else { + extract(BigInteger::minMaxBits($bits)); + 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 @@ -551,8 +543,6 @@ class RSA // 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); - - $exponents[$i] = $e->modInverse($temp); } list($temp) = $lcm['top']->divide($lcm['bottom']); @@ -560,9 +550,14 @@ class RSA $i0 = 1; } while (!$gcd->equals(self::$one)); + $coefficients[2] = $primes[2]->modInverse($primes[1]); + $d = $e->modInverse($temp); - $coefficients[2] = $primes[2]->modInverse($primes[1]); + foreach ($primes as $i => $prime) { + $temp = $prime->subtract(self::$one); + $exponents[$i] = $e->modInverse($temp); + } // from : // RSAPrivateKey ::= SEQUENCE { @@ -594,8 +589,7 @@ class RSA return array( 'privatekey' => $privatekey, - 'publickey' => $publickey, - 'partialkey' => false + 'publickey' => $publickey ); } @@ -705,11 +699,7 @@ class RSA $format = strtolower($type); if (isset(self::$fileFormats[$format])) { $format = self::$fileFormats[$format]; - try { - $components = $format::load($key, $this->password); - } catch (\Exception $e) { - $components = false; - } + $components = $format::load($key, $this->password); } } @@ -721,7 +711,7 @@ class RSA $this->format = $format; $this->modulus = $components['modulus']; - $this->k = strlen($this->modulus->toBytes()); + $this->k = $this->modulus->getLengthInBytes(); $this->exponent = isset($components['privateExponent']) ? $components['privateExponent'] : $components['publicExponent']; if (isset($components['primes'])) { $this->primes = $components['primes']; @@ -788,6 +778,34 @@ class RSA } return $type::savePrivateKey($this->modulus, $this->publicExponent, $this->exponent, $this->primes, $this->exponents, $this->coefficients, $this->password); + + /* + $key = $type::savePrivateKey($this->modulus, $this->publicExponent, $this->exponent, $this->primes, $this->exponents, $this->coefficients, $this->password); + 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); + */ } /** @@ -798,9 +816,9 @@ class RSA * @access public * @return int */ - function getSize() + function getLength() { - return !isset($this->modulus) ? 0 : strlen($this->modulus->toBits()); + return !isset($this->modulus) ? 0 : $this->modulus->getLength(); } /** @@ -1038,12 +1056,16 @@ class RSA */ function __toString() { - $key = $this->getPrivateKey($this->privateKeyFormat); - if (is_string($key)) { - return $key; + try { + $key = $this->getPrivateKey($this->privateKeyFormat); + if (is_string($key)) { + return $key; + } + $key = $this->_getPrivatePublicKey($this->publicKeyFormat); + return is_string($key) ? $key : ''; + } catch (\Exception $e) { + return ''; } - $key = $this->_getPrivatePublicKey($this->publicKeyFormat); - return is_string($key) ? $key : ''; } /** @@ -1112,7 +1134,7 @@ class RSA $this->hash = new Hash('sha256'); $this->hashName = 'sha256'; } - $this->hLen = $this->hash->getLength(); + $this->hLen = $this->hash->getLengthInBytes(); } /** @@ -1142,7 +1164,7 @@ class RSA default: $this->mgfHash = new Hash('sha256'); } - $this->mgfHLen = $this->mgfHash->getLength(); + $this->mgfHLen = $this->mgfHash->getLengthInBytes(); } /** @@ -1316,7 +1338,7 @@ class RSA * @param string $y * @return bool */ - function _equals($x, $y) + static function _equals($x, $y) { if (strlen($x) != strlen($y)) { return false; @@ -1528,7 +1550,7 @@ class RSA $db = $maskedDB ^ $dbMask; $lHash2 = substr($db, 0, $this->hLen); $m = substr($db, $this->hLen); - if ($lHash != $lHash2) { + if (!self::_equals($lHash, $lHash2)) { return false; } $m = ltrim($m, chr(0)); @@ -1746,7 +1768,7 @@ class RSA $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 $this->_equals($h, $h2); + return self::_equals($h, $h2); } /** @@ -1940,7 +1962,7 @@ class RSA } // Compare - return $this->_equals($em, $em2); + return self::_equals($em, $em2); } /** @@ -2044,7 +2066,7 @@ class RSA $em = $hash->hash($m); $em2 = Base64::decode($decoded['digest']); - return $this->_equals($em, $em2); + return self::_equals($em, $em2); } /** diff --git a/phpseclib/Crypt/RSA/PKCS.php b/phpseclib/Crypt/RSA/PKCS.php deleted file mode 100644 index adab5a39..00000000 --- a/phpseclib/Crypt/RSA/PKCS.php +++ /dev/null @@ -1,433 +0,0 @@ - - * @copyright 2015 Jim Wigginton - * @license http://www.opensource.org/licenses/mit-license.html MIT License - * @link http://phpseclib.sourceforge.net - */ - -namespace phpseclib\Crypt\RSA; - -use ParagonIE\ConstantTime\Base64; -use ParagonIE\ConstantTime\Hex; -use phpseclib\Crypt\Common\BlockCipher; -use phpseclib\Crypt\AES; -use phpseclib\Crypt\Base; -use phpseclib\Crypt\DES; -use phpseclib\Crypt\TripleDES; -use phpseclib\Math\BigInteger; -use phpseclib\Common\Functions\Strings; -use phpseclib\Common\Functions\ASN1; - -/** - * PKCS Formatted RSA Key Handler - * - * @package RSA - * @author Jim Wigginton - * @access public - */ -abstract class PKCS -{ - /**#@+ - * @access private - * @see \phpseclib\Crypt\RSA::createKey() - */ - /** - * ASN1 Integer - */ - const ASN1_INTEGER = 2; - /** - * ASN1 Bit String - */ - const ASN1_BITSTRING = 3; - /** - * ASN1 Octet String - */ - const ASN1_OCTETSTRING = 4; - /** - * ASN1 Object Identifier - */ - const ASN1_OBJECT = 6; - /** - * ASN1 Sequence (with the constucted bit set) - */ - const ASN1_SEQUENCE = 48; - /**#@-*/ - - /**#@+ - * @access private - */ - /** - * Auto-detect the format - */ - const MODE_ANY = 0; - /** - * Require base64-encoded PEM's be supplied - */ - const MODE_PEM = 1; - /** - * Require raw DER's be supplied - */ - const MODE_DER = 2; - /**#@-*/ - - /** - * Is the key a base-64 encoded PEM, DER or should it be auto-detected? - * - * @access private - * @param int - */ - static $format = self::MODE_ANY; - - /** - * Returns the mode constant corresponding to the mode string - * - * @access public - * @param string $mode - * @return int - * @throws \UnexpectedValueException if the block cipher mode is unsupported - */ - static function getEncryptionMode($mode) - { - switch ($mode) { - case 'CBC': - return BlockCipher::MODE_CBC; - case 'ECB': - return BlockCipher::MODE_ECB; - case 'CFB': - return BlockCipher::MODE_CFB; - case 'OFB': - return BlockCipher::MODE_OFB; - case 'CTR': - return BlockCipher::MODE_CTR; - } - throw new \UnexpectedValueException('Unsupported block cipher mode of operation'); - } - - /** - * Returns a cipher object corresponding to a string - * - * @access public - * @param string $algo - * @return string - * @throws \UnexpectedValueException if the encryption algorithm is unsupported - */ - static function getEncryptionObject($algo) - { - $modes = '(CBC|ECB|CFB|OFB|CTR)'; - switch (true) { - case preg_match("#^AES-(128|192|256)-$modes$#", $algo, $matches): - $cipher = new AES(self::getEncryptionMode($matches[2])); - $cipher->setKeyLength($matches[1]); - return $cipher; - case preg_match("#^DES-EDE3-$modes$#", $algo, $matches): - return new TripleDES(self::getEncryptionMode($matches[1])); - case preg_match("#^DES-$modes$#", $algo, $matches): - return new DES(self::getEncryptionMode($matches[1])); - default: - throw new \UnexpectedValueException('Unsupported encryption algorithmn'); - } - } - - /** - * Generate a symmetric key for PKCS#1 keys - * - * @access public - * @param string $password - * @param string $iv - * @param int $length - * @return string - */ - static function generateSymmetricKey($password, $iv, $length) - { - $symkey = ''; - $iv = substr($iv, 0, 8); - while (strlen($symkey) < $length) { - $symkey.= md5($symkey . $password . $iv, true); - } - return substr($symkey, 0, $length); - } - - /** - * Break a public or private key down into its constituent components - * - * @access public - * @param string $key - * @param string $password optional - * @return array - */ - static function load($key, $password = '') - { - if (!is_string($key)) { - return false; - } - - $components = array('isPublicKey' => strpos($key, 'PUBLIC') !== false); - - /* 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 - protect private keys, however, that's not what OpenSSL* does. OpenSSL protects private keys by adding - two new "fields" to the key - DEK-Info and Proc-Type. These fields are discussed here: - - http://tools.ietf.org/html/rfc1421#section-4.6.1.1 - http://tools.ietf.org/html/rfc1421#section-4.6.1.3 - - DES-EDE3-CBC as an algorithm, however, is not discussed anywhere, near as I can tell. - DES-CBC and DES-EDE are discussed in RFC1423, however, DES-EDE3-CBC isn't, nor is its key derivation - function. As is, the definitive authority on this encoding scheme isn't the IETF but rather OpenSSL's - own implementation. ie. the implementation *is* the standard and any bugs that may exist in that - implementation are part of the standard, as well. - - * OpenSSL is the de facto standard. It's utilized by OpenSSH and other projects */ - if (preg_match('#DEK-Info: (.+),(.+)#', $key, $matches)) { - $iv = Hex::decode(trim($matches[2])); - // remove the Proc-Type / DEK-Info sections as they're no longer needed - $key = preg_replace('#^(?:Proc-Type|DEK-Info): .*#m', '', $key); - $ciphertext = self::_extractBER($key); - if ($ciphertext === false) { - $ciphertext = $key; - } - $crypto = self::getEncryptionObject($matches[1]); - $crypto->setKey(self::generateSymmetricKey($password, $iv, $crypto->getKeyLength() >> 3)); - $crypto->setIV($iv); - $key = $crypto->decrypt($ciphertext); - if ($key === false) { - return false; - } - } else { - if (self::$format != self::MODE_DER) { - $decoded = self::_extractBER($key); - if ($decoded !== false) { - $key = $decoded; - } elseif (self::$format == self::MODE_PEM) { - return false; - } - } - } - - if (ord(Strings::shift($key)) != self::ASN1_SEQUENCE) { - return false; - } - if (ASN1::decodeLength($key) != strlen($key)) { - return false; - } - - $tag = ord(Strings::shift($key)); - /* intended for keys for which OpenSSL's asn1parse returns the following: - - 0:d=0 hl=4 l= 631 cons: SEQUENCE - 4:d=1 hl=2 l= 1 prim: INTEGER :00 - 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 - - ie. PKCS8 keys */ - - if ($tag == self::ASN1_INTEGER && substr($key, 0, 3) == "\x01\x00\x30") { - Strings::shift($key, 3); - $tag = self::ASN1_SEQUENCE; - } - - if ($tag == self::ASN1_SEQUENCE) { - $temp = Strings::shift($key, ASN1::decodeLength($key)); - if (ord(Strings::shift($temp)) != self::ASN1_OBJECT) { - return false; - } - $length = ASN1::decodeLength($temp); - switch (Strings::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(Strings::shift($temp)) != self::ASN1_SEQUENCE) { - return false; - } - if (ASN1::decodeLength($temp) != strlen($temp)) { - return false; - } - Strings::shift($temp); // assume it's an octet string - $salt = Strings::shift($temp, ASN1::decodeLength($temp)); - if (ord(Strings::shift($temp)) != self::ASN1_INTEGER) { - return false; - } - ASN1::decodeLength($temp); - list(, $iterationCount) = unpack('N', str_pad($temp, 4, chr(0), STR_PAD_LEFT)); - Strings::shift($key); // assume it's an octet string - $length = ASN1::decodeLength($key); - if (strlen($key) != $length) { - return false; - } - - $crypto = new DES(DES::MODE_CBC); - $crypto->setPassword($password, 'pbkdf1', 'md5', $salt, $iterationCount); - $key = $crypto->decrypt($key); - if ($key === false) { - return false; - } - return self::load($key); - default: - return false; - } - /* intended for keys for which OpenSSL's asn1parse returns the following: - - 0:d=0 hl=4 l= 290 cons: SEQUENCE - 4:d=1 hl=2 l= 13 cons: SEQUENCE - 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 */ - $tag = ord(Strings::shift($key)); // skip over the BIT STRING / OCTET STRING tag - ASN1::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 - // unused bits in the final subsequent octet. The number shall be in the range zero to seven." - // -- http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf (section 8.6.2.2) - if ($tag == self::ASN1_BITSTRING) { - Strings::shift($key); - } - if (ord(Strings::shift($key)) != self::ASN1_SEQUENCE) { - return false; - } - if (ASN1::decodeLength($key) != strlen($key)) { - return false; - } - $tag = ord(Strings::shift($key)); - } - if ($tag != self::ASN1_INTEGER) { - return false; - } - - $length = ASN1::decodeLength($key); - $temp = Strings::shift($key, $length); - if (strlen($temp) != 1 || ord($temp) > 2) { - $components['modulus'] = new BigInteger($temp, 256); - Strings::shift($key); // skip over self::ASN1_INTEGER - $length = ASN1::decodeLength($key); - $components[$components['isPublicKey'] ? 'publicExponent' : 'privateExponent'] = new BigInteger(Strings::shift($key, $length), 256); - - return $components; - } - if (ord(Strings::shift($key)) != self::ASN1_INTEGER) { - return false; - } - $length = ASN1::decodeLength($key); - $components['modulus'] = new BigInteger(Strings::shift($key, $length), 256); - Strings::shift($key); - $length = ASN1::decodeLength($key); - $components['publicExponent'] = new BigInteger(Strings::shift($key, $length), 256); - Strings::shift($key); - $length = ASN1::decodeLength($key); - $components['privateExponent'] = new BigInteger(Strings::shift($key, $length), 256); - Strings::shift($key); - $length = ASN1::decodeLength($key); - $components['primes'] = array(1 => new BigInteger(Strings::shift($key, $length), 256)); - Strings::shift($key); - $length = ASN1::decodeLength($key); - $components['primes'][] = new BigInteger(Strings::shift($key, $length), 256); - Strings::shift($key); - $length = ASN1::decodeLength($key); - $components['exponents'] = array(1 => new BigInteger(Strings::shift($key, $length), 256)); - Strings::shift($key); - $length = ASN1::decodeLength($key); - $components['exponents'][] = new BigInteger(Strings::shift($key, $length), 256); - Strings::shift($key); - $length = ASN1::decodeLength($key); - $components['coefficients'] = array(2 => new BigInteger(Strings::shift($key, $length), 256)); - - if (!empty($key)) { - if (ord(Strings::shift($key)) != self::ASN1_SEQUENCE) { - return false; - } - ASN1::decodeLength($key); - while (!empty($key)) { - if (ord(Strings::shift($key)) != self::ASN1_SEQUENCE) { - return false; - } - ASN1::decodeLength($key); - $key = substr($key, 1); - $length = ASN1::decodeLength($key); - $components['primes'][] = new BigInteger(Strings::shift($key, $length), 256); - Strings::shift($key); - $length = ASN1::decodeLength($key); - $components['exponents'][] = new BigInteger(Strings::shift($key, $length), 256); - Strings::shift($key); - $length = ASN1::decodeLength($key); - $components['coefficients'][] = new BigInteger(Strings::shift($key, $length), 256); - } - } - - return $components; - } - - /** - * Require base64-encoded PEM's be supplied - * - * @see self::load() - * @access public - */ - static function requirePEM() - { - self::$format = self::MODE_PEM; - } - - /** - * Require raw DER's be supplied - * - * @see self::load() - * @access public - */ - static function requireDER() - { - self::$format = self::MODE_DER; - } - - /** - * Accept any format and auto detect the format - * - * This is the default setting - * - * @see self::load() - * @access public - */ - static function requireAny() - { - self::$format = self::MODE_ANY; - } - - /** - * Extract raw BER from Base64 encoding - * - * @access private - * @param string $str - * @return string - */ - static function _extractBER($str) - { - /* X.509 certs are assumed to be base64 encoded but sometimes they'll have additional things in them - * above and beyond the ceritificate. - * ie. some may have the following preceding the -----BEGIN CERTIFICATE----- line: - * - * Bag Attributes - * localKeyID: 01 00 00 00 - * subject=/O=organization/OU=org unit/CN=common name - * issuer=/O=organization/CN=common name - */ - $temp = preg_replace('#.*?^-+[^-]+-+[\r\n ]*$#ms', '', $str, 1); - // remove the -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- stuff - $temp = preg_replace('#-+[^-]+-+#', '', $temp); - // remove new lines - $temp = str_replace(array("\r", "\n", ' '), '', $temp); - $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? Base64::decode($temp) : false; - return $temp != false ? $temp : $str; - } -} diff --git a/phpseclib/Crypt/RSA/PKCS1.php b/phpseclib/Crypt/RSA/PKCS1.php index 451e766d..f79d30a1 100644 --- a/phpseclib/Crypt/RSA/PKCS1.php +++ b/phpseclib/Crypt/RSA/PKCS1.php @@ -24,14 +24,55 @@ namespace phpseclib\Crypt\RSA; -use ParagonIE\ConstantTime\Base64; -use ParagonIE\ConstantTime\Hex; -use phpseclib\Crypt\AES; -use phpseclib\Crypt\DES; -use phpseclib\Crypt\Random; -use phpseclib\Crypt\TripleDES; use phpseclib\Math\BigInteger; -use phpseclib\Common\Functions\ASN1; +use phpseclib\Crypt\Common\PKCS1 as Progenitor; +use phpseclib\File\ASN1; + +// version must be multi if otherPrimeInfos present +define(__NAMESPACE__ . '\Version', [ + 'type' => ASN1::TYPE_INTEGER, + 'mapping' => ['two-prime', 'multi'] +]); + +define(__NAMESPACE__ . '\OtherPrimeInfo', [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'prime' => ['type' => ASN1::TYPE_INTEGER], // ri + 'exponent' => ['type' => ASN1::TYPE_INTEGER], // di + 'coefficient' => ['type' => ASN1::TYPE_INTEGER] // ti + ] +]); + +define(__NAMESPACE__ . '\OtherPrimeInfos', [ + 'type' => ASN1::TYPE_SEQUENCE, + 'min' => 1, + 'max' => -1, + 'children' => OtherPrimeInfo +]); + +define(__NAMESPACE__ . '\RSAPrivateKey', [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'version' => Version, + 'modulus' => ['type' => ASN1::TYPE_INTEGER], // n + 'publicExponent' => ['type' => ASN1::TYPE_INTEGER], // e + 'privateExponent' => ['type' => ASN1::TYPE_INTEGER], // d + 'prime1' => ['type' => ASN1::TYPE_INTEGER], // p + 'prime2' => ['type' => ASN1::TYPE_INTEGER], // q + 'exponent1' => ['type' => ASN1::TYPE_INTEGER], // d mod (p-1) + 'exponent2' => ['type' => ASN1::TYPE_INTEGER], // d mod (q-1) + 'coefficient' => ['type' => ASN1::TYPE_INTEGER], // (inverse of q) mod p + 'otherPrimeInfos' => OtherPrimeInfos + ['optional' => true] + ] +]); + +define(__NAMESPACE__ . '\RSAPublicKey', [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'modulus' => ['type' => ASN1::TYPE_INTEGER], + 'publicExponent' => ['type' => ASN1::TYPE_INTEGER] + ] +]); /** * PKCS#1 Formatted RSA Key Handler @@ -40,25 +81,58 @@ use phpseclib\Common\Functions\ASN1; * @author Jim Wigginton * @access public */ -class PKCS1 extends PKCS +class PKCS1 extends Progenitor { /** - * Default encryption algorithm - * - * @var string - * @access private - */ - static $defaultEncryptionAlgorithm = 'DES-EDE3-CBC'; - - /** - * Sets the default encryption algorithm + * Break a public or private key down into its constituent components * * @access public - * @param string $algo + * @param string $key + * @param string $password optional + * @return array */ - static function setEncryptionAlgorithm($algo) + static function load($key, $password = '') { - self::$defaultEncryptionAlgorithm = $algo; + if (!is_string($key)) { + return false; + } + + $components = ['isPublicKey' => strpos($key, 'PUBLIC') !== false]; + + $key = parent::load($key, $password); + if ($key === false) { + return false; + } + + $asn1 = new ASN1(); + $decoded = $asn1->decodeBER($key); + if (empty($decoded)) { + return false; + } + + $key = $asn1->asn1map($decoded[0], RSAPrivateKey); + 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']; + } + } + return $components; + } + + $key = $asn1->asn1map($decoded[0], RSAPublicKey); + + return is_array($key) ? $components + $key : false; } /** @@ -77,64 +151,29 @@ class PKCS1 extends PKCS static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, $primes, $exponents, $coefficients, $password = '') { $num_primes = count($primes); - $raw = array( - 'version' => $num_primes == 2 ? chr(0) : chr(1), // two-prime vs. multi - 'modulus' => $n->toBytes(true), - 'publicExponent' => $e->toBytes(true), - 'privateExponent' => $d->toBytes(true), - 'prime1' => $primes[1]->toBytes(true), - 'prime2' => $primes[2]->toBytes(true), - 'exponent1' => $exponents[1]->toBytes(true), - 'exponent2' => $exponents[2]->toBytes(true), - 'coefficient' => $coefficients[2]->toBytes(true) - ); - - $components = array(); - foreach ($raw as $name => $value) { - $components[$name] = pack('Ca*a*', self::ASN1_INTEGER, ASN1::encodeLength(strlen($value)), $value); + $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] + ]; } - $RSAPrivateKey = implode('', $components); + $asn1 = new ASN1(); + $key = $asn1->encodeDER($key, RSAPrivateKey); - if ($num_primes > 2) { - $OtherPrimeInfos = ''; - for ($i = 3; $i <= $num_primes; $i++) { - // OtherPrimeInfos ::= SEQUENCE SIZE(1..MAX) OF OtherPrimeInfo - // - // OtherPrimeInfo ::= SEQUENCE { - // prime INTEGER, -- ri - // exponent INTEGER, -- di - // coefficient INTEGER -- ti - // } - $OtherPrimeInfo = pack('Ca*a*', self::ASN1_INTEGER, ASN1::encodeLength(strlen($primes[$i]->toBytes(true))), $primes[$i]->toBytes(true)); - $OtherPrimeInfo.= pack('Ca*a*', self::ASN1_INTEGER, ASN1::encodeLength(strlen($exponents[$i]->toBytes(true))), $exponents[$i]->toBytes(true)); - $OtherPrimeInfo.= pack('Ca*a*', self::ASN1_INTEGER, ASN1::encodeLength(strlen($coefficients[$i]->toBytes(true))), $coefficients[$i]->toBytes(true)); - $OtherPrimeInfos.= pack('Ca*a*', self::ASN1_SEQUENCE, ASN1::encodeLength(strlen($OtherPrimeInfo)), $OtherPrimeInfo); - } - $RSAPrivateKey.= pack('Ca*a*', self::ASN1_SEQUENCE, ASN1::encodeLength(strlen($OtherPrimeInfos)), $OtherPrimeInfos); - } - - $RSAPrivateKey = pack('Ca*a*', self::ASN1_SEQUENCE, ASN1::encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey); - - if (!empty($password) || is_string($password)) { - $cipher = self::getEncryptionObject(self::$defaultEncryptionAlgorithm); - $iv = Random::string($cipher->getBlockLength() >> 3); - $cipher->setKey(self::generateSymmetricKey($password, $iv, $cipher->getKeyLength() >> 3)); - $cipher->setIV($iv); - $iv = strtoupper(Hex::encode($iv)); - $RSAPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\r\n" . - "Proc-Type: 4,ENCRYPTED\r\n" . - "DEK-Info: " . self::$defaultEncryptionAlgorithm . ",$iv\r\n" . - "\r\n" . - chunk_split(Base64::encode($cipher->encrypt($RSAPrivateKey)), 64) . - '-----END RSA PRIVATE KEY-----'; - } else { - $RSAPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\r\n" . - chunk_split(Base64::encode($RSAPrivateKey), 64) . - '-----END RSA PRIVATE KEY-----'; - } - - return $RSAPrivateKey; + return self::wrapPrivateKey($key, 'RSA', $password); } /** @@ -147,31 +186,14 @@ class PKCS1 extends PKCS */ static function savePublicKey(BigInteger $n, BigInteger $e) { - $modulus = $n->toBytes(true); - $publicExponent = $e->toBytes(true); + $key = [ + 'modulus' => $n, + 'publicExponent' => $e + ]; - // from : - // RSAPublicKey ::= SEQUENCE { - // modulus INTEGER, -- n - // publicExponent INTEGER -- e - // } - $components = array( - 'modulus' => pack('Ca*a*', self::ASN1_INTEGER, ASN1::encodeLength(strlen($modulus)), $modulus), - 'publicExponent' => pack('Ca*a*', self::ASN1_INTEGER, ASN1::encodeLength(strlen($publicExponent)), $publicExponent) - ); + $asn1 = new ASN1(); + $key = $asn1->encodeDER($key, RSAPublicKey); - $RSAPublicKey = pack( - 'Ca*a*a*', - self::ASN1_SEQUENCE, - ASN1::encodeLength(strlen($components['modulus']) + strlen($components['publicExponent'])), - $components['modulus'], - $components['publicExponent'] - ); - - $RSAPublicKey = "-----BEGIN RSA PUBLIC KEY-----\r\n" . - chunk_split(Base64::encode($RSAPublicKey), 64) . - '-----END RSA PUBLIC KEY-----'; - - return $RSAPublicKey; + return self::wrapPublicKey($key, 'RSA'); } } diff --git a/phpseclib/Crypt/RSA/PKCS8.php b/phpseclib/Crypt/RSA/PKCS8.php index a57dc1e3..ab904fa8 100644 --- a/phpseclib/Crypt/RSA/PKCS8.php +++ b/phpseclib/Crypt/RSA/PKCS8.php @@ -27,21 +27,51 @@ namespace phpseclib\Crypt\RSA; -use ParagonIE\ConstantTime\Base64; -use phpseclib\Crypt\DES; -use phpseclib\Crypt\Random; use phpseclib\Math\BigInteger; -use phpseclib\Common\Functions\ASN1; +use phpseclib\Crypt\Common\PKCS8 as Progenitor; +use phpseclib\File\ASN1; /** - * PKCS#8 Formatted RSA Key Handler + * PKCS#1 Formatted RSA Key Handler * * @package RSA * @author Jim Wigginton * @access public */ -class PKCS8 extends PKCS +class PKCS8 extends Progenitor { + /** + * Break a public or private key down into its constituent components + * + * @access public + * @param string $key + * @param string $password optional + * @return array + */ + static function load($key, $password = '') + { + $components = ['isPublicKey' => strpos($key, 'PUBLIC') !== false]; + + $key = parent::load($key, $password); + if ($key === false) { + return false; + } + + $type = isset($key['privateKey']) ? 'private' : 'public'; + + if ($key[$type . 'KeyAlgorithm']['algorithm'] != '1.2.840.113549.1.1.1') { + return false; + } + + $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. * @@ -57,108 +87,9 @@ class PKCS8 extends PKCS */ static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, $primes, $exponents, $coefficients, $password = '') { - $num_primes = count($primes); - $raw = array( - 'version' => $num_primes == 2 ? chr(0) : chr(1), // two-prime vs. multi - 'modulus' => $n->toBytes(true), - 'publicExponent' => $e->toBytes(true), - 'privateExponent' => $d->toBytes(true), - 'prime1' => $primes[1]->toBytes(true), - 'prime2' => $primes[2]->toBytes(true), - 'exponent1' => $exponents[1]->toBytes(true), - 'exponent2' => $exponents[2]->toBytes(true), - 'coefficient' => $coefficients[2]->toBytes(true) - ); - - $components = array(); - foreach ($raw as $name => $value) { - $components[$name] = pack('Ca*a*', self::ASN1_INTEGER, ASN1::encodeLength(strlen($value)), $value); - } - - $RSAPrivateKey = implode('', $components); - - if ($num_primes > 2) { - $OtherPrimeInfos = ''; - for ($i = 3; $i <= $num_primes; $i++) { - // OtherPrimeInfos ::= SEQUENCE SIZE(1..MAX) OF OtherPrimeInfo - // - // OtherPrimeInfo ::= SEQUENCE { - // prime INTEGER, -- ri - // exponent INTEGER, -- di - // coefficient INTEGER -- ti - // } - $OtherPrimeInfo = pack('Ca*a*', self::ASN1_INTEGER, ASN1::encodeLength(strlen($primes[$i]->toBytes(true))), $primes[$i]->toBytes(true)); - $OtherPrimeInfo.= pack('Ca*a*', self::ASN1_INTEGER, ASN1::encodeLength(strlen($exponents[$i]->toBytes(true))), $exponents[$i]->toBytes(true)); - $OtherPrimeInfo.= pack('Ca*a*', self::ASN1_INTEGER, ASN1::encodeLength(strlen($coefficients[$i]->toBytes(true))), $coefficients[$i]->toBytes(true)); - $OtherPrimeInfos.= pack('Ca*a*', self::ASN1_SEQUENCE, ASN1::encodeLength(strlen($OtherPrimeInfo)), $OtherPrimeInfo); - } - $RSAPrivateKey.= pack('Ca*a*', self::ASN1_SEQUENCE, ASN1::encodeLength(strlen($OtherPrimeInfos)), $OtherPrimeInfos); - } - - $RSAPrivateKey = pack('Ca*a*', self::ASN1_SEQUENCE, ASN1::encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey); - - $rsaOID = "\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00"; // hex version of MA0GCSqGSIb3DQEBAQUA - $RSAPrivateKey = pack( - 'Ca*a*Ca*a*', - self::ASN1_INTEGER, - "\01\00", - $rsaOID, - 4, - ASN1::encodeLength(strlen($RSAPrivateKey)), - $RSAPrivateKey - ); - $RSAPrivateKey = pack('Ca*a*', self::ASN1_SEQUENCE, ASN1::encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey); - if (!empty($password) || is_string($password)) { - $salt = Random::string(8); - $iterationCount = 2048; - - $crypto = new DES(DES::MODE_CBC); - $crypto->setPassword($password, 'pbkdf1', 'md5', $salt, $iterationCount); - $RSAPrivateKey = $crypto->encrypt($RSAPrivateKey); - - $parameters = pack( - 'Ca*a*Ca*N', - self::ASN1_OCTETSTRING, - ASN1::encodeLength(strlen($salt)), - $salt, - self::ASN1_INTEGER, - ASN1::encodeLength(4), - $iterationCount - ); - $pbeWithMD5AndDES_CBC = "\x2a\x86\x48\x86\xf7\x0d\x01\x05\x03"; - - $encryptionAlgorithm = pack( - 'Ca*a*Ca*a*', - self::ASN1_OBJECT, - ASN1::encodeLength(strlen($pbeWithMD5AndDES_CBC)), - $pbeWithMD5AndDES_CBC, - self::ASN1_SEQUENCE, - ASN1::encodeLength(strlen($parameters)), - $parameters - ); - - $RSAPrivateKey = pack( - 'Ca*a*Ca*a*', - self::ASN1_SEQUENCE, - ASN1::encodeLength(strlen($encryptionAlgorithm)), - $encryptionAlgorithm, - self::ASN1_OCTETSTRING, - ASN1::encodeLength(strlen($RSAPrivateKey)), - $RSAPrivateKey - ); - - $RSAPrivateKey = pack('Ca*a*', self::ASN1_SEQUENCE, ASN1::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; + $key = PKCS1::savePrivateKey($n, $e, $d, $primes, $exponents, $coefficients); + $key = ASN1::extractBER($key); + return self::wrapPrivateKey($key, '1.2.840.113549.1.1.1', [], $password); } /** @@ -171,43 +102,8 @@ class PKCS8 extends PKCS */ static function savePublicKey(BigInteger $n, BigInteger $e) { - $modulus = $n->toBytes(true); - $publicExponent = $e->toBytes(true); - - // from : - // RSAPublicKey ::= SEQUENCE { - // modulus INTEGER, -- n - // publicExponent INTEGER -- e - // } - $components = array( - 'modulus' => pack('Ca*a*', self::ASN1_INTEGER, ASN1::encodeLength(strlen($modulus)), $modulus), - 'publicExponent' => pack('Ca*a*', self::ASN1_INTEGER, ASN1::encodeLength(strlen($publicExponent)), $publicExponent) - ); - - $RSAPublicKey = pack( - 'Ca*a*a*', - self::ASN1_SEQUENCE, - ASN1::encodeLength(strlen($components['modulus']) + strlen($components['publicExponent'])), - $components['modulus'], - $components['publicExponent'] - ); - - // sequence(oid(1.2.840.113549.1.1.1), null)) = rsaEncryption. - $rsaOID = "\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00"; // hex version of MA0GCSqGSIb3DQEBAQUA - $RSAPublicKey = chr(0) . $RSAPublicKey; - $RSAPublicKey = chr(3) . ASN1::encodeLength(strlen($RSAPublicKey)) . $RSAPublicKey; - - $RSAPublicKey = pack( - 'Ca*a*', - self::ASN1_SEQUENCE, - ASN1::encodeLength(strlen($rsaOID . $RSAPublicKey)), - $rsaOID . $RSAPublicKey - ); - - $RSAPublicKey = "-----BEGIN PUBLIC KEY-----\r\n" . - chunk_split(Base64::encode($RSAPublicKey), 64) . - '-----END PUBLIC KEY-----'; - - return $RSAPublicKey; + $key = PKCS1::savePublicKey($n, $e); + $key = ASN1::extractBER($key); + return self::wrapPublicKey($key, '1.2.840.113549.1.1.1'); } } diff --git a/phpseclib/Crypt/RSA/PuTTY.php b/phpseclib/Crypt/RSA/PuTTY.php index 1a9dbe4a..f2187abc 100644 --- a/phpseclib/Crypt/RSA/PuTTY.php +++ b/phpseclib/Crypt/RSA/PuTTY.php @@ -138,9 +138,6 @@ class PuTTY $crypto->setIV(str_repeat("\0", $crypto->getBlockLength() >> 3)); $crypto->disablePadding(); $private = $crypto->decrypt($private); - if ($private === false) { - return false; - } } extract(unpack('Nlength', Strings::shift($private, 4))); @@ -289,7 +286,7 @@ class PuTTY $n ); $key = "---- BEGIN SSH2 PUBLIC KEY ----\r\n" . - 'Comment: "' . str_replace(array('\\', '"'), array('\\\\', '\"'), self::$comment) . "\"\r\n"; + 'Comment: "' . str_replace(array('\\', '"'), array('\\\\', '\"'), self::$comment) . "\"\r\n" . chunk_split(Base64::encode($key), 64) . '---- END SSH2 PUBLIC KEY ----'; diff --git a/phpseclib/Crypt/Random.php b/phpseclib/Crypt/Random.php index c102e5e0..21d6addc 100644 --- a/phpseclib/Crypt/Random.php +++ b/phpseclib/Crypt/Random.php @@ -184,6 +184,7 @@ class Random $v = $crypto->encrypt($r ^ $i); // strlen($r) == 20 $result.= $r; } + return substr($result, 0, $length); } diff --git a/phpseclib/File/ASN1.php b/phpseclib/File/ASN1.php index 6b3a7328..f20d70b7 100644 --- a/phpseclib/File/ASN1.php +++ b/phpseclib/File/ASN1.php @@ -1286,4 +1286,31 @@ class ASN1 } return $out; } + + /** + * Extract raw BER from Base64 encoding + * + * @access private + * @param string $str + * @return string + */ + static function extractBER($str) + { + /* X.509 certs are assumed to be base64 encoded but sometimes they'll have additional things in them + * above and beyond the ceritificate. + * ie. some may have the following preceding the -----BEGIN CERTIFICATE----- line: + * + * Bag Attributes + * localKeyID: 01 00 00 00 + * subject=/O=organization/OU=org unit/CN=common name + * issuer=/O=organization/CN=common name + */ + $temp = preg_replace('#.*?^-+[^-]+-+[\r\n ]*$#ms', '', $str, 1); + // remove the -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- stuff + $temp = preg_replace('#-+[^-]+-+#', '', $temp); + // remove new lines + $temp = str_replace(array("\r", "\n", ' '), '', $temp); + $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? Base64::decode($temp) : false; + return $temp != false ? $temp : $str; + } } diff --git a/phpseclib/File/X509.php b/phpseclib/File/X509.php index fee55ed6..735f6bb5 100644 --- a/phpseclib/File/X509.php +++ b/phpseclib/File/X509.php @@ -1462,7 +1462,7 @@ class X509 $asn1 = new ASN1(); if ($mode != self::FORMAT_DER) { - $newcert = $this->_extractBER($cert); + $newcert = ASN1::extractBER($cert); if ($mode == self::FORMAT_PEM && $cert == $newcert) { return false; } @@ -2974,7 +2974,7 @@ class X509 $asn1 = new ASN1(); if ($mode != self::FORMAT_DER) { - $newcsr = $this->_extractBER($csr); + $newcsr = ASN1::extractBER($csr); if ($mode == self::FORMAT_PEM && $csr == $newcsr) { return false; } @@ -3216,7 +3216,7 @@ class X509 $asn1 = new ASN1(); if ($mode != self::FORMAT_DER) { - $newcrl = $this->_extractBER($crl); + $newcrl = ASN1::extractBER($crl); if ($mode == self::FORMAT_PEM && $crl == $newcrl) { return false; } @@ -3847,7 +3847,7 @@ class X509 if (strtolower($date) == 'lifetime') { $temp = '99991231235959Z'; $asn1 = new ASN1(); - $temp = chr(ASN1::TYPE_GENERALIZED_TIME) . $asn1->_encodeLength(strlen($temp)) . $temp; + $temp = chr(ASN1::TYPE_GENERALIZED_TIME) . Functions::encodeLength(strlen($temp)) . $temp; $this->endDate = new Element($temp); } else { $this->endDate = @date('D, d M Y H:i:s O', @strtotime($date)); @@ -4465,7 +4465,7 @@ class X509 } // If in PEM format, convert to binary. - $key = $this->_extractBER($key); + $key = ASN1::extractBER($key); // Now we have the key string: compute its sha-1 sum. $hash = new Hash('sha1'); @@ -4770,33 +4770,6 @@ class X509 return false; } - /** - * Extract raw BER from Base64 encoding - * - * @access private - * @param string $str - * @return string - */ - function _extractBER($str) - { - /* X.509 certs are assumed to be base64 encoded but sometimes they'll have additional things in them - * above and beyond the ceritificate. - * ie. some may have the following preceding the -----BEGIN CERTIFICATE----- line: - * - * Bag Attributes - * localKeyID: 01 00 00 00 - * subject=/O=organization/OU=org unit/CN=common name - * issuer=/O=organization/CN=common name - */ - $temp = preg_replace('#.*?^-+[^-]+-+[\r\n ]*$#ms', '', $str, 1); - // remove the -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- stuff - $temp = preg_replace('#-+[^-]+-+#', '', $temp); - // remove new lines - $temp = str_replace(array("\r", "\n", ' '), '', $temp); - $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? Base64::decode($temp) : false; - return $temp != false ? $temp : $str; - } - /** * Returns the OID corresponding to a name * diff --git a/phpseclib/Math/BigInteger.php b/phpseclib/Math/BigInteger.php index 358151bb..0fc8bc7c 100644 --- a/phpseclib/Math/BigInteger.php +++ b/phpseclib/Math/BigInteger.php @@ -53,6 +53,7 @@ namespace phpseclib\Math; use ParagonIE\ConstantTime\Base64; use ParagonIE\ConstantTime\Hex; use phpseclib\Crypt\Random; +use phpseclib\Common\Functions\ASN1; /** * Pure-PHP arbitrary precision integer arithmetic library. Supports base-2, base-10, base-16, and base-256 @@ -1602,26 +1603,26 @@ class BigInteger ); $components = array( - 'modulus' => pack('Ca*a*', 2, self::_encodeASN1Length(strlen($components['modulus'])), $components['modulus']), - 'publicExponent' => pack('Ca*a*', 2, self::_encodeASN1Length(strlen($components['publicExponent'])), $components['publicExponent']) + 'modulus' => pack('Ca*a*', 2, ASN1::encodeLength(strlen($components['modulus'])), $components['modulus']), + 'publicExponent' => pack('Ca*a*', 2, ASN1::encodeLength(strlen($components['publicExponent'])), $components['publicExponent']) ); $RSAPublicKey = pack( 'Ca*a*a*', 48, - self::_encodeASN1Length(strlen($components['modulus']) + strlen($components['publicExponent'])), + ASN1::encodeLength(strlen($components['modulus']) + strlen($components['publicExponent'])), $components['modulus'], $components['publicExponent'] ); $rsaOID = "\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00"; // hex version of MA0GCSqGSIb3DQEBAQUA $RSAPublicKey = chr(0) . $RSAPublicKey; - $RSAPublicKey = chr(3) . self::_encodeASN1Length(strlen($RSAPublicKey)) . $RSAPublicKey; + $RSAPublicKey = chr(3) . ASN1::encodeLength(strlen($RSAPublicKey)) . $RSAPublicKey; $encapsulated = pack( 'Ca*a*', 48, - self::_encodeASN1Length(strlen($rsaOID . $RSAPublicKey)), + ASN1::encodeLength(strlen($rsaOID . $RSAPublicKey)), $rsaOID . $RSAPublicKey ); @@ -3009,10 +3010,35 @@ class BigInteger return $this->bitwise_leftRotate(-$shift); } + /** + * Returns the smallest and largest n-bit number + * + * @param int $bits + * @return \phpseclib\Math\BigInteger + * @access public + */ + static function minMaxBits($bits) + { + $bytes = $bits >> 3; + $min = str_repeat(chr(0), $bytes); + $max = str_repeat(chr(0xFF), $bytes); + $msb = $bits & 7; + if ($msb) { + $min = chr(1 << ($msb - 1)) . $min; + $max = chr((1 << $msb) - 1) . $max; + } else { + $min[0] = chr(0x80); + } + return array( + 'min' => new static($min, 256), + 'max' => new static($max, 256) + ); + } + /** * Generates a random number of a certain size * - * Byte length is equal to $size. Uses \phpseclib\Crypt\Random if it's loaded and mt_rand if it's not. + * Bit length is equal to $size. * * @param int $size * @return \phpseclib\Math\BigInteger @@ -3020,23 +3046,8 @@ class BigInteger */ static function random($size) { - if (class_exists('\phpseclib\Crypt\Random')) { - $random = Random::string($size); - } else { - $random = ''; - - if ($size & 1) { - $random.= chr(mt_rand(0, 255)); - } - - $blocks = $size >> 1; - for ($i = 0; $i < $blocks; ++$i) { - // mt_rand(-2147483648, 0x7FFFFFFF) always produces -2147483648 on some systems - $random.= pack('n', mt_rand(0, 0xFFFF)); - } - } - - return new static($random, 256); + extract(self::minMaxBits($size)); + return self::randomRange($min, $max); } /** @@ -3058,7 +3069,7 @@ class BigInteger $compare = $max->compare($min); if (!$compare) { - return $this->_normalize($min); + return $min; } elseif ($compare < 0) { // if $min is bigger then $max, swap $min and $max $temp = $max; @@ -3072,6 +3083,7 @@ class BigInteger } $max = $max->subtract($min->subtract($one)); + $size = strlen(ltrim($max->toBytes(), chr(0))); /* @@ -3090,7 +3102,7 @@ class BigInteger http://crypto.stackexchange.com/questions/5708/creating-a-small-number-from-a-cryptographically-secure-random-string */ $random_max = new static(chr(1) . str_repeat("\0", $size), 256); - $random = static::random($size); + $random = new static(Random::string($size), 256); list($max_multiple) = $random_max->divide($max); $max_multiple = $max_multiple->multiply($max); @@ -3099,7 +3111,7 @@ class BigInteger $random = $random->subtract($max_multiple); $random_max = $random_max->subtract($max_multiple); $random = $random->bitwise_leftShift(8); - $random = $random->add(self::random(1)); + $random = $random->add(new static(Random::string(1), 256)); $random_max = $random_max->bitwise_leftShift(8); list($max_multiple) = $random_max->divide($max); $max_multiple = $max_multiple->multiply($max); @@ -3112,46 +3124,30 @@ class BigInteger /** * Generates a random prime number of a certain size * - * Byte length is equal to $size + * Bit length is equal to $size * * @param int $size - * @param int $timeout * @return \phpseclib\Math\BigInteger * @access public */ - static function randomPrime($size, $timeout = false) + static function randomPrime($size) { - $min = str_repeat(chr(0), $bytes); - $max = str_repeat(chr(0xFF), $bytes); - $msb = $bits & 7; - if ($msb) { - $min = chr(1 << ($msb - 1)) . $min; - $max = chr((1 << $msb) - 1) . $max; - } else { - $min[0] = chr(0x80); - } - - return self::randomRangePrime( - new Math_BigInteger($min, 256), - new Math_BigInteger($max, 256), - $timeout - ); + extract(self::minMaxBits($size)); + return self::randomRangePrime($min, $max); } /** * Generate a random prime number between a range * * If there's not a prime within the given range, false will be returned. - * If more than $timeout seconds have elapsed, give up and return false. * * @param \phpseclib\Math\BigInteger $min * @param \phpseclib\Math\BigInteger $max - * @param int $timeout * @return Math_BigInteger|false * @access public * @internal See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap4.pdf#page=15 HAC 4.44}. */ - static function randomRangePrime(BigInteger $min, BigInteger $max, $timeout = false) + static function randomRangePrime(BigInteger $min, BigInteger $max) { $compare = $max->compare($min); @@ -3170,9 +3166,7 @@ class BigInteger $two = new static(2); } - $start = time(); - - $x = self::random($min, $max); + $x = self::randomRange($min, $max); // gmp_nextprime() requires PHP 5 >= 5.2.0 per . if (MATH_BIGINTEGER_MODE == self::MODE_GMP && extension_loaded('gmp')) { @@ -3187,7 +3181,7 @@ class BigInteger $x = $x->subtract($one); } - return self::randomPrime($min, $x); + return self::randomRangePrime($min, $x); } if ($x->equals($two)) { @@ -3207,10 +3201,6 @@ class BigInteger $initial_x = clone $x; while (true) { - if ($timeout !== false && time() - $start > $timeout) { - return false; - } - if ($x->isPrime()) { return $x; } @@ -3391,7 +3381,7 @@ class BigInteger } for ($i = 0; $i < $t; ++$i) { - $a = self::random($two, $n_2); + $a = self::randomRange($two, $n_2); $y = $a->modPow($r, $n); if (!$y->equals($one) && !$y->equals($n_1)) { @@ -3661,26 +3651,6 @@ class BigInteger return $temp['int']; } - /** - * DER-encode an integer - * - * The ability to DER-encode integers is needed to create RSA public keys for use with OpenSSL - * - * @see self::modPow() - * @access private - * @param int $length - * @return string - */ - static function _encodeASN1Length($length) - { - if ($length <= 0x7F) { - return chr($length); - } - - $temp = ltrim(pack('N', $length), chr(0)); - return pack('Ca*', 0x80 | strlen($temp), $temp); - } - /** * Single digit division * @@ -3723,7 +3693,6 @@ class BigInteger * @param \phpseclib\Math\BigInteger $n * @access public * @return \phpseclib\Math\BigInteger - * * @internal This function is based off of {@link http://mathforum.org/library/drmath/view/52605.html this page} and {@link http://stackoverflow.com/questions/11242920/calculating-nth-root-with-bcmath-in-php this stackoverflow question}. */ function root($n = null) @@ -3876,4 +3845,26 @@ class BigInteger } return $max; } + + /** + * Return the size of a BigInteger in bits + * + * @access public + * @return int + */ + function getLength() + { + return strlen($this->toBits()); + } + + /** + * Return the size of a BigInteger in bytes + * + * @access public + * @return int + */ + function getLengthInBytes() + { + return strlen($this->toBytes()); + } } diff --git a/phpseclib/Net/SSH2.php b/phpseclib/Net/SSH2.php index c889e6ab..181831fa 100644 --- a/phpseclib/Net/SSH2.php +++ b/phpseclib/Net/SSH2.php @@ -1511,7 +1511,7 @@ class SSH2 -- http://tools.ietf.org/html/rfc4419#section-6.2 */ $one = new BigInteger(1); - $keyLength = min($kexHash->getLength(), max($encryptKeyLength, $decryptKeyLength)); + $keyLength = min($kexHash->getLengthInBytes(), max($encryptKeyLength, $decryptKeyLength)); $max = $one->bitwise_leftShift(16 * $keyLength); // 2 * 8 * $keyLength $max = $max->subtract($one); @@ -2928,9 +2928,6 @@ class SSH2 if ($this->decrypt !== false) { $raw = $this->decrypt->decrypt($raw); } - if ($raw === false) { - throw new \RuntimeException('Unable to decrypt content'); - } extract(unpack('Npacket_length/Cpadding_length', Strings::shift($raw, 5))); diff --git a/tests/Unit/Crypt/HashTest.php b/tests/Unit/Crypt/HashTest.php index 9301e671..1d45c08b 100644 --- a/tests/Unit/Crypt/HashTest.php +++ b/tests/Unit/Crypt/HashTest.php @@ -375,7 +375,7 @@ class Unit_Crypt_HashTest extends PhpseclibTestCase public function testGetLengthKnown($algorithm, $length) { $hash = new Hash($algorithm); - $this->assertSame($hash->getLength(), $length); + $this->assertSame($hash->getLengthInBytes(), $length); } public function lengths() diff --git a/tests/Unit/Crypt/RSA/CreateKeyTest.php b/tests/Unit/Crypt/RSA/CreateKeyTest.php index e7478be3..67ed2d90 100644 --- a/tests/Unit/Crypt/RSA/CreateKeyTest.php +++ b/tests/Unit/Crypt/RSA/CreateKeyTest.php @@ -1,4 +1,5 @@ * @copyright 2015 Jim Wigginton @@ -6,6 +7,7 @@ */ use phpseclib\Crypt\RSA; +use phpseclib\Crypt\RSA\PKCS1; class Unit_Crypt_RSA_CreateKeyTest extends PhpseclibTestCase { @@ -16,6 +18,8 @@ class Unit_Crypt_RSA_CreateKeyTest extends PhpseclibTestCase $this->assertInstanceOf('\phpseclib\Crypt\RSA', $publickey); $this->assertNotEmpty("$privatekey"); $this->assertNotEmpty("$publickey"); + $this->assertSame($privatekey->getLength(), 768); + $this->assertSame($publickey->getLength(), 768); return array($publickey, $privatekey); } @@ -31,4 +35,31 @@ class Unit_Crypt_RSA_CreateKeyTest extends PhpseclibTestCase $plaintext = $privatekey->decrypt($ciphertext); $this->assertSame($plaintext, 'zzz'); } + + public function testMultiPrime() + { + RSA::setEngine(RSA::ENGINE_INTERNAL); + RSA::setSmallestPrime(256); + extract(RSA::createKey(1024)); + $this->assertInstanceOf('\phpseclib\Crypt\RSA', $privatekey); + $this->assertInstanceOf('\phpseclib\Crypt\RSA', $publickey); + $privatekey->setPrivateKeyFormat('PKCS1'); + $this->assertNotEmpty("$privatekey"); + $this->assertNotEmpty("$publickey"); + $this->assertSame($privatekey->getLength(), 1024); + $this->assertSame($publickey->getLength(), 1024); + $r = PKCS1::load("$privatekey"); + $this->assertCount(4, $r['primes']); + // the last prime number could be slightly over. eg. 99 * 99 == 9801 but 10 * 10 = 100. the more numbers you're + // multiplying the less certain you are to have each of them multiply to an n-bit number + foreach (array_slice($r['primes'], 0, 3) as $i => $prime) { + $this->assertSame($prime->getLength(), 256); + } + + $rsa = new RSA(); + $rsa->load($privatekey->getPrivateKey()); + $signature = $rsa->sign('zzz'); + $rsa->load($rsa->getPublicKey()); + $this->assertTrue($rsa->verify('zzz', $signature)); + } } diff --git a/tests/Unit/Crypt/RSA/LoadKeyTest.php b/tests/Unit/Crypt/RSA/LoadKeyTest.php index 02aae609..372ecf79 100644 --- a/tests/Unit/Crypt/RSA/LoadKeyTest.php +++ b/tests/Unit/Crypt/RSA/LoadKeyTest.php @@ -7,6 +7,7 @@ use phpseclib\Crypt\RSA; use phpseclib\Crypt\RSA\PKCS1; +use phpseclib\Crypt\RSA\PKCS8; use phpseclib\Crypt\RSA\PuTTY; use phpseclib\Math\BigInteger; @@ -376,6 +377,8 @@ Private-MAC: 03e2cb74e1d67652fbad063d2ed0478f31bdf256 $key = preg_replace('#(?assertTrue($rsa->load($key)); + $rsa->setPrivateKeyFormat('PKCS1'); + PKCS1::setEncryptionAlgorithm('AES-256-CBC'); $rsa->setPassword('demo'); @@ -554,4 +557,368 @@ AAIBAAIBAAIBAAIBAA== $rsa->sign('zzzz', RSA::PADDING_PKCS1); } + + public function pkcs8tester($key, $pass) + { + $rsa = new RSA(); + $rsa->setPassword($pass); + $rsa->load($key); + $r = PKCS8::load($key, $pass); + PKCS8::setEncryptionAlgorithm($r['meta']['algorithm']); + if (isset($r['meta']['cipher'])) { + PKCS8::setEncryptionScheme($r['meta']['cipher']); + } + if (isset($r['meta']['prf'])) { + PKCS8::setPRF($r['meta']['prf']); + } + $newkey = "$rsa"; + + $r2 = PKCS8::load($newkey, $pass); + $this->assertSame($r['meta']['algorithm'], $r2['meta']['algorithm']); + if (isset($r['meta']['cipher']) || isset($r2['meta']['cipher'])) { + $this->assertSame($r['meta']['cipher'], $r2['meta']['cipher']); + } + if (isset($r['meta']['prf']) || isset($r2['meta']['prf'])) { + $this->assertSame($r['meta']['prf'], $r2['meta']['prf']); + } + + $rsa2 = new RSA(); + $rsa2->setPassword($pass); + $rsa2->load($newkey); + + // comparing $key to $newkey won't work since phpseclib randomly generates IV's and salt's + // so we'll strip the encryption + + $rsa->setPassword(); + $rsa2->setPassword(); + $this->assertSame("$rsa", "$rsa2"); + } + + public function testPKCS8AES256CBC() + { + // openssl pkcs8 -in private.pem -topk8 -v2 aes-256-cbc -v2prf hmacWithSHA256 -out enckey.pem + + // EncryptionAlgorithm: id-PBES2 + // EncryptionScheme: aes256-CBC-PAD + // PRF: id-hmacWithSHA256 (default) + + $key = '-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIIU53ox17kUkCAggA +MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBATi8ZER9juR41S2c35WXTQBIIE +0K98r5dQq/OxbwA2CH0ENs9Jw2qjvW0uGkH8DdO8XvCJohMrIU8FABxw/50Af5Ew +Nq4FJIYz90LjZzlI7kf97TDMZKw2K3AleymwmfMcKer5pZ6jqdxLGXFztdj3Fm/S +P+NcVjEZFSEH1MNDEPhiPSIUAf1yQcLwKAzHH0JTnZBOFSBbGxTZLYfvD2angVNL +xTivLYJGdr1cUrAuZQcM3JGQEvCA5qAC7oRhdVgGyJrl8xXY3mVlaXMsW8A+Q7xj +NyH7lJUFEF3YPMbpWr8zblCQYgGByM++yOfYQXno50AgWdYjPO88pPzKcCe4x6WV +qKlvqTYZqb1HgZurTd3BS/e6GWRgnRt8W87nuNcyJasud92Z0FhSGwIirlE89gUW +EinbY8m6+sL9VZZ5+t66TROtpj1Ohj8t3W+01oLDCtdSTGwLuq9XUsEyuYZSqUN9 +0F43U8pOykNbChi1S8vfFdwf7U1R+hgoF0MRNDwh3hRfSS0zPUnCGb6hDZrOZB9C +e3xbfXiujVlfhRc7r2qbZHAwqNLcccC98oLfbEIUdBXn6M7GfFIwiuNiS48rehp0 +dA9+CiWJBq+7b/lRdcgQJxjwUpxtMXr/812Bky4dDoMDs32cmMghH2sgUvht0imy +ZhA3IvSCAV1wVoQLqUuPXLMskcKsNCTbL9AYEpJm612dm43btXec2vtjCc4ajpCg +wICLE2V1jwzWw0girrT/IMt8QUd3fkJZkEAbmFHwuZptFnreRCidZjfQqYhWfyqJ +nGW+cc7G1bGwxt32fC5eu23hBTJERmRlvkhC+v2WKhYXcKyOKQn5/I4eaEZauDn3 +wmg3f4h/PPuQgqv/vspOai9a5HhPRNyeIjXsk3hxHepEgV+kVSU50BpchSSzBuhK +71F3nOMTyJ/XXxaZrLLtpo3CcXmI0/JuNG7pjDS++Vx/BQFs8xxDfxRs0Um7RlT1 +piGZGDn9zHNpbspHkAeoQmlplbmjtCClojhfBj4HbXTtlYmDgwKHul4YIni6kgCr +G+WduGXLeyxmH976vvJasD4wyttL2CZTHLR7Elp+yl0xjXMlj/iP4WYozJAmGifq +xjLWMsZ0gaBtAoOFrvcgOueE7+E+NdbIHzU4u5FTbz0DLCvrsZeKwpOPEsMw0LVG +T6rNsBzMY3XyBtV1FdXwmuOcWha62Ezr/RRrfvRPRImy/xVVKOrOQ/KbyELkjroh +UAEPs7s+89Ovc7P30IfS0Xzlhz2aSRflZarOIqu1JtjTYZ0XWLTWoQT2fjZdnMDV +qFrbTPdXezqTAAzk3rnkkghgamTVQ7Y8D+BIGHIc4+oVT2jxzSjBQC7szmudanGQ +hfGLyO+vwLg4r1lanzSULtqfwTZMarjYGxLqpQp8cIjJfzvLI3psRDFyuWCdIbEs +y3VKgoNsa+PmyimGSa7x2cw6ayTx9wlOhPzaBwqMhHxr4qJwS2ohDONeRfnPr34+ +oVD7mnCBLB14qiZcpQv+qPGvd/Q/tA5SBNbZhPuWtjqvy7/K+1FQX6xvx1kl7p9W +l0Q99rwqECl8y+CiKEXdItkCTA/vgxblSt465Mbdic7cbcP6wAMSGmpryrmZomm/ +mKVKf5kPx2aR2W2KAcgw3TJIu1QX7N+l3kFrf9Owtz1a +-----END ENCRYPTED PRIVATE KEY-----'; + $pass = 'asdf'; + + $this->pkcs8tester($key, $pass); + } + + public function testPKCS8RC2CBC() + { + // openssl pkcs8 -in private.pem -topk8 -v2 rc2 -out enckey.pem + + // EncryptionAlgorithm: id-PBES2 + // EncryptionScheme: rc2CBC + // PRF: id-hmacWithSHA1 + + $key = '-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFFjBIBgkqhkiG9w0BBQ0wOzAeBgkqhkiG9w0BBQwwEQQI/V5Qw9+hKt4CAggA +AgEQMBkGCCqGSIb3DQMCMA0CAToECPxrtS4U+IIBBIIEyKQyYpJ8tfWXVvitxaPq ++gtrVVWd/ukjwZ+jQY3g/ZjZNWQPq5XbuoP5F3u5g4V+RoXzAIkdwyiveEv+XssV +DJVHfNiL6VcdxhFJ1rmt2uq9vFW/x9UHDqAWsnytn46NFRWUqgKzkYWMDqU71IC/ +wyq7UzjTtqdLzaCxkTWYst1o+Iu7VXapFVcscPYyGshLVyZ0x/etc/09LOC4bIpk +3Qzf+f+adrNxW0mbD3SyDfVadvS8mApsd7bJR320iEKd4CmW0sNAzKkm2ya2aUIi +Hrk3DEgr4rPmpn3BVfZ6pg+yRu+MOxBhl+8yfA+E8kXfe/F7BiMkJQcJTOfLRLfH +TXipyb4f8oa+gmwwWK0jfCuxoxiOTA1CBCjZoTvdSuFYVTdblysQO3BivvSQgbmD +oHntb7HEoZ6yB49u/LrrowUQNH+XihBcototyLCmC5K+x8N5cZsp+yaLJekDHlQs +ATVMeKCbPjYaS4g48lDyC1VbtNtJc/zN5gOUB0PM80iB02iZegYyeW5+WWzY+Lgu +lpWLH7PdpqL5KtoH6SJKD6Szl8dKJLYzpHI2esckpp9YsDtX2z/VkUFKTd0PeeNh +WefX0q8A47NBeBLFEZqmzPrL6IyaPnnPCUsvqk6MEA1DgsmY3DFd8nEYhzJIAwoy +Rw1mCqwL0uukQPqFGByU9YRHyhJd5aAPyF1xSLfUQUJb9xn+wyN57xoamFePPWMi +UXdESZWX+rjA0ChfEtL9AzXcfO9PBS1p/2JkVxUt/UPfI9SgQn92kLo0LRi/iRLk +H4zjnkaDy65ZY15bzyK+EvJ+VZ+P24QI7X12f1m+rkssMekHWHf5/SitUpW26ZFe +M6vXyz3RlXxow+0WcsPob19n/vbgeJQPTfMY0zPS0iCRIggC/liWMEOzP/R1jCYi +q8TEaUi1Ztx3Gp4Y8Vcf33a/YsxKoUsQlFFtyE6KE3ZEI03E6cMiX21nWULKrk9l ++8Tq4T1a8I4goVa+e4CYBYwMAY9fdfUJ/p1EnrG6Ynj06a2Zx0IK/dF5w0b/5TeL +PMyafb4FHkpkyYYFlktQdKIqGjjtmKUr56/7vumVHUyItf5nSuM8lLps8to2MLkE +MAolD+X4FIGs/1Z5NlUb5AlNVNRY1c7tf+YSXI21PlkBpaRSAvN9/2fmGnxWSvAa +BEGR0JA4zMPrCSpxrBQpOrZPh/cD9YXNu+N9P4dtf57smCviKTJg8eMl9NYu4vtc +FygdqPKuhJM2WI5Qdrqjbf4NQ1mngSxXNKrcNmC/m60JPNKHC7dM2ynbN8QyZqEE +EzSdL0Z3YQGnuwr+4zKHHsNnO4nRJfUowWks4Gvi2HIyy3DBVqqyEPxDDGEpcqs+ +8GNKTGBg1PCVg+I9Xjxio4tBuwLDo6Y8Ef8SphN/0DC9svaQRfEOY3/9WB+fDnrq +SUSNZNWetkCd247WHwl+JvJDXCuzGJ2+JG5DXuEdCq2EhEVNUWPuotXTPvI+0wsP +Kq23uvzS53ZArQnxlqgwyXQ06jzc+J4AiNtl3uIw8D6LrRyaDsOsKQCEh7qjkqTc +khzefbnNRDL5PIJnTfM7vSQ4nUzdAxs/7YzX6GMx1DaCtBANbUVUoIE+3oKdqpGV +9AmO2phYWCBefw== +-----END ENCRYPTED PRIVATE KEY-----'; + $pass = 'asdf'; + + $this->pkcs8tester($key, $pass); + } + + public function testPKCS83DES() + { + // openssl pkcs8 -in private.pem -topk8 -v1 PBE-SHA1-3DES -out enckey.pem + + // EncryptionAlgorithm: pbeWithSHAAnd3-KeyTripleDES-CBC + + $key = '-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIE6jAcBgoqhkiG9w0BDAEDMA4ECHwurC0qxNK7AgIIAASCBMjRJefQ1oLo/pml +Zw1qTE2NMgSNdP6z3vEap0qMMs/EXO37GDuHGla/yvvBIZbBXPVoQwd7K9QfU1NP +JCBtNBTD9Pl7a4fJIlrf2dN5SCP8lu+nFa5ZyCiBvUtxfdROnhXfkhs0kqOowLaq +1mw+Di9FPSA3ZkdJTPpAyMNPMlYpQII2ex+j6NmRB8t7O121o6ynbmDj0Rh87dv9 +VtRhO9sonTy160Mv2HPMLMliXMwFUvpEH9XNE+K6V6DnoMc7I9jmEnqLAzmjsKv/ +AOwuX8t8cPboeGOu++/0h3879+OVcnkXGMW0aAT+3sX4oMgEVREHDwkn2IGsrIuV +SerUKg8WSoyhRNb9j7uJAlvU6bCrivcOujjNasdWKG47ojeiySFUkKu9JBohQ+vI +mrlkqZv+FMwEKgApPhCKbQYqLuKl/kp6lNBXmhcusuxsGCnaw7/Wa+Y6p+Gz6UL5 +caFpDm6FX+Snvi5/6sUpMKL9LPAAZZVRpKj9JWcidEoXa5rINIMtKyVpl+GEQmac +9lCdFq+5zO2r94af9AKRUIqTquyCkcy2s2mzNq2IIv2atibnb2HQex0/EhLFxMC/ +UZbl61YaSBxrH52LY4SNOUy+ppCsP4z0ojTdci9Yc3BnMqzSPD3FMQPmGpWWRGOq +Jdj2/B76Q7rYZjIdrh6UbSROrgTNgmbeASxfDSHHmmcZyIUtMzBC45bmz3ra/FO0 +eb+6srXOmdIG368/xRdo9o1R/cNw9BHgGu5R/Bx+AxhK8DuhL7rMYLVn3Ukl2qrt +0koCtDbPxq2YgF8VYXz4WNRCmLuUroty99WVOM7BvM3XyfSP5iLynoRr8B7Rju5K +t5o/OJUrNqSNjtzYd3PZEXqi7ShWkYp5BACzBfSxkGfL6bBMfoM8Yd3dBLrplRu8 +WpVbJSA3DbJwAyGhKP3dQmmhBH9nJEppSK0iQPCruAyyZJT6kEmPhcNuYq8CyWe4 ++l9Hs5qIHMrkEq1BcLYiQFBLVLXR6eHf3J8fAMmz8I74TWNM2u3FZUcDoIwqHmHG +zDwYZ9h1tkijvyvbH5RkMWRb7WAB2b9Q9ZsPR0naQbqHmO73Uypu/pfugx+cmOPr +AiYOQHnwSCDcaTHJnO33L7KhgA+RfIRoigJXgeYzlWmjW/U6SRK8RTvda4lxPsOo +/bXTZOoUA8qTDKT02n20h0Ab6kLDApSigGQlYI8Jhfre/PGFWfrLxPpy4ED53sPg +xY1d6tIa18yQCAznC3k6Q9OK02bGaFTcGnTPg88PBgyUFuqljKGrG9rpm4uTPzwa +1vzKv05oYjK9xyzy6LkIPYHyp6R2tedVO34pa52LNCO3/DaLnwiDfMRW5SFu0J0E +P7/viLPTwwR6zdAAlt8hsT6apBYlLzVqGRy9nbN60ZS3Q8lIe6xR47koWAJvnHuS +uBx9xP3JWcs+Cnis85wODY1qxXa0Yx58kUVmoyyiBOWHcwsi82YtgwAAjhR8K1wy +gRJR72XIKmdZ/RINp7f2dN7xswy8iU2m3vBxgc1AH2/8knlGLebNS7/RJwW1KXsb +pp/6vHRPSla5cxzsF9NmYHmSAUpk1t+Mo+YjjoT63bVC4xNzkXdft4l4QyUQQXU2 +aENeUJKn2r8X3Tpz92U= +-----END ENCRYPTED PRIVATE KEY----- +'; + $pass = 'asdf'; + + $this->pkcs8tester($key, $pass); + } + + public function testPKCS82DES() + { + // openssl pkcs8 -in private.pem -topk8 -v1 PBE-SHA1-2DES -out enckey.pem + + // EncryptionAlgorithm: pbeWithSHAAnd2-KeyTripleDES-CBC + + $key = '-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIE6jAcBgoqhkiG9w0BDAEEMA4ECGT21lf3Tl4dAgIIAASCBMgJmnyaQktoSk8u +JyUAaB4ZgGWW22BX1xRA7en0sNj4PhBxj1DEXGKNUBx3k6u6Cd7JUupxGfeag5aw +fi0bWNgEw7YSITmZKaKO5Ee4shQrEDucaH6KEGYV6YspNys5dD817hmd4Yc31q2e +Ig5k3rIpP72Yy9Si0FXmKDE8/GmCYckdIQVUCGZD9nLugvqEC0adludfMAwzCHUY +68jeyKiPJGTFSmE0wPD5EaWknn3U0eRcmKwZPtFpWEAEv5GTm1h6Y5q+P2X2Qsia +Neoa4jjnSEww85zbno/k0KdoRIuSEM8qOHdNuU6XNTCGKxEkgBLkY+vjRjOCi+K4 +fzJSAPxaYATGX+W8EWegz3yhwiFujjDPkO9nfeoyks/saFbP9uT8aesZUn4/rIw0 +KyW7lYW0TUyBxfIXg1DEsKcmSrrb0WrFLN/MnjO8Y1bAY63KgKgpFZk+7H/7eCmD +2Egj6o2LXTEPkxwYyeR41k64LM5RFF5qs4wS0Gfo1oTc6lSbuZNFHSgsXkb+CXFL +JZ3CuaYFY5Ldfm+1HsrZ9s3GmNAnog6WABXIcz9aULUyJfLr+oZaQR7TC5KpM5Xy +dyztlsN43D9UZKdz93zW2V3LxbzbOWTrcd9dB1GwrPvWIy/0/dqFOvpcr9k/4S1T +AJ6pja4x19EQLj2DUvO7JQEy2Rlam+SI/ARQTc0W0dJ8x7FboHZDxUQCRDih5Qw3 +s/xoGflLUYYtAR5hfgjbWuvG3Told4IYlBn2vvVu7UxXQekUOaZLePqucAP3sTDC +pK7JK+OT223FNU5NieGS4hh+jxZQnLuuyxWQaTCJM9isYPqJYsWT9X+c44ixOgLJ +unYtg+8Lck3On6wiDUPWTLCGJvjb53NhPSovTjNBW2Q7YszXXjeO5svwwxtKHabx +vCDsG0zdNdwIgupqynbtcuUhsmIsJKBu5c+9i8P21rNF0DZjOkv8mThRA6YQLce8 +mLTcnpLsvCGNehVEStD6pr+CtGsQEEtH3bPc2ZBrpxtz1EHmrI7H3kX1gjbD7bsT +XWzaxsId+8pmqnAcMRzU3mRv4Fe+387X2irG4OxR/6cFMk3+yfpKJLSsNh6HAVRX +xzYwVz2WP7RM0KuLh7auAcI1mHk+0xAvDi7s3ggy3SzLzQq9p+EEFVGVSYuVFLbi +TtlY6HQ3b1Z6KgntoPj79YuOmri6/8w7nBkKt09faYLUf9wZWHLL9/LjZqoJxPfl +lX5Ss4+MDV1aG9aJoTT53d8Gn8ApWK+XFToFg2InYZzZqBnKP8DHPG8D2Gh/MZlA +Yt4hPDNLf733zm1zJTWo0TF6+4AwZp7XUKTg+pM1CZJDOTbJlEA+cXY3BxOq10bl +JPJmV/JFINqSeBLN8V2Ong8Q0Dt4uabSmlOUz19SXpimBrO8ztxaqigMFIKMbLXX +uIVAoxG7KLPuv44yK3Fjsg6OtwZnrWqea02b1qwFFrIKoqmQ8FNFBMYqcHU2IkSq +gJqSylfqcre5Y1DOSlcjGa7aP4C8AyB5qOG7LZ/CLAePKqgAHtMd/K40Zgku36Ir +9OAcUXy/H5PFJIleEyjvLLBE0VWs0TVBXi/FiIqwvAYNOFqXl/gtRcZ0kVex/QeN +rcONqwmUGJOjrfhUyJA= +-----END ENCRYPTED PRIVATE KEY----- +'; + $pass = 'asdf'; + + $this->pkcs8tester($key, $pass); + } + + public function testPKCS8RC2() + { + // openssl pkcs8 -in private.pem -topk8 -v1 PBE-SHA1-RC2-128 -out enckey.pem + + // EncryptionAlgorithm: pbeWithSHAAnd128BitRC2-CBC + + $key = '-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIE6jAcBgoqhkiG9w0BDAEFMA4ECAdC2l5rzAQeAgIIAASCBMjuEQDNkvSX6ylp +WsgQZUSvPdNpdlG084oLmhTV0z4pZeLB0YCyKCM7GMVQ0tsprRW0ky86ulbY3W5Q +86WNHXYtVIFXEmIjN1syRG5Pq3RZ4Ba6wf36Gc/1713p6GjcPxLZ+JOw1xBEm1rh +1nI9b43PzbKmczs+6IvRO5b9MjKNkBeNzH9kh4b3zsEW/IFgYaz8zip/zRu4hCSW +ORhnRYFvbI22E84g4/SB1WS34nR/flyZBXT4P87s7bwXEOsXAGnEeVF38znV3awD +V8rry2e1drRmlhfNhvDroQrkv7O9X2ee91I9gahPKpXtAlGXBgRb8qjVHeI8Ea3K +Ty2zcWnjdE7/jt6pO07+B+FlHNdKySlFdKTHEmJ1x+O5Ui4JaGjI2UML0yGHoYFU +wGH/1DYJ4+R9d97BYJ/yp9+JAQAjpG7UUt3jFgNx0CAbP7d438l06z2EE87EqIEa +3Y0ZG1Q5PWE60hPJsvUELdgzcUiKkVCxhOPhwmbSlQpEYXZRBWv0RAJzey6yPMQ+ +L/TkMDpgTNUk9x+n3ehnRuA/tlthxXN/ViDthPO7ovSVCsKsUq6lXotO+3hHLGs0 +u8ZyVKHNEqGso7PfAFsjcJq2C46mQNME4HPOWm5J+TFf/vvwqdYKCiF3arV0hUtW +x9lyPR2PvZM1ik9jXi6lc5hPegcwmx9/j5yc4/3rQNiwe4dtUpL5JLKAxecKBLgr +atyVnAs69JYaaUT9aKRDzYzCRjo0jIQ9/lgJl2DqVRF9aYnknrVWRIjyKbfKjw7N +w0yfXlVRw46YuJ72PSHpr7HcfzL1EWzmKcAEPDH/UCpIaoeNTwxeEQSltOM0D0Su +ZzyAQP8sWSXSwdtD5YD7iiUjDN4UMDuIwAEMLN9231/RjvKuLAys0oNRQfHkkiCo +9rt/VUP8be98UTyKu7Bx7JUEW6VVnYM+Y274MLQk6TcjZyXgbKwhHFJyAjcBAFQp +5kkYES0kk+57HeBImcB0a5qBor/uAnlsCV690roUlBtkVhBOkTjVi7w/uZsSjIWr +MBndNHTFqqnkbm8xOOoSH6vS32c1KE8FrGpmkPGc6wziX9Ja/MkuLDXrBIlnP4Yj +aCf0sVMSR1/LoHIGGaGXmTzs0VTR8Z5EyW5uvvCy6dWCWnTKEWmTTS+zqW1RYVBZ +n/P2ovj2Kl4rhQuSpfOE9xBFWsgPAD6T2FJzfvu0/D3Sw5pI/RT4NnQ77oJSs+jN +lV33FeceRoJqjcP6YMAiRX4RmkTeD5Hgy0YRLrfQ4PKwAQG82uIj3yqXWveexwb0 +Cpm5XxzgCMWGBRvIvM+yByf0SP7fIWYHbzsEWJkN5btF3tMc6i12q7AJ0/UFMQLt +KNiMg+dLWP18cySU8OysXqPq1JKHDU1NMg2Xf9o2c35eOktLfcO9axS4oAAz4bGN +hTB8rk+MWnHfSlWvMPMzlJyndXv/WxfojujLogDTOHd4/q4KyoOwJY3H44eeW79u +sS7pDbjKMl8A15BLMLx01DaOYk7EiHFnGIpY2V2+2Xm7vQu9+8fHSf8whuCRVmBY +Drhy2HTF1veKrQ6IrIyQicmzTtW6moSnNg69SpuzKTegYyCRsSHDIL3WxMoopVwr +Pu3ed6UvXDfotj3v8rE= +-----END ENCRYPTED PRIVATE KEY-----'; + $pass = 'asdf'; + + $this->pkcs8tester($key, $pass); + } + + public function testPKCS8RC240() + { + // openssl pkcs8 -in private.pem -topk8 -v1 PBE-SHA1-RC2-40 -out enckey.pem + + // EncryptionAlgorithm: pbeWithSHAAnd40BitRC2-CBC + + $key = '-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIE6jAcBgoqhkiG9w0BDAEGMA4ECHzZ2FqUJyiCAgIIAASCBMiKVNJiU/1UCaoq +V6VSX33gL8CjQqhzDEUlXhHoSYx7IWAJIx7C1DDgeLDfJ//cCMlBPbIOr6knohFQ +KegbsulIYm2DXUQvEoivh02F4An2RLkP0JSMl5CmYTbiu/nJic2jdin+vaKWhRA8 +Kznk5wEuhz4t6Oo4Hp3k3+0sd6YqLbmdcFCiYSXE52WN271VvUXqwz3TosUgOUVH +YNzlER1xzNBFgcUyrfiyA+t5NaSDfpYncD64zXFz2KgkjcpfPBBMRnQ2I4mperbe +3uUt8ZFCVeRiROWtx9WQgCVDDWztlrYzfEo/lFtNKjdseiYDj1/1gG6S2x5/S5dP +LzEndNYiVEB2q9XrocrdKVumr7EqNe7C/AMNxuyfAoU2QV5SRyDDSRa1muYwHBa7 +x2XIThMS2tQLdN9bjJGcT653DKoq0Qwf1uMAOdHuqBLNXOpZw8PG8d3xmVHCyB4u +rr5E48L2DmD0TwY3YBjfb3KCw/r/CvT1/cWkCpO90aNmSS4JMuOBiFlKaERMvXcw +Ffo0ErZWwlgDN40hQO7xySxI3Paz6/QbxXunVnFQkTclkcQG63K6nWO9fMtgxRaZ +ZGsv/jdWUZ6fBvOvW09zLvF2ZXKhTbfwU47C+2TefvENVz4rTAJcGgtF1wiFv1Lt +0XT2FeJ6/jVGhk6745cHgOhsxqamOTuQ/y948ViMmR037INHouazD8dWHkjOY45d +hy0DjRGIiig94/r+b7YZn81QUkk0HddyH4zi18f5Lx+ExiLDKaLqLv57PQ3ZCeBy +v6Hoq/5tpZWCdXkLIAHx/a7ltiJQlyRUr8QcKPcGfr/qvIcYsUocHZ3iwkhxCnZ9 +77E2f/Owf8VaS3x4g5V6RYNlkhuqVixLq/3QyphykcqDK2g4PnWfq8prGY97jHXN ++LZdwwV8LJkkoxCG1aehPlvtpGYGS75aeU1iFbqfke+gF2JG4LJZQsl1dAoL8R+X +ZdILuN+182CpwwptK4QmCvSWXk/ZJtYK9e6OtE1keLM4oQW122fxwyVkEnnAR2/f +Qc2U3WLk/UZSuhcHExxreWP4kiN8cYgSpw+k2uk7Xuw2kelu6hv6UzB4EtL8Xy4O +kK7y6EjCkRJVqQoZ3vVny5408edc7Kh6bz0P5G4LIaCpvrcCYOJv2f57/lr8dMM6 +XcoZ0YIXATdUzKzhXKNFiib7SzBwFRRD+jMwzLnNeUhss8IUVl5LYwD6sOWbhdep +LtUO2eXa74i6pa7sz1PLLWDZQ64f9fVPX7EEBy2LBVP3iLqLd2x/OHw+s7pTPT0G +EgRpW0+IYBZGQjGN9s1VXXyhTm3RL03KeYls7aHmmcVqDqvozarqplN/kAhSGPmD +N99FlSrIfihCEZlXO4iP2LRkJaFy11mU0ZvIMkDJ51fNO8PWypca4Rgi4azMCaiR +HW/dtSBH2N8St0G80c2gUKXRmmJWFqebUIk4000VrpDcZlg0INC2unY0NwRFDe2a +55/NR7TO9GdhWYDWsZedRatUFul5DWznncIfrXAD3T32gUR3I9zc62OFVsH2Dve3 +oMZUabvnj3g1sQqiRgJyIe5aVZljgXMh0cdWjcUi34vOVBU8yOU921/jSinJWyH7 +GxyNlS3BiKQ22CLvbjc= +-----END ENCRYPTED PRIVATE KEY-----'; + $pass = 'asdf'; + + $this->pkcs8tester($key, $pass); + } + + public function testPKCS8RC4() + { + // openssl pkcs8 -in private.pem -topk8 -v1 PBE-SHA1-RC4-128 -out enckey.pem + + // EncryptionAlgorithm: pbeWithSHAAnd128BitRC4 + + $key = '-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIE5DAcBgoqhkiG9w0BDAEBMA4ECKQOMmQxsqe0AgIIAASCBMJ86iav9sz/nsgC +KrPC1m7fPDuJF1BCPd6Yu+b+D4++4htITNBujK+Ur0xgQnfs7+Et8/cz521KR1zp +kalr56rqvPu9X45l86f/PYv6D+3660jxAadk1bZ9Y7nXzjXsFlZljEi/oLYSAKwh +rn88ZNBM7hwoEtEZJXOK7yZlcpfLuNyRJhfxRp893yeG3SHDN+SAKzqbjrGtPnJY +2X0Y/KidhYAYLi3onhxkq6I9aEI54oBZUiKLHhRD5/ASx8EeSPK2Ydj20PfDIRIk +t75Tlqn5eLC124xdO0rm/vrczIrzo+JaqLq8dO0T7PGrR7hv9OyFwM6ssfzl2MyT +Si4Yv3gBk6dUQ4lySj6XfscjEPwnUSjO3SMwAV0uBoBxDyeKg+58sT0e4Ow7k+6U +SFoqa2m+gBAjXzL8SaGfvjfy0ViBtgLycGrK80dp7k0L5pJAZou7WCPWlP+5+kIl +IprSGD1luOm1olQBSaQO+GkhQlMg4jK7cMKM2bRWyT2ibq8KZujlhWlcqXbbaqSh +nJdadTfAsaCf3/hK1fiFwwSyFbiPjIE1H+WS+JMcg826S7FzoZ3BzcEVbiHRsBXy +PS95ZM3v/HWOejEO44NEqnrwjyqBlSJXOK2WLOUWWlf6t8pdQEjA1xUfJARXqv16 +rQEXq2ZTGBOGeorwKLeUNgMQS7SfVP56Mmi23A3QQk7JNPXfkcrQscHu3mzesYKA ++ckJwDsyjnTwYWFXfDxfReKVA17YSV160oKCPhO7jIeiHO8azw7RKaaIaueKe5QU +boKWPAKeEfsDrSxtEYxy6hQ/45LYB+gUlAauaFlT4d0qMWQzt05Zs4ugUtx8SuI3 +hWB80fi8XEJajti/3JIg0+cDEmv9XtYQXpaFKR/gTHl1ReSscY/rNyiUc7t1dBsn +kAwMhk/7p/0MZdEpG0e3qQ9Fs2pELlShzORobM0HWd41d2BkW54W/TJ9ERJhMU9Q +NJ5KZDukkCdTIgvnPKJ/50byYVGtt8VBXDzMQsm5ex9yDkEmBLtc14z7UaGd7FCK +xmbcVfkf+h5GPuJqXiZv9RsOfV0eVXlNx7jQ8Pq3FM5EiL8Wtj1XB3+cpFkPREoC +lA9enCZNdjXPB4SSz4kF+UwWdaNS77SGXDq4NQRT/cu4mce+1VPjepEc1WzLw5m+ +aaHtJJCdLhaUJmYfaPGz4kg2CSdFCDjzDLOQCOwGtqAY6667ZOFb2VCukQR2aSfK +XJF4Br7UsKhtlZvRDZGLSdxS/6IPe3KgzInP+27kLpv5UcolD3GfuS9WZhfa7tlB +37n5nyGJCgVHufWRrYdKPI32Dn4R312/k+6X9zR1G4FlYzbuA+g2Q5CX8n5e/9jm +1WjCv8ppB3p3BjIv8iEAyfPShwe4uk2ohry+nY/pq7qYl3E7Y6vS1MOmRJT5jvBA +oCWQITjRu/d0xocYp6agkMEBgkyiqzLEW8PV2bziRZVGsYvC/4ky1MVERFO9jiSk +3L6Xllp1yB+Mw9y12bUhDAZNAqpkNtL0CJbLKh6fQ7x6l4d0t/QqpuKXPvF5l0wr ++Fb131STrh7fkiTT1glrra1UhJzz/KVOR+TG32GOSI0hOTqu4/gDQ/vUV0gh0SJw +OvndKFWbSnE= +-----END ENCRYPTED PRIVATE KEY-----'; + $pass = 'asdf'; + + $this->pkcs8tester($key, $pass); + } + + public function testPKCS8RC440() + { + // openssl pkcs8 -in private.pem -topk8 -v1 PBE-SHA1-RC4-40 -out enckey.pem + + // EncryptionAlgorithm: pbeWithSHAAnd40BitRC4 + + $key = '-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIE5DAcBgoqhkiG9w0BDAECMA4ECNrUHnZnezluAgIIAASCBMKm0lEML1hIdzcu +pglVZSwG8JyigX3xgHwnN4oAz6HYTNeatkV3xqGP23DP84e9En7mQTRxuQ9Tk8sN +NoEK2VsjYmrJtsbyTH21M0vPlAvFv/sIgxVYcF7jv58jHhLNnibPtDeMXZDjd4uq +vdppAGRxIr5z8XZ8ruAlsAB/xzcz1bIK8CwWH9NBOWLDe195/OYoUrgf9h+U2s8e +1rbq+U/woJZHD7+3RuPmtWTtrneY/NVTiU03OUUleys88eCSqZrQzR0faNxdVzTg +mhSrtkqwho9xxUFY6KqLOTF4BQufD+ZSNt9LN3EaFdvGcI1QWWDH11ne98oz3GUa +GCGhaADTOAAOdvXyv+6YRfj1VvtisUeiGjttFmaUHGOmHCCYoyVkbhsRq4QnbbCv +641ogRbuISBHwr+mzqjwTZXC5Laxsrn5EnCZ309vohq7l+g3M1Y9nzR8hOsiqTFu +7PPj1jYM8znYkVx/me+xnpB/d3Ot86K6NszbTaWk9cHr4qfkF+pu2kUYQ/26CUBE +y7DxYmhXnOBRGUTvQebrMoSK8hOaw0uWEQtYp3gLOS1hituL965m2qRbP/ysDP85 +DAorOSbKDEMHYy7UP3xh743FErEOoY83GtugnJgjrTlJ/5NyS1KFr5QUsQD/N/Zw +bIkjdFT5mjWVaotHzpNc1IigpAPbNpe4J1E1E2nB8YE5ckSEVseUJ+ypgWSvJxmU +G68YvidODClnBekff8sRDCNN4dekQgnNEMbAWgHRWtMERvXS/9xfJZiqiq+7WvIE +Xvu1Qq5zG3+mESNX9AVLngv5btD2m6QFEqOLG9JKQWp61J3c2lG/kdtWBjsXiWoi +zvkA4u9ZxUzX3s3T2aHozg9O4+0ti947l7wSIxbxLYA0d1M7cQoeKAuRnpwzfCZ9 +gpQ9VG9acDhU9LCxcZBHfuKROeI7D7wL//MJp/ue26uhOZY7Z0gbFIfjeSPW+HD0 +fRGA849/1aKIsRarKg2YleqsXO04E3J/lpTt1gjy3aGE25Arq6qo+4DRsUIIWeS+ +QwzdDeqy1zs8BIPxa51U/jvbqxCvqXsMw4la0txkSwymMvc6U+QpJgm2KqSDCs8W ++QYIz4SYlADLgl+MVDGd9IB/PN8AIZ0Lr7QqKKBIrfyegO/gjCkHCdNIh1Q/Bzbf +rq8AYwbxHnp2Jn2MAzw9s13ncENpZqCDHkhmd89hJc1B4f8rv5KhDsIVb85XQJek +pdpqugcYjxohSBEa9yzp0JDRa97Btir7D4+9HG2NUullFgXvbqKvlKPj+ORUDxJd +DMGC2Uov1koiVBvvahtmr8eTBNdA48cA7l/c5t8UsGbjrwpqLZLTJ1FHjnVKybuu +soPwPAxr3WBE4Ien7WqPj+GTeLWMb9//kpi5grguv3Db6rdH2Y4PT9Fi4UBxd+6N +LqB1rPkt4AQtQwda1ccixYXIFfWSJ6+XEyp6/wsW05DZAiu3R4o/T9Z59KPGlbf0 +aaEAW+FZ9jYa6sDBlMwCN2TEmnBFkytJYe8+B5UxkEAIn3g/Vr9R4t4YDCSE2ugs +q6YJC1bQ8jHojcWTs47zcefCXhOkKOg3oxzYIQe9Ikdmf70JxIo+bS92O2vrkV0p +OFLPBrLe4Hw= +-----END ENCRYPTED PRIVATE KEY-----'; + $pass = 'asdf'; + + $this->pkcs8tester($key, $pass); + } }