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] != "") {
return false;
}
$public['publicKey'] = base64_encode(substr($public['publicKey'], 1));
return $public;
}
return false;
}