/** * Convert a public key to the appropriate format * * @access public * @param \phpseclib\Math\BigInteger $n * @param \phpseclib\Math\BigInteger $e * @return string */ static function savePublicKey(BigInteger $n, BigInteger $e) { $key = PKCS1::savePublicKey($n, $e); $key = ASN1::extractBER($key); return self::wrapPublicKey($key, '1.2.840.113549.1.1.1'); }
/** * 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] != "") { return false; } $public['publicKey'] = base64_encode(substr($public['publicKey'], 1)); return $public; } return false; }
/** * 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; }