Search code examples
windowsencryptionrsacng

How to export RSA Keys generated from CNG API?


I successfully generated RSA keys using the following sequence of CNG APIs:

BCryptOpenAlgorithmProvider(.., BCRYPT_RSA_ALGORITHM, ...);
BCryptGenerateKeyPair(..., 2048/*Key size*/, ...);
BCryptFinalizeKeyPair(...);
BCryptExportKey(..., BCRYPT_RSAPRIVATE_BLOB, ...);
BCryptExportKey(..., BCRYPT_RSAPUBLIC_BLOB, ...);

I'm confused though which part of the buffer generated, should be Base64 encoded to write out a PEM file? I'm not seeing 'MIIE' within the buffer if I Base64 encode the whole buffer that existing valid PEM files have.

Any guidance is greatly appreciated.


Solution

  • really need not direct base64 encode output from BCryptExportKey or NCryptExportKey but do extra steps:

    1. use BCryptExportKey (or NCryptExportKey) with BCRYPT_RSAFULLPRIVATE_BLOB (but not BCRYPT_RSAPRIVATE_BLOB ) or BCRYPT_RSAPUBLIC_BLOB
    2. encode resulting BCRYPT_RSAKEY_BLOB with CNG_RSA_PRIVATE_KEY_BLOB or CNG_RSA_PUBLIC_KEY_BLOB and put to CRYPT_PRIVATE_KEY_INFO
    3. encode CRYPT_PRIVATE_KEY_INFO with PKCS_PRIVATE_KEY_INFO
    4. call CryptBinaryToStringA

    only after this will be 'MIIE' within the buffer

    HRESULT bthr(BOOL b)
    {
        return b ? S_OK : HRESULT_FROM_WIN32(GetLastError());
    }
    
    HRESULT ExportToPem(_In_ BCRYPT_KEY_HANDLE hKey, BOOL bPrivate, _Out_ PSTR* ppsz, _Out_ PULONG pcch)
    {
        HRESULT hr;
        CRYPT_PRIVATE_KEY_INFO PrivateKeyInfo = { 0, {const_cast<PSTR>(szOID_RSA_RSA)} };
        ULONG cbKey = 0;
        PUCHAR pbKey = 0;//really PBCRYPT_RSAKEY_BLOB
    
        PCWSTR pszBlobType;
        PCSTR lpszStructType;
    
        if (bPrivate)
        {
            pszBlobType = BCRYPT_RSAFULLPRIVATE_BLOB;
            lpszStructType = CNG_RSA_PRIVATE_KEY_BLOB;
        }
        else
        {
            pszBlobType = BCRYPT_RSAPUBLIC_BLOB;
            lpszStructType = CNG_RSA_PUBLIC_KEY_BLOB;
        }
    
        while (0 <= (hr = BCryptExportKey(hKey, 0, pszBlobType, pbKey, cbKey, &cbKey, 0)))
        {
            if (pbKey)
            {
                if (0 <= (hr = bthr(CryptEncodeObjectEx(X509_ASN_ENCODING, 
                    lpszStructType, pbKey, CRYPT_ENCODE_ALLOC_FLAG, 0, 
                    &PrivateKeyInfo.PrivateKey.pbData, &PrivateKeyInfo.PrivateKey.cbData))))
                {
                    hr = bthr(CryptEncodeObjectEx(X509_ASN_ENCODING, PKCS_PRIVATE_KEY_INFO, 
                        &PrivateKeyInfo, CRYPT_ENCODE_ALLOC_FLAG, 0, 
                        &pbKey, &cbKey));
    
                    LocalFree(PrivateKeyInfo.PrivateKey.pbData);
    
                    if (0 <= hr)
                    {
                        PSTR psz = 0;
                        ULONG cch = 0;
                        while (0 <= (hr = bthr(CryptBinaryToStringA(
                            pbKey, cbKey, CRYPT_STRING_BASE64, psz, &cch))))
                        {
                            if (psz)
                            {
                                *ppsz = psz, *pcch = cch;
                                break;
                            }
    
                            if (!(psz = (PSTR)LocalAlloc(0, cch)))
                            {
                                hr = HRESULT_FROM_WIN32(GetLastError());
                                break;
                            }
                        }
    
                        LocalFree(pbKey);
                    }
                }
                break;
            }
    
            pbKey = (PUCHAR)alloca(cbKey);
        }
    
        return hr;
    }
    

    and use it:

        PSTR psz;
        ULONG cch;
        if (0 <= ExportToPem(hKey, bPrivate, &psz, &cch))
        {
            PSTR pc = psz;
            ULONG cb;
            do 
            {
                cb = min(cch, 0x100);
                DbgPrint("%.*s", cb, pc);
            } while (pc += cb, cch -= cb);
            LocalFree(psz);
        }