如何使用 CryptoApi 导入 PKCS#8

How to import a PKCS#8 with CryptoApi

我有一个 PKCS#8 密钥,我拼命尝试导入 CryptoAPI 但没有成功。我有:

-----BEGIN PRIVATE KEY-----
<privatekey>
-----END PRIVATE KEY-----

包含这个:

Private Key algo RSA
 Private Format  PKCS#8
 ASN1 Dump
RSA Private CRT Key [.....]
            modulus: .....
    public exponent: .....

我尝试像这样导入密钥:

 if not CryptStringToBinaryA(
           PansiChar(aBase64PrivateKey), // pszString: LPCSTR;
           length(aBase64PrivateKey), // cchString: DWORD;
           CRYPT_STRING_BASE64HEADER, // dwFlags: DWORD;
           nil, // pbBinary: pByte;
           @cbPrivKey, // pcbBinary: PDWORD;
           nil, // pdwSkip: PDWORD;
           nil) then raiseLastOsError; // pdwFlags: PDWORD
  setlength(pPrivKey, cbPrivKey);
  if not CryptStringToBinaryA(
           PansiChar(aBase64PrivateKey), // pszString: LPCSTR;
           length(aBase64PrivateKey), // cchString: DWORD;
           CRYPT_STRING_BASE64HEADER, // dwFlags: DWORD;
           @pPrivKey[0], // pbBinary: pByte;
           @cbPrivKey, // pcbBinary: PDWORD;
           nil, // pdwSkip: PDWORD;
           nil) then raiseLastOsError; // pdwFlags: PDWORD

  //init pKeyBlob
  if not CryptDecodeObjectEx(
           X509_ASN_ENCODING or PKCS_7_ASN_ENCODING, // dwCertEncodingType: DWORD;
           PKCS_PRIVATE_KEY_INFO, // lpszStructType: LPCSTR;
           @pPrivKey[0], // const pbEncoded: PBYTE;
           cbPrivKey, // cbEncoded: DWORD;
           0, // dwFlags: DWORD;
           nil, // pDecodePara: PCRYPT_DECODE_PARA;
           nil, // pvStructInfo: Pointer;
           @cbKeyBlob) then raiseLastOsError; // pcbStructInfo: PDWORD
  setlength(pKeyBlob, cbKeyBlob);
  if not CryptDecodeObjectEx(
           X509_ASN_ENCODING or PKCS_7_ASN_ENCODING, // dwCertEncodingType: DWORD;
           PKCS_PRIVATE_KEY_INFO, // lpszStructType: LPCSTR;
           @pPrivKey[0], // const pbEncoded: PBYTE;
           cbPrivKey, // cbEncoded: DWORD;
           0, // dwFlags: DWORD;
           nil, // pDecodePara: PCRYPT_DECODE_PARA;
           @pKeyBlob[0], // pvStructInfo: Pointer;
           @cbKeyBlob) then raiseLastOsError; // pcbStructInfo: PDWORD

  //acquire a handle to a particular key container
  if (not CryptAcquireContextA(@hProv, // phProv: PHCRYPTPROV;
                               nil, // pszContainer: PAnsiChar;
                               nil, // pszProvider: PAnsiChar;
                               PROV_RSA_AES, // dwProvType: DWORD;
                               CRYPT_VERIFYCONTEXT)) then raiselastOsError; // dwFlags: DWORD
  try

    // Now import the key.
    if not CryptImportKey(hProv, // hProv: HCRYPTPROV;
                          @pKeyBlob[0], // const pbData: PBYTE;
                          cbKeyBlob, // dwDataLen: DWORD;
                          0, // hPubKey: HCRYPTKEY;
                          0, // dwFlags: DWORD;
                          @hRSAKey) then raiseLastOsError; // phKey: PHCRYPTKEY

CryptImportKey 失败并显示 “提供商版本错误”,我猜是因为它正在等待 PKCS#1 密钥。如何导入 PKCS#8 密钥?

用于将 PKCS #8 格式转换为 legacyCNG 加密 api 需要几个步骤。

首先需要通过 CryptStringToBinaryACRYPT_STRING_BASE64HEADER

将字符串转换为二进制

比用 PKCS_PRIVATE_KEY_INFO (if private key encrypted need use PKCS_ENCRYPTED_PRIVATE_KEY_INFO 调用 CryptDecodeObjectEx )

再次 调用 CryptDecodeObjectExPKCS_RSA_PRIVATE_KEY 用于旧加密 api 和 CNG_RSA_PUBLIC_KEY_BLOB 用于 CNG.

现在我们可以调用 BCryptImportKeyPairCryptImportKey

用于导入 public 密钥需要使用 X509_PUBLIC_KEY_INFO,对于来自证书的 public 密钥 - X509_CERT_TO_BE_SIGNED(+ CNG_RSA_PUBLIC_KEY_BLOB 对于 CNG ,旧版可以使用 CryptImportPublicKeyInfo)

code 旧版

inline ULONG BOOL_TO_ERROR(BOOL f)
{
    return f ? NOERROR : GetLastError();
}

ULONG CryptImportPublicKey(_Out_ HCRYPTKEY *phKey, 
                           _In_ HCRYPTPROV hProv,
                           _In_ PCUCHAR pbKeyOrCert, 
                           _In_ ULONG cbKeyOrCert, 
                           _In_ bool bCert)
{
    ULONG cb;

    union {
        PVOID pvStructInfo;
        PCERT_INFO pCertInfo;
        PCERT_PUBLIC_KEY_INFO PublicKeyInfo;
    };

    ULONG dwError = BOOL_TO_ERROR(CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 
        bCert ? X509_CERT_TO_BE_SIGNED : X509_PUBLIC_KEY_INFO, 
        pbKeyOrCert, cbKeyOrCert, CRYPT_DECODE_ALLOC_FLAG|CRYPT_DECODE_NOCOPY_FLAG, 0, &pvStructInfo, &cb));

    if (dwError == NOERROR)
    {
        PVOID pv = pvStructInfo;

        if (bCert)
        {
            PublicKeyInfo = &pCertInfo->SubjectPublicKeyInfo;
        }

        dwError = BOOL_TO_ERROR(CryptImportPublicKeyInfo(hProv, 
            X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, PublicKeyInfo, phKey));

        LocalFree(pv);
    }

    return dwError;
}

ULONG CryptImportPrivateKey(_Out_ HCRYPTKEY* phKey, 
                            _In_ HCRYPTPROV hProv, 
                            _In_ PCUCHAR pbKey, 
                            _In_ ULONG cbKey)
{
    ULONG cb;
    PCRYPT_PRIVATE_KEY_INFO PrivateKeyInfo;

    ULONG dwError = BOOL_TO_ERROR(CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, PKCS_PRIVATE_KEY_INFO, 
        pbKey, cbKey, CRYPT_DECODE_ALLOC_FLAG|CRYPT_DECODE_NOCOPY_FLAG, 0, (void**)&PrivateKeyInfo, &cb));

    if (dwError == NOERROR)
    {
        PUBLICKEYSTRUC* ppks;  

        dwError = BOOL_TO_ERROR(CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 
            PKCS_RSA_PRIVATE_KEY, PrivateKeyInfo->PrivateKey.pbData, PrivateKeyInfo->PrivateKey.cbData, 
            CRYPT_DECODE_ALLOC_FLAG, 0, (void**)&ppks, &cb));

        LocalFree(PrivateKeyInfo);

        if (dwError == NOERROR)
        {
            dwError = BOOL_TO_ERROR(CryptImportKey(hProv, (PUCHAR)ppks, cb, 0, CRYPT_EXPORTABLE, phKey));
            LocalFree(ppks);
        }
    }

    return dwError;
}

enum BLOB_TYPE { bt_priv, bt_pub, bt_cert };

ULONG CryptImportKey(_Out_ HCRYPTKEY *phKey, 
                     _In_ HCRYPTPROV hProv,
                     _In_ BLOB_TYPE bt, 
                     _In_ PCSTR szKey, 
                     _In_ ULONG cchKey)
{
    PUCHAR pbKey = 0;
    ULONG cbKey = 0;
    ULONG dwError;

    while (CryptStringToBinaryA(szKey, cchKey, CRYPT_STRING_BASE64HEADER, pbKey, &cbKey, 0, 0))
    {
        if (pbKey)
        {
            switch (bt)
            {
            case bt_priv:
                dwError = CryptImportPrivateKey(phKey, hProv, pbKey, cbKey);
                break;
            case bt_pub:
                dwError = CryptImportPublicKey(phKey, hProv, pbKey, cbKey, false);
                break;
            case bt_cert:
                dwError = CryptImportPublicKey(phKey, hProv, pbKey, cbKey, true);
                break;
            default: dwError = ERROR_INVALID_PARAMETER;
            }

            _freea(pbKey);

            return dwError;
        }

        if (!(pbKey = (PUCHAR)_malloca(cbKey)))
        {
            break;
        }
    }

    dwError = GetLastError();

    if (pbKey) _freea(pbKey);

    return dwError;
}

void DoLegacyTest(_In_ PCSTR szToBeSigned, 
                  _In_ PCSTR szPrivateKey, 
                  _In_ ULONG cchPrivateKey,
                  _In_ PCSTR szPublicKeyOrCert, 
                  _In_ ULONG cchPublicKeyOrCert,
                  _In_ bool bCert)
{
    HCRYPTPROV hProv;
    if (CryptAcquireContextW(&hProv, 0, MS_ENH_RSA_AES_PROV, PROV_RSA_AES, CRYPT_VERIFYCONTEXT))
    {
        HCRYPTKEY hKey;
        HCRYPTHASH hHash;

        if (CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hHash))
        {
            if (CryptHashData(hHash, (PUCHAR)szToBeSigned, (ULONG)strlen(szToBeSigned), 0))
            {
                PUCHAR pbSignature = 0;
                ULONG cbSignature = 0;
                BOOL fOk = false;

                if (NOERROR == CryptImportKey(&hKey, hProv, bt_priv, szPrivateKey, cchPrivateKey))
                {
                    ULONG dwKeySpec, cb; 

                    if (CryptGetKeyParam(hKey, KP_ALGID, (PUCHAR)&dwKeySpec, &(cb = sizeof(dwKeySpec)), 0))
                    {
                        switch (dwKeySpec)
                        {
                        case CALG_RSA_KEYX:
                            dwKeySpec = AT_KEYEXCHANGE;
                            break;
                        case CALG_RSA_SIGN:
                            dwKeySpec = AT_SIGNATURE;
                            break;
                        default: dwKeySpec = 0;
                        }

                        if (CryptGetKeyParam(hKey, KP_BLOCKLEN, (PUCHAR)&cbSignature, &(cb = sizeof(cbSignature)), 0))
                        {
                            pbSignature = (PUCHAR)alloca(cbSignature >>= 3);

                            fOk = CryptSignHashW(hHash, dwKeySpec, 0, 0, pbSignature, &cbSignature);
                        }
                    }

                    CryptDestroyKey(hKey);
                }

                if (fOk)
                {
                    if (NOERROR == CryptImportKey(&hKey, hProv, bCert ? bt_cert : bt_pub, szPublicKeyOrCert, cchPublicKeyOrCert))
                    {
                        if (!CryptVerifySignatureW(hHash, pbSignature, cbSignature, hKey, 0, 0))
                        {
                            __debugbreak();
                        }
                        CryptDestroyKey(hKey);
                    }
                }
            }

            CryptDestroyHash(hHash);
        }

        CryptReleaseContext(hProv, 0);
    }
}

code 对于 CNG

inline ULONG BOOL_TO_ERROR(BOOL f)
{
    return f ? NOERROR : GetLastError();
}

NTSTATUS openssl_verify(_In_ BCRYPT_KEY_HANDLE hKey,
                        _In_ PCUCHAR pbToBeSigned, 
                        _In_ ULONG cbToBeSigned,
                        _In_ PCUCHAR pbSignature, 
                        _In_ ULONG cbSignature,
                        _In_ PCWSTR pszAlgId)
{
    BCRYPT_ALG_HANDLE hAlgorithm;

    NTSTATUS status = BCryptOpenAlgorithmProvider(&hAlgorithm, pszAlgId, 0, 0);

    if (0 <= status)
    {
        BCRYPT_HASH_HANDLE hHash = 0;

        ULONG HashBlockLength, cb;

        0 <= (status = BCryptGetProperty(hAlgorithm, BCRYPT_HASH_LENGTH, (PUCHAR)&HashBlockLength, sizeof(ULONG), &cb, 0)) &&
            0 <= (status = BCryptCreateHash(hAlgorithm, &hHash, 0, 0, 0, 0, 0));

        BCryptCloseAlgorithmProvider(hAlgorithm, 0);

        if (0 <= status)
        {
            PUCHAR pbHash = (PUCHAR)alloca(HashBlockLength);

            0 <= (status = BCryptHashData(hHash, const_cast<PUCHAR>(pbToBeSigned), cbToBeSigned, 0)) &&
                0 <= (status = BCryptFinishHash(hHash, pbHash, HashBlockLength, 0));

            BCryptDestroyHash(hHash);

            if (0 <= status)
            {
                BCRYPT_PKCS1_PADDING_INFO pi = { pszAlgId };

                status = BCryptVerifySignature(hKey, &pi, pbHash, HashBlockLength, 
                    const_cast<PUCHAR>(pbSignature), cbSignature, BCRYPT_PAD_PKCS1);
            }
        }
    }

    return status;
}

inline NTSTATUS openssl_verify(_In_ BCRYPT_KEY_HANDLE hKey,
                               _In_ PCSTR szToBeSigned,
                               _In_ PCUCHAR pbSignature, 
                               _In_ ULONG cbSignature,
                               _In_ PCWSTR pszAlgId)
{
    return openssl_verify(hKey, (PCUCHAR)szToBeSigned, (ULONG)strlen(szToBeSigned), pbSignature, cbSignature, pszAlgId);
}

NTSTATUS openssl_sign(_In_ BCRYPT_KEY_HANDLE hKey,
                      _In_ PCUCHAR pbToBeSigned, 
                      _In_ ULONG cbToBeSigned,
                      _Out_ PUCHAR pbSignature, 
                      _Inout_ PULONG pcbSignature,
                      _In_ PCWSTR pszAlgId)
{
    BCRYPT_ALG_HANDLE hAlgorithm;

    NTSTATUS status = BCryptOpenAlgorithmProvider(&hAlgorithm, pszAlgId, 0, 0);

    if (0 <= status)
    {
        BCRYPT_HASH_HANDLE hHash = 0;

        ULONG HashBlockLength, cb;

        0 <= (status = BCryptGetProperty(hAlgorithm, BCRYPT_HASH_LENGTH, (PUCHAR)&HashBlockLength, sizeof(ULONG), &cb, 0)) &&
            0 <= (status = BCryptCreateHash(hAlgorithm, &hHash, 0, 0, 0, 0, 0));

        BCryptCloseAlgorithmProvider(hAlgorithm, 0);

        if (0 <= status)
        {
            PUCHAR pbHash = (PUCHAR)alloca(HashBlockLength);

            0 <= (status = BCryptHashData(hHash, const_cast<PUCHAR>(pbToBeSigned), cbToBeSigned, 0)) &&
                0 <= (status = BCryptFinishHash(hHash, pbHash, HashBlockLength, 0));

            BCryptDestroyHash(hHash);

            if (0 <= status)
            {
                BCRYPT_PKCS1_PADDING_INFO pi = { pszAlgId };

                status = BCryptSignHash(hKey, &pi, pbHash, HashBlockLength, 
                    pbSignature, *pcbSignature, pcbSignature, BCRYPT_PAD_PKCS1);
            }
        }
    }

    return status;
}

inline NTSTATUS openssl_sign(_In_ BCRYPT_KEY_HANDLE hKey,
                             _In_ PCSTR szToBeSigned,
                             _Out_ PUCHAR pbSignature, 
                             _Inout_ PULONG pcbSignature,
                             _In_ PCWSTR pszAlgId)
{
    return openssl_sign(hKey, (PCUCHAR)szToBeSigned, (ULONG)strlen(szToBeSigned), pbSignature, pcbSignature, pszAlgId);
}

NTSTATUS BCryptImportKey(_Out_ BCRYPT_KEY_HANDLE *phKey, 
                         _In_ PCWSTR pszBlobType, 
                         _In_ BCRYPT_RSAKEY_BLOB* prkb, 
                         _In_ ULONG cb)
{
    BCRYPT_ALG_HANDLE hAlgorithm;

    NTSTATUS status = BCryptOpenAlgorithmProvider(&hAlgorithm, BCRYPT_RSA_ALGORITHM, 0, 0);

    if (0 <= status)
    {
        status = BCryptImportKeyPair(hAlgorithm, 0, pszBlobType, phKey, (PUCHAR)prkb, cb, 0);

        BCryptCloseAlgorithmProvider(hAlgorithm, 0);
    }

    return status;
}

HRESULT BCryptImportPrivateKey(_Out_ BCRYPT_KEY_HANDLE *phKey, _In_ PCUCHAR pbKey, _In_ ULONG cbKey)
{
    ULONG cb;
    PCRYPT_PRIVATE_KEY_INFO PrivateKeyInfo;

    ULONG dwError = BOOL_TO_ERROR(CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, PKCS_PRIVATE_KEY_INFO, 
        pbKey, cbKey, CRYPT_DECODE_ALLOC_FLAG|CRYPT_DECODE_NOCOPY_FLAG, 0, (void**)&PrivateKeyInfo, &cb));

    if (dwError == NOERROR)
    {
        BCRYPT_RSAKEY_BLOB* prkb;

        dwError = BOOL_TO_ERROR(CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 
            CNG_RSA_PRIVATE_KEY_BLOB, PrivateKeyInfo->PrivateKey.pbData, PrivateKeyInfo->PrivateKey.cbData, 
            CRYPT_DECODE_ALLOC_FLAG, 0, (void**)&prkb, &cb));

        LocalFree(PrivateKeyInfo);

        if (dwError == NOERROR)
        {
            NTSTATUS status = BCryptImportKey(phKey, BCRYPT_RSAPRIVATE_BLOB, prkb, cb);
            LocalFree(prkb);
            return HRESULT_FROM_NT(status);
        }
    }

    return HRESULT_FROM_WIN32(dwError);
}

HRESULT BCryptImportPublicKey(_Out_ BCRYPT_KEY_HANDLE *phKey, _In_ PCUCHAR pbKeyOrCert, _In_ ULONG cbKeyOrCert, _In_ bool bCert)
{
    ULONG cb;

    union {
        PVOID pvStructInfo;
        PCERT_INFO pCertInfo;
        PCERT_PUBLIC_KEY_INFO PublicKeyInfo;
    };

    ULONG dwError = BOOL_TO_ERROR(CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 
        bCert ? X509_CERT_TO_BE_SIGNED : X509_PUBLIC_KEY_INFO, 
        pbKeyOrCert, cbKeyOrCert, CRYPT_DECODE_ALLOC_FLAG|CRYPT_DECODE_NOCOPY_FLAG, 0, &pvStructInfo, &cb));

    if (dwError == NOERROR)
    {
        BCRYPT_RSAKEY_BLOB* prkb;

        PVOID pv = pvStructInfo;

        if (bCert)
        {
            PublicKeyInfo = &pCertInfo->SubjectPublicKeyInfo;
        }

        dwError = BOOL_TO_ERROR(CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 
            CNG_RSA_PUBLIC_KEY_BLOB, 
            PublicKeyInfo->PublicKey.pbData, 
            PublicKeyInfo->PublicKey.cbData, 
            CRYPT_DECODE_ALLOC_FLAG, 0, (void**)&prkb, &cb));

        LocalFree(pv);

        if (dwError == NOERROR)
        {
            NTSTATUS status = BCryptImportKey(phKey, BCRYPT_RSAPUBLIC_BLOB, prkb, cb);
            LocalFree(prkb);
            return HRESULT_FROM_NT(status);
        }
    }

    return HRESULT_FROM_WIN32(dwError);
}

enum BLOB_TYPE { bt_priv, bt_pub, bt_cert };

HRESULT BCryptImportKey(_Out_ BCRYPT_KEY_HANDLE *phKey, _In_ BLOB_TYPE bt, _In_ PCSTR szKey, _In_ ULONG cchKey)
{
    PUCHAR pbKey = 0;
    ULONG cbKey = 0;
    HRESULT hr;

    while (CryptStringToBinaryA(szKey, cchKey, CRYPT_STRING_BASE64HEADER, pbKey, &cbKey, 0, 0))
    {
        if (pbKey)
        {
            switch (bt)
            {
            case bt_priv:
                hr = BCryptImportPrivateKey(phKey, pbKey, cbKey);
                break;
            case bt_pub:
                hr = BCryptImportPublicKey(phKey, pbKey, cbKey, false);
                break;
            case bt_cert:
                hr = BCryptImportPublicKey(phKey, pbKey, cbKey, true);
                break;
            default: hr = E_INVALIDARG;
            }

            _freea(pbKey);

            return hr;
        }

        if (!(pbKey = (PUCHAR)_malloca(cbKey)))
        {
            break;
        }
    }

    hr = HRESULT_FROM_WIN32(GetLastError());

    if (pbKey) _freea(pbKey);

    return hr;
}

void DoCNGTest(_In_ PCSTR szToBeSigned, 
               _In_ PCSTR szPrivateKey, 
               _In_ ULONG cchPrivateKey,
               _In_ PCSTR szPublicKeyOrCert, 
               _In_ ULONG cchPublicKeyOrCert,
               _In_ bool bCert)
{
    HRESULT hr;
    BCRYPT_KEY_HANDLE hKey;

    PUCHAR pbSignature = 0;
    ULONG cbSignature = 0, cb;

    if (0 <= (hr = BCryptImportKey(&hKey, bt_priv, szPrivateKey, cchPrivateKey)))
    {
        if (0 <= (hr = BCryptGetProperty(hKey, BCRYPT_SIGNATURE_LENGTH, (PUCHAR)&cbSignature, sizeof(ULONG), &cb, 0)))
        {
            pbSignature = (PUCHAR)alloca(cbSignature);

            hr = HRESULT_FROM_NT(openssl_sign(hKey, szToBeSigned, pbSignature, &cbSignature, BCRYPT_SHA256_ALGORITHM));
        }

        BCryptDestroyKey(hKey);
    }

    if (0 <= hr)
    {
        if (0 <= (hr = BCryptImportKey(&hKey, bCert ? bt_cert : bt_pub, szPublicKeyOrCert, cchPublicKeyOrCert)))
        {
            hr = HRESULT_FROM_NT(openssl_verify(hKey, szToBeSigned, pbSignature, cbSignature, BCRYPT_SHA256_ALGORITHM));

            if (0 > hr)
            {
                __debugbreak();
            }

            BCryptDestroyKey(hKey);
        }
    }
}