How to export AES key derived using CryptoAPI
Asked Answered
C

2

6

I want to use the Windows CryptoAPI functions for AES encryption.

As you know, the API has a lot of functions to create, hash and change the entered key; it derives the key and you get a handle to it.

My problem is that I want to know what the derived key is?

#include <Windows.h>
#include <stdio.h>

int main()
{
    HCRYPTPROV hProv = 0;
    HCRYPTKEY hKey = 0;
    HCRYPTHASH hHash = 0;
    DWORD dwCount = 5;
    BYTE  rgData[512] = {0x01, 0x02, 0x03, 0x04, 0x05};
    LPWSTR wszPassword = L"pass";
    DWORD cbPassword = (wcslen(wszPassword)+1)*sizeof(WCHAR);

    if(!CryptAcquireContext(
        &hProv, 
        NULL,  
        MS_ENH_RSA_AES_PROV, 
        PROV_RSA_AES, 
        CRYPT_VERIFYCONTEXT))
    {
        printf("Error %x during CryptAcquireContext!\n", GetLastError());
        goto Cleanup;
    }

    if(!CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hHash)) 
    { 
        printf("Error %x during CryptCreateHash!\n", GetLastError());
        goto Cleanup;
    } 

    if(!CryptHashData(hHash, (PBYTE)wszPassword, cbPassword, 0)) 
    { 
        printf("Error %x during CryptHashData!\n", GetLastError());
        goto Cleanup;
    } 

    if (!CryptDeriveKey(hProv, CALG_AES_256, hHash, CRYPT_EXPORTABLE, &hKey)) 
    { 
        printf("Error %x during CryptDeriveKey!\n", GetLastError());
        goto Cleanup;
    }

    for(DWORD i = 0; i < dwCount; i++)
    {
        printf("%d ",rgData[i]);
    }
    printf("\n");

    if (!CryptEncrypt(
        hKey,
        0,
        TRUE,
        0,
        rgData,
        &dwCount,
        sizeof(rgData))) 
    {
        printf("Error %x during CryptEncrypt!\n", GetLastError());
        goto Cleanup;
    }

    for(DWORD i = 0; i < dwCount; i++)
    {
        printf("%d ",rgData[i]);
    }
    printf("\n");

Cleanup:
    if(hKey) 
    {
        CryptDestroyKey(hKey);
    }
    if(hHash) 
    {
        CryptDestroyHash(hHash);
    }
    if(hProv) 
    {
        CryptReleaseContext(hProv, 0);
    }
    return 0;
}
Comehither answered 12/4, 2015 at 4:56 Comment(1)
See this old Microsoft KB article: Q228786, Export/Import Plain Text Session Key Using CryptoAPI.Ascensive
M
2

EDIT: if the key derivation is in software then the answer of softwariness is probably better. So that's a more generic answer and should be preferred. You can use this replay method if the method of softwariness fails. This could be the case if the token does not allow plaintext export.

In general these method have been created in such a way that retrieving the resulting secret is hard if not impossible. However, the method to derive the key is described in the Remarks section of the API documentation of CryptDeriveKey. So you can replay the creation of you have the base data.

The API doesn't describe what happens if SHA-2 is used, but I presume it just uses the leftmost bits of the SHA-256 result for the key.

After derivation you can of course test by encrypting/decrypting or some data.

Let n be the required derived key length, in bytes. The derived key is the first n bytes of the hash value after the hash computation has been completed by CryptDeriveKey. If the hash is not a member of the SHA-2 family and the required key is for either 3DES or AES, the key is derived as follows:

  1. Form a 64-byte buffer by repeating the constant 0x36 64 times. Let k be the length of the hash value that is represented by the input parameter hBaseData. Set the first k bytes of the buffer to the result of an XOR operation of the first k bytes of the buffer with the hash value that is represented by the input parameter hBaseData.
  2. Form a 64-byte buffer by repeating the constant 0x5C 64 times. Set the first k bytes of the buffer to the result of an XOR operation of the first k bytes of the buffer with the hash value that is represented by the input parameter hBaseData.
  3. Hash the result of step 1 by using the same hash algorithm as that used to compute the hash value that is represented by the hBaseData parameter.
  4. Hash the result of step 2 by using the same hash algorithm as that used to compute the hash value that is represented by the hBaseData parameter.
  5. Concatenate the result of step 3 with the result of step 4.
  6. Use the first n bytes of the result of step 5 as the derived key.
Maitund answered 12/4, 2015 at 11:31 Comment(5)
MickeySoft doesn't seem to provide a permalink or an anchor to the Remarks section so update when the page vanishes please.Maitund
My interpretation of the OP's question was that they wanted to export the key derived using CryptDeriveKey, which they can do using CryptExportKey, as they have made the derived key exportable by passing CryptDeriveKey the CRYPT_EXPORTABLE flag in their example, and I have made some edits to the question to clarify this. Let me know if you interpreted this differently, given you have gone a different direction here with this, which seems to be correct, but was not what I thought the OP is looking for.Metaphysical
@Metaphysical The OP asked "My problem is that I want to know what the derived key is?" But my problem was that I didn't read much past: "The key BLOB to export is useless until the intended recipient uses the CryptImportKey function on it to import the key or key pair into a recipient's CSP." mentioned by the Mickeysoft documentation. Happy to upvote your answer, I'll leave my answer here in case a token does not allow plaintext export of the binary material.Maitund
in case a token does not allow plaintext export - that's a valid point. I'd meant to mention that and forgot by the time I'd finished the coding. I've linked to your answer with a note regarding that scenario.Metaphysical
Thank you so much for pointing this out! This is critical in porting CryptoAPI to Crypto++ or OpenSSL, etc. I was very confused why using the same hashing/crypto scheme was yielding different results between the libraries.Jut
M
4

As you have made your derived key exportable by passing CRYPT_EXPORTABLE to the CryptDeriveKey function, it is possible to use the CryptExportKey function to export the derived key material.

The exception to this would be if you are using a third-party CSP which does not permit key export, even with the CRYPT_EXPORTABLE flag, in which case see Maarten Bodewes' answer to replay the key derivation steps yourself outside the CSP.

If you follow that CryptExportKey MSDN link you'll see there's an example function showing how to use that function to export they key material in the plain. It is also possible to export the key wrapped using another key (i.e. encrypted by another key) if you want, by passing a key handle to the hExpKey (second parameter) to CryptExportKey. Passing NULL to this parameter exports in the plain instead.

I've updated your example program (below) to export the key using the example code from the MSDN link above, and to use the CryptBinaryToString function to print the key material as a base64 string. I've exported as the PLAINTEXTKEYBLOB blob-type, which uses the data structure BLOBHEADER|key length|key material so there is also a little work to be done to pull out the raw key material that we're interested in out of that blob:

#include <Windows.h>
#include <stdio.h>

#pragma comment(lib, "crypt32.lib")

BOOL GetExportedKey(
    HCRYPTKEY hKey,
    DWORD dwBlobType,
    LPBYTE *ppbKeyBlob,
    LPDWORD pdwBlobLen)
{
    DWORD dwBlobLength;
    *ppbKeyBlob = NULL;
    *pdwBlobLen = 0;

    // Export the public key. Here the public key is exported to a 
    // PUBLICKEYBLOB. This BLOB can be written to a file and
    // sent to another user.

    if (CryptExportKey(
        hKey,
        NULL,
        dwBlobType,
        0,
        NULL,
        &dwBlobLength))
    {
        printf("Size of the BLOB for the public key determined. \n");
    }
    else
    {
        printf("Error computing BLOB length.\n");
        return FALSE;
    }

    // Allocate memory for the pbKeyBlob.
    if (*ppbKeyBlob = (LPBYTE)malloc(dwBlobLength))
    {
        printf("Memory has been allocated for the BLOB. \n");
    }
    else
    {
        printf("Out of memory. \n");
        return FALSE;
    }

    // Do the actual exporting into the key BLOB.
    if (CryptExportKey(
        hKey,
        NULL,
        dwBlobType,
        0,
        *ppbKeyBlob,
        &dwBlobLength))
    {
        printf("Contents have been written to the BLOB. \n");
        *pdwBlobLen = dwBlobLength;
    }
    else
    {
        printf("Error exporting key.\n");
        free(*ppbKeyBlob);
        *ppbKeyBlob = NULL;

        return FALSE;
    }

    return TRUE;
}

int main()
{
    HCRYPTPROV hProv = 0;
    HCRYPTKEY hKey = 0;
    HCRYPTHASH hHash = 0;
    DWORD dwCount = 5;
    LPBYTE keyBlob = NULL;
    DWORD keyBlobLength;
    LPSTR keyBlobBase64 = NULL;
    DWORD base64Length = 0;
    BYTE  rgData[512] = { 0x01, 0x02, 0x03, 0x04, 0x05 };
    LPWSTR wszPassword = L"pass";
    DWORD cbPassword = (wcslen(wszPassword) + 1)*sizeof(WCHAR);

    if (!CryptAcquireContext(
        &hProv,
        NULL,
        MS_ENH_RSA_AES_PROV,
        PROV_RSA_AES,
        CRYPT_VERIFYCONTEXT))
    {
        printf("Error %x during CryptAcquireContext!\n", GetLastError());
        goto Cleanup;
    }

    if (!CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hHash))
    {
        printf("Error %x during CryptCreateHash!\n", GetLastError());
        goto Cleanup;
    }

    if (!CryptHashData(hHash, (PBYTE)wszPassword, cbPassword, 0))
    {
        printf("Error %x during CryptHashData!\n", GetLastError());
        goto Cleanup;
    }

    if (!CryptDeriveKey(hProv, CALG_AES_256, hHash, CRYPT_EXPORTABLE, &hKey))
    {
        printf("Error %x during CryptDeriveKey!\n", GetLastError());
        goto Cleanup;
    }

    if (!GetExportedKey(hKey, PLAINTEXTKEYBLOB, &keyBlob, &keyBlobLength))
    {
        printf("Error %x during GetExportedKey!\n", GetLastError());
        goto Cleanup;
    }

    while (1)
    {
        // PLAINTEXTKEYBLOB: BLOBHEADER|DWORD key length|Key material|
        DWORD keyMaterialLength;
        LPBYTE keyMaterial;

        keyMaterialLength = *(DWORD*)(keyBlob + sizeof(BLOBHEADER));
        keyMaterial = (keyBlob + sizeof(BLOBHEADER) + sizeof(DWORD));

        if (!CryptBinaryToStringA(keyMaterial, keyMaterialLength, CRYPT_STRING_BASE64, keyBlobBase64, &base64Length))
        {
            printf("Error %x during GetExportedKey!\n", GetLastError());
            goto Cleanup;
        }

        if (keyBlobBase64)
        {
            printf("%d-bit key blob: %s\n", keyMaterialLength * 8, keyBlobBase64);
            break;
        }
        else
        {
            keyBlobBase64 = malloc(base64Length);
        }
    }

    for (DWORD i = 0; i < dwCount; i++)
    {
        printf("%d ", rgData[i]);
    }
    printf("\n");

    if (!CryptEncrypt(
        hKey,
        0,
        TRUE,
        0,
        rgData,
        &dwCount,
        sizeof(rgData)))
    {
        printf("Error %x during CryptEncrypt!\n", GetLastError());
        goto Cleanup;
    }

    for (DWORD i = 0; i < dwCount; i++)
    {
        printf("%d ", rgData[i]);
    }
    printf("\n");

Cleanup:
    free(keyBlob);
    free(keyBlobBase64);
    if (hKey)
    {
        CryptDestroyKey(hKey);
    }
    if (hHash)
    {
        CryptDestroyHash(hHash);
    }
    if (hProv)
    {
        CryptReleaseContext(hProv, 0);
    }
    return 0;
}
Metaphysical answered 12/4, 2015 at 12:24 Comment(0)
M
2

EDIT: if the key derivation is in software then the answer of softwariness is probably better. So that's a more generic answer and should be preferred. You can use this replay method if the method of softwariness fails. This could be the case if the token does not allow plaintext export.

In general these method have been created in such a way that retrieving the resulting secret is hard if not impossible. However, the method to derive the key is described in the Remarks section of the API documentation of CryptDeriveKey. So you can replay the creation of you have the base data.

The API doesn't describe what happens if SHA-2 is used, but I presume it just uses the leftmost bits of the SHA-256 result for the key.

After derivation you can of course test by encrypting/decrypting or some data.

Let n be the required derived key length, in bytes. The derived key is the first n bytes of the hash value after the hash computation has been completed by CryptDeriveKey. If the hash is not a member of the SHA-2 family and the required key is for either 3DES or AES, the key is derived as follows:

  1. Form a 64-byte buffer by repeating the constant 0x36 64 times. Let k be the length of the hash value that is represented by the input parameter hBaseData. Set the first k bytes of the buffer to the result of an XOR operation of the first k bytes of the buffer with the hash value that is represented by the input parameter hBaseData.
  2. Form a 64-byte buffer by repeating the constant 0x5C 64 times. Set the first k bytes of the buffer to the result of an XOR operation of the first k bytes of the buffer with the hash value that is represented by the input parameter hBaseData.
  3. Hash the result of step 1 by using the same hash algorithm as that used to compute the hash value that is represented by the hBaseData parameter.
  4. Hash the result of step 2 by using the same hash algorithm as that used to compute the hash value that is represented by the hBaseData parameter.
  5. Concatenate the result of step 3 with the result of step 4.
  6. Use the first n bytes of the result of step 5 as the derived key.
Maitund answered 12/4, 2015 at 11:31 Comment(5)
MickeySoft doesn't seem to provide a permalink or an anchor to the Remarks section so update when the page vanishes please.Maitund
My interpretation of the OP's question was that they wanted to export the key derived using CryptDeriveKey, which they can do using CryptExportKey, as they have made the derived key exportable by passing CryptDeriveKey the CRYPT_EXPORTABLE flag in their example, and I have made some edits to the question to clarify this. Let me know if you interpreted this differently, given you have gone a different direction here with this, which seems to be correct, but was not what I thought the OP is looking for.Metaphysical
@Metaphysical The OP asked "My problem is that I want to know what the derived key is?" But my problem was that I didn't read much past: "The key BLOB to export is useless until the intended recipient uses the CryptImportKey function on it to import the key or key pair into a recipient's CSP." mentioned by the Mickeysoft documentation. Happy to upvote your answer, I'll leave my answer here in case a token does not allow plaintext export of the binary material.Maitund
in case a token does not allow plaintext export - that's a valid point. I'd meant to mention that and forgot by the time I'd finished the coding. I've linked to your answer with a note regarding that scenario.Metaphysical
Thank you so much for pointing this out! This is critical in porting CryptoAPI to Crypto++ or OpenSSL, etc. I was very confused why using the same hashing/crypto scheme was yielding different results between the libraries.Jut

© 2022 - 2025 — McMap. All rights reserved.