How to export AES key derived using CryptoAPI

戏子无情 提交于 2019-12-05 19:38:28
Maarten Bodewes

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.
softwariness

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;
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!