5.27 Extracting Raw Key Data from a CryptoAPI Key Object

5.27.1 Problem

You have a symmetric key stored in a CryptoAPI key object that you want to use with another API, such as OpenSSL.

5.27.2 Solution

The Microsoft CryptoAPI is designed to prevent unintentional disclosure of sensitive key information. To do this, key information is stored in opaque data objects by the Cryptographic Service Provider (CSP) used to create the key object. Key data is exportable from key objects, but the data must be encrypted with another key to prevent accidental disclosure of the raw key data.

To extract the raw key data from a CryptoAPI key, you must first export the key using the CryptoAPI function CryptoExportKey( ). The key data obtained from this function will be encrypted with another key, which you can then use to decrypt the encrypted key data to obtain the raw key data that another API, such as OpenSSL, can use.

5.27.3 Discussion

To export a key using the CryptoExportKey( ) function, you must provide the function with another key that will be used to encrypt the key data that's to be exported. Recipe 5.26 includes a function, SpcGetExportableContext( ), that obtains a handle to a CSP context object suitable for exporting keys created with it. The CSP context object uses a "container" to store public key pairs. Every public key container can have a special public key pair known as an exchange key, which is the key that we'll use to decrypt the exported key data.

The function CryptGetUserKey( ) is used to obtain the exchange key. If it doesn't exist, SpcExportKeyData( ), listed later in this section, will create a 1,024-bit exchange key, which will be stored as the exchange key in the public key container so future attempts to get the key will succeed. The special algorithm identifier AT_KEYEXCHANGE is used to reference the exchange key.

Symmetric keys are always exported via CryptExportKey( ) in "simple blob" format, specified by the SIMPLEBLOB constant passed to CryptExportKey( ). The data returned in the buffer from CryptExportKey( ) will have a BLOBHEADER structure, followed by an ALG_ID for the algorithm used to encrypt the key data. The raw key data will follow the BLOBHEADER and ALG_ID header information. For extracting the raw key data from a CryptoAPI key, the data in the BLOBHEADER structure and the ALG_ID are of no interest, but you must be aware of their existence so that you can skip over them to find the encrypted key data.

Finally, the encrypted key data can be decrypted using CryptDecrypt( ) and the exchange key. The CryptDecrypt( ) function is described in more detail in Recipe 5.25. The decrypted data is the raw key data that can now be passed off to other APIs or used in protocols that already provide their own protection for the key. The return from SpcExportKeyData( ) will be a buffer allocated with LocalAlloc( ) that contains the unencrypted symmetric key if no errors occur; otherwise, NULL will be returned.

#include <windows.h>
#include <wincrypt.h>
BYTE *SpcExportKeyData(HCRYPTPROV hProvider, HCRYPTKEY hKey, DWORD *cbData) {
  BOOL      bResult = FALSE;
  BYTE      *pbData = 0, *pbKeyData;
  HCRYPTKEY hExpKey = 0;
  if (!CryptGetUserKey(hProvider, AT_KEYEXCHANGE, &hExpKey)) {
    if (GetLastError(  ) != NTE_NO_KEY) goto done;
    if (!CryptGenKey(hProvider, AT_KEYEXCHANGE, (1024 << 16), &hExpKey))
      goto done;
  if (!CryptExportKey(hKey, hExpKey, SIMPLEBLOB, 0, 0, cbData)) goto done;
  if (!(pbData = (BYTE *)LocbalAlloc(LMEM_FIXED, *cbData))) goto done;
  if (!CryptExportKey(hKey, hExpKey, SIMPLEBLOB, 0, pbData, cbData))
    goto done;
  pbKeyData = pbData + sizeof(BLOBHEADER) + sizeof(ALG_ID);
  (*cbData) -= (sizeof(BLOBHEADER) + sizeof(ALG_ID));
  bResult = CryptDecrypt(hExpKey, 0, TRUE, 0, pbKeyData, cbData);
  if (hExpKey) CryptDestroyKey(hExpKey);
  if (!bResult && pbData) LocalFree(pbData);
  else if (pbData) MoveMemory(pbData, pbKeyData, *cbData);
  return (bResult ? (BYTE *)LocalReAlloc(pbData, *cbData, 0) : 0);

5.27.4 See Also

Recipe 5.25, Recipe 5.26