/**
* 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) == "0") {
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 "*†H†÷\r":
// rsaEncryption
break;
case "*†H†÷\r":
// 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;
}