Skip to content

CSP Blobs Between C and C#

March 19, 2011

This post explains one way to export keys generated by Windows Cryptographic Service Provider. The CSP provides cryptographic security functions and is accessible through Windows APIs.   The consistency of documentation on the Crypto API varies, with some parts well-explained and others left to trial-and-error to figure out, and what I describe here falls into the latter category.  As a result, I’m not sure what follows is a desirable way to do things, but by attempting it I learned a bit, so it seems worthwhile to write about it anyway.

For a programmer, the CSP may be treated as a black box.  The CSP may generate strong keys, or load keys from the Windows certificate store.  It also provides a generic API for encrypting and decrypting data, which is independent of the cryptographic algorithm, for example the same encryption functions are shared by symmetric algorithms such as AES and asymmetric ones like RSA.  However, arguments to the same functions may differ depending on the algorithm, so some of the generality gets lost once you start working with them (incidentally making it harder to use because now you need to be aware of multiple interpretations of arguments).  Fortunately in .NET, higher-level objects for the different Crypto algorithm providers exist, making it simpler to write for a specific algorithm or family.

I wrote a library to contact a service where data is exchanged using RSA encryption.  Both managed and unmanaged applications use this library, which resulted in me building two versions.  Ideally, a single version would be better, but the C++ users could not tolerate managed code (due to predictability constraints), so implementing only a C# version was not an option. I could have created only the unmanaged library–however it’s much easier to develop and test code in C# to begin with, and later port it to C++, resulting in two working versions, so at that point they may was well remain separate.

Both libraries need to use a public key of the web service to encrypt authentication requests, so I looked for a way to add a CSP-generated key to both libraries.  The first way that came to mind was using a certificate file, with the library reading it from the secure certificate store without ever exposing the key.  Unfortunately, some of the applications are deployed in data centers where there is no practical way to upload a key to their certificate stores.

Instead, I  looked at the APIs for a clue how to exchange keys.  The .NET RSACryptoServiceProvider has three ways to export key information:  ExportParameters, ExportCspBlob, or ToXmlString (there are also inverse functions to import data).  The first function creates another .NET object, so doesn’t help export a key outside the application.  The other two show more promise.  In order for this to work, the C API must be able to import the same type of data.  There’s no function to import an XML key, however BCryptImportKeyPair is likely to be compatible because, as stated in documentation, the “ExportCspBlob method returns a blob containing key information that is compatible with the unmanaged Microsoft Cryptographic API (CAPI).”   The helpfulness of the documentation ends there, because no where on the MSDN site could I find an example of this function in use, or what parameters are required to correctly import a key with BCryptImportKeyPair.

I set out to try to import a key created by the C# .NET library.  First, here’s some sample code that shows how to export a newly created key blob to a binary file:

1
2
3
4
5
6
7
8
9
10
11
12
13
void ExportBlobToFile(string filepath, bool exportPrivateKey)
{
    var csp = new CspParameters(1, "Microsoft Strong Cryptographic Provider");
    // specify a key size 2048 bits
    using (var rsa = new RSACryptoServiceProvider(2048, csp))
    {
        // After construction, rsa is populated with a newly generated key
        // but we could load a key from elsewhere if exporting an existing one.
        byte[] blob = rsa.ExportCspBlob(exportPrivateKey);
        using (var fs = new FileStream(filepath, FileMode.Create))
            fs.Write(blob, 0, blob.Length);
    }
}

Here’s a sample of code that does the inverse, importing the key:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void ImportBlobFromFile(string filepath)
{
    byte[] blob;
    using (var fs = new FileStream(filepath, FileMode.Open))
    {
        blob= new byte[fs.Length];
        fs.Read(blob, 0, blob.Length);
    }
 
    using (var rsa = new RSACryptoServiceProvider(p))
    {
        rsa.ImportCspBlob(blob);
        /* Here we can use rsa object to decrypt or encrypt data! */
    }
}

To import the key in C++ using the BCrypt API:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void ImportBlobFromFile(const std::string &filepath)
{
    BCRYPT_ALG_HANDLE hAlg;
    NTSTATUS ntStatus = BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_RSA_ALGORITHM, NULL, 0);
    if (ntStatus != STATUS_SUCCESS)
    {
        return;
    }
 
    ULONG blobLen;
    BYTE *blob= ReadFile(filepath, &blobLen); // ReadFile details left out
    if(blob == NULL)
    {
        BCryptCloseAlgorithmProvider(hAlg, 0);
        return;
    }
 
    BCRYPT_KEY_HANDLE hKey;
    ntStatus = BCryptImportKeyPair(hAlg, NULL, LEGACY_RSAPRIVATE_BLOB, &hKey, (PUCHAR) blob, blobLen, 0);
    delete[] blob;
 
    if (ntStatus != STATUS_SUCCESS)
    {
        BCryptCloseAlgorithmProvider(hAlg, 0);
        return;
    }
 
    /* Here we can use hKey to decrypt or encrypt data! */
 
    BCryptDestroyKey(hKey);
    BCryptCloseAlgorithmProvider(hAlg, 0);
}

Notice on line 19, the import must specify what kind of key the blob contains (as opposed to the C# version, which just figures it out without intervention). This is where trial-and-error came into play. There are seventeen possible options for this argument, four of which are specific to RSA: BCRYPT_RSAxxx_BLOB or LEGACY_RSAxxx_BLOB, where xxx may be PRIVATE or PUBLIC. As it turns out, when exchanging keys between BCrypt and C#, I must specify the LEGACY_* versions.

Here’s the further catch: if I create a public key in C#, it is NOT importable in C. In this case, the documentation says so: “The BLOB is an RSA public key BLOB that was exported by using CryptoAPI. The Microsoft primitive provider does not support importing this BLOB type.” I tested this anyway and confirmed it is indeed true; attempting to results in an NTSTATUS of “request is not supported” (see below to discover how to convert NTSTATUS to a string).

That last gotcha bit me, because it was public key I wished to export. Since I cannot import a public key generated by C# code, I now wondered if I could import one created in C to .NET. So I wrote an export function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
void ExportBlobToFile(const std::string &filepath)
{
    BCRYPT_ALG_HANDLE hAlg;
    NTSTATUS ntStatus = BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_RSA_ALGORITHM, NULL, 0);
    if (ntStatus != STATUS_SUCCESS)
    {
        return;
    }
 
    BCRYPT_KEY_HANDLE hKey;
    BCryptGenerateKeyPair(hAlg, &hKey, 2048, 0);
    BCryptFinalizeKeyPair(hKey, 0);
 
    // First, get the size by passing in a NULL buffer
    ULONG blobLen;
    ntStatus = BCryptExportKey(hKey, NULL, LEGACY_RSAPUBLIC_BLOB, NULL, 0, &blobLen, 0);
    if (ntStatus != STATUS_SUCCESS)
    {
        BCryptDestroyKey(hKey);
        BCryptCloseAlgorithmProvider(hAlg, 0);
        return;
    }
 
    unsigned char *blob= new unsigned char[blobLen];
    ntStatus = BCryptExportKey(hKey, NULL, LEGACY_RSAPUBLIC_BLOB, blob, blobLen, &blobLen, 0);
    if (ntStatus != STATUS_SUCCESS)
    {
        delete[] blob;
        BCryptDestroyKey(hKey);
        BCryptCloseAlgorithmProvider(hAlg, 0);
        return;
    }
 
    WriteBlobToFile(filepath, blob, blobLen) // details elided
 
    delete[] blob;
    BCryptDestroyKey(hKey);
    BCryptCloseAlgorithmProvider(hAlg, 0);
}

As an aside, in my C examples I must be careful to clean up and avoid leaked resources. In that respect, C# is much easier to write cleaner and safer code (objects that use system resources implement IDispose, and the “using” statement calls it for you). And while I’m on a tangent, it might be useful to show how to discover what went wrong when one of the BCrypt functions fails. They return an NTSTATUS integer, but examining this digit is not helpful because the possible values are not enumerated on MSDN. This creates a human readable error message:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void PrintError(NTSTATUS err)
{
    HMODULE Hand = LoadLibrary(TEXT( "NTDLL.DLL"));
    if(Hand != NULL)
    {
        LPTSTR buffer = NULL;
        FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_HMODULE, Hand, (DWORD) err, 0, (LPTSTR)&buffer, 256, NULL);
        if(buffer != NULL)
        {
            _tprintf(TEXT("BCryptError: %s\n"), buffer);
            LocalFree(buffer);
        }
        FreeLibrary(Hand);
    }
}

Again, programming in C ratchets up the annoyance factor compared to the ease of C#, where typically I just need to print an exception message.

In the end, I created a key pair using the C BCrypt API, and exported both the private and public keys to LEGACY blobs.  Using these blobs, I initialize the algorithm in my C# server by importing the private blob (which I must also take care to keep secret), and initialize the C# client using the public blob.  Since I also needed the same public key for the C++ client, and because the BCrypt API can’t import a LEGACY public blob, I additionally exported the BCRYPT_RSAPUBLIC_BLOB version.  Ultimately, I encoded the binary public CSP blobs as base64 strings, so they could be loaded as configuration options in the clients.  This wasn’t as slick as my original intent of using certificates, but it gets the job done.

Since this post is basically a dump of what I did to transfer keys, I may as well provide one more bit of extraneous information. The first time I attempted this, I used a different Windows cryptography library.  What I have been calling BCrypt is apparently officially called CNG:   “Cryptography API: Next Generation (CNG) is the long-term replacement for the CryptoAPI.”  This also explains why those CSP blobs are called “legacy.” When I originally started out with this older CryptoAPI, I discovered that data encrypted by RSA needed to be reversed when exchanged with C#. At first, I got a “bad data” message when trying to exchange the cipher texts. Searching for the reason led me to a forum link that pointed to blog post describing the problem. This is another case where documentation on MSDN is lacking, so I felt lucky to find this post that provided a solution. Once I switched to CNG, I no longer needed to reverse data when exchanging between C and .NET.

10 Comments leave one →
  1. jrrtolkienfan permalink
    March 10, 2012 5:18 PM

    Hi Scott,
    Great post! I have a problem and was hoping you could help.

    I need to import a public key from an X.509 certificate. This needs to be done in the kernel so I cannot use any of the certificate handling functions for the same. I’m trying to write some sample code that tries to get a key handle from a set of bytes. I used a sample cert from google to test my code. This uses a 1024 bit RSA key.

    I wasn’t sure which set of bytes represented the key so I used CertCreateCertificateContext and CryptImportPublicKeyInfoEx2 to determine the same. Here’s a snippet that tries to import the key represented in the following array:

    BYTE key[] = {
    0x30,0x81,0x89,0x02,0x81,0x81,0x00,0xbf,0xcb,0xb8
    ,0x10,0x20,0xd0,0x17,0x54,0x4c,0xab,0xb9,0x49,0x5a
    ,0xa8,0x03,0xdc,0xad,0x74,0xa7,0xb3,0x26,0x49,0x48
    ,0x7e,0xeb,0x67,0x21,0xf9,0xf6,0xfe,0x70,0x30,0xf3
    ,0xf4,0x4e,0xe6,0x47,0xf1,0xf7,0xa1,0x11,0x59,0x75
    ,0x1e,0xa0,0x0f,0xc5,0x77,0xf6,0xe4,0xdd,0xdc,0x4b
    ,0x9e,0x12,0x99,0x7b,0x5e,0xf3,0xc4,0x10,0x97,0x73
    ,0xfc,0x7f,0xd0,0x64,0xec,0x69,0xeb,0x62,0x08,0xea
    ,0x0e,0x04,0xc1,0x1f,0xbf,0xd3,0x19,0x52,0xfe,0xaa
    ,0x80,0x61,0xf8,0x40,0x9f,0x5b,0x96,0xff,0xea,0xd4
    ,0x87,0xbc,0xc9,0x5b,0x61,0xb6,0xc4,0x62,0x45,0x6b
    ,0x66,0xf6,0xb1,0x20,0x8d,0xe4,0x27,0x59,0x81,0xac
    ,0x28,0xcd,0xc7,0xd2,0x60,0x1c,0x0b,0xfa,0x08,0xce
    ,0xa3,0x85,0xd0,0xe5,0x3f,0x02,0x03,0x01,0x00,0x01}; //140 bytes
    //open an algorithm handle
    if(!NT_SUCCESS(status = BCryptOpenAlgorithmProvider(
      &hSignAlg,
      BCRYPT_RSA_ALGORITHM,
      MS_PRIMITIVE_PROVIDER,
      0
    )))
    {
      wprintf(L"**** Error 0x%x returned by BCryptOpenAlgorithmProvider\n", status);
      goto Cleanup;
    }
    if(!NT_SUCCESS(status = BCryptImportKeyPair(
      hSignAlg,
      NULL,
      BCRYPT_RSAPUBLIC_BLOB,
      &hTmpKey,
      key,
      140,
      0)))
    {
      wprintf(L"**** Error 0x%x returned by BCryptImportKeyPair\n", status);
      PrintError(status);
      goto Cleanup;
    }
    

    This returns bad data. Reversing the key array did not help either. Any ideas to what I could be doing wrong? I’d really appreciate all the help I can get.

    • sbanacho permalink*
      March 10, 2012 5:21 PM

      Hi,
      The BCryptImportKeyPair function is intended to import a key encoded as a CSP blob. I suspect your key is not encoded that way.

      What I think you want to do is generate a key using BCryptGenerateKeyPair to get an initial key handle, then use the BCryptSetProperty function to change the value of the key to the one you intent to use.

  2. Augustine Mathew (msft) permalink
    July 13, 2012 11:15 PM

    Disclaimer: I do not work for the security team. I have minimal experience with this. Also, the opinions presented here are mine and not of Microsoft.

    However, I figured I’ll add my two cents here.

    Please do not export asymmetric keys as blobs but package them as certificate pfx. This gives you added security. Once you have them exported as a cert you can use either bcrypt or crypt32 Apis to read them and get access to the keys.

    I liked your post. Kudos for getting around the issues you faced. You succinctly explain the problems you faced. I know that ms code samples on the subject is very bare and sometimes you have to resort to trial and error.

    I will forward this to the right set of people to get in touch with you.

  3. July 20, 2012 2:28 PM

    When I saw this posting I thought it was just what I needed. I have a key in a x509 certificate (used for test signing code). I need to export the private key in a format I can import it using BCryptImportKeyPair. So far I have tried using the X509Certificate2 class to access the certificate and get a RSACryptoServiceProvider via the PrivateKey method. I then use ExportCspBlob(true) to export the key to a blob. In my case I do not write it to a file (this is test code) but rather I simply try to import it using BCryptImportKeyPair. I have tried LEGACY_RSAPRIVATE_BLOB as well as BCRYPT_RSAPRIVATE_BLOB but both return 0x80090005. The size of the blob returned by ExportCspBlob seems too large and the data does not seem correct for BCryptImportKeyPair. I can see the header BCrypt wants but it is 8 bytes into the blob returned by ExportCspBlob and the magic value is RSA2 (BCrypt seems to want RSA1). Can you shed any light on what the problem is?

  4. JohnC permalink
    September 27, 2013 6:22 AM

    A million thank yous! I’ve been trying to import a key generated in .NET for hours ….

    Thanks for sharing your experience.

    • Anon permalink
      December 30, 2014 9:54 AM

      I’ve got code that exports the public key from a c# client using AesCryptoServiceProvider or TripleDesCryptoServiceProvider, sends that public key to a C++ server that imports it using CryptImportKey (after CryptAcquireContext loads the proper provider). This works just fine.

      • Anon permalink
        December 30, 2014 9:57 AM

        Forgot to add the export is using ExportCspBlob. The raw byte array is sent to the server as hex, converted back to a byte array and CryptImportKey takes it fine.

        The server then generates a session key, encrypts it with the public key and sends it back to the client. The client decrypts it with the private key and they are off and running with the session key from then on.

      • Anon permalink
        December 30, 2014 10:02 AM

        Misread the code, the ExportCspBlob is coming from an RSACryptoServiceProvider. The AES or 3DES is used for the session key.

  5. Anon Eemuss permalink
    January 28, 2015 5:40 PM

    Hey,

    I’ve got the same code pattern for exporting a legacy RSA blob in C++, but can’t get it to import properly on the C# side of things. It says “Bad Provider Version”.

    I take the blob that is exported, send it across the wire and reassemble the byte array in C#, and … it just doesn’t want to go.

    Is there some other step that has to be taken on the C# side to import the blob using RSACryptoProvider’s importCSPBlob function?

  6. ravi permalink
    May 1, 2017 1:02 AM

    I have already generated rsa xml key pair and I would like to store these key pair in machine store. it is possible using above functions.

Leave a Reply

Your email address will not be published. Required fields are marked *