问题
Using Microsoft CryptoAPI, I've generated a new RSA key pair, and am now trying to export the private key to a PKCS#8 encrypted (password-protected) PEM file.
I first investigated CryptExportPKCS8() and CryptExportPKCS8Ex(), but the former doesn't support encrypting the key, and the latter is not exported by crypt32.dll. MSDN says that both functions have been deprecated anyway.
My current attempt is to pass a session key derived from the password to CryptExportKey():
HCRYPTPROV provider;
BOOL result = CryptAcquireContext(&provider, CONTAINER_NAME, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_NEWKEYSET | CRYPT_SILENT);
HCRYPTKEY keyPair;
result = CryptGenKey(provider, CALG_RSA_KEYX, (2048 << 16) | CRYPT_EXPORTABLE, &keyPair);
HCRYPTHASH hash;
result = CryptCreateHash(provider, CALG_SHA1, 0, 0, &hash);
const char *password = "password";
result = CryptHashData(hash, (const BYTE *)password, strlen(password), 0);
HCRYPTKEY sessionKey;
result = CryptDeriveKey(provider, CALG_3DES, hash, CRYPT_EXPORTABLE, &sessionKey);
DWORD blobSize;
result = CryptExportKey(keyPair, sessionKey, PRIVATEKEYBLOB, 0, NULL, &blobSize);
BYTE *blobBytes = new BYTE[blobSize];
result = CryptExportKey(keyPair, sessionKey, PRIVATEKEYBLOB, 0, blobBytes, &blobSize);
DWORD derSize;
// This throws "First-chance exception ... Access violation reading ..." and returns FALSE
result = CryptEncodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, PKCS_RSA_PRIVATE_KEY, blobBytes, 0, NULL, NULL, &derSize);
// error is 3221225477 (0xC0000005)
DWORD error = GetLastError();
BYTE *derBytes = new BYTE[derSize];
result = CryptEncodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, PKCS_RSA_PRIVATE_KEY, blobBytes, 0, NULL, derBytes, &derSize);
// ... CryptBinaryToString() to convert to PEM
// ... Write PEM to file
All calls succeed up until the commented CryptEncodeObjectEx().
If I don't pass the session key to CryptExportKey() then I can successfully use CryptEncodeObjectEx() to encode the private key, but obviously it is then plain-text.
How can I export a password-protected private key? Is there something wrong with the way I derive the session key? Is PKCS_RSA_PRIVATE_KEY the wrong encoding type?
I've been testing in Visual Studio 2013 on Windows 7.
回答1:
PKCS_RSA_PRIVATE_KEY used only when private key blob not encrypted. when it encrypted you must use PKCS_ENCRYPTED_PRIVATE_KEY_INFO. example of working code
BOOL expKey(PCSTR password)
{
BOOL fOk = FALSE;
HCRYPTPROV hProv;
if (CryptAcquireContext(&hProv, 0, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
{
HCRYPTKEY hKey, hExpKey;
HCRYPTHASH hHash;
BOOL f = FALSE;
if (CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hHash))
{
if (CryptHashData(hHash, (PBYTE)password, (ULONG)strlen(password), 0))
{
f = CryptDeriveKey(hProv, CALG_3DES, hHash, 0, &hExpKey);
}
CryptDestroyHash(hHash);
}
if (f)
{
if (CryptGenKey(hProv, CALG_RSA_KEYX, RSA1024BIT_KEY*2|CRYPT_EXPORTABLE, &hKey))
{
CRYPT_ENCRYPTED_PRIVATE_KEY_INFO cepki = {{ szOID_RSA_DES_EDE3_CBC}};
if (
CryptExportKey(hKey, hExpKey, PRIVATEKEYBLOB, 0, 0, &cepki.EncryptedPrivateKey.cbData) &&
CryptExportKey(hKey, hExpKey, PRIVATEKEYBLOB, 0, cepki.EncryptedPrivateKey.pbData = (PBYTE)alloca(cepki.EncryptedPrivateKey.cbData), &cepki.EncryptedPrivateKey.cbData)
)
{
ULONG cb;
PVOID pvEncoded;
if (CryptEncodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, PKCS_ENCRYPTED_PRIVATE_KEY_INFO, &cepki, CRYPT_ENCODE_ALLOC_FLAG, 0, &pvEncoded, &(cb = sizeof(PVOID))))
{
fOk = TRUE;
LocalFree(pvEncoded);
}
}
}
CryptDestroyKey(hExpKey);
}
CryptReleaseContext(hProv, 0);
}
return fOk;
}
来源:https://stackoverflow.com/questions/37717931/how-to-export-a-password-protected-private-key-using-ms-cryptoapi