如何导出使用CryptoAPI派生的AES密钥

时间:2020-12-11 18:22:05

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

我想使用Windows的密码api函数来进行AES加密。

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.

如您所知,该API有许多函数来创建、哈希和更改输入的键;它派生了键,你就得到了它的句柄。

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;
}

2 个解决方案

#1


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.

一般来说,这些方法都是用这样一种方式创建的,如果不是不可能,那么检索结果的秘密是很困难的。但是,在CryptDeriveKey的API文档的备注部分中描述了导出密钥的方法。所以你可以重新创建你有基本数据。

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.

API没有描述如果使用SHA-2会发生什么,但是我假设它只是使用SHA-256的最左位作为键。

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:

让n为所需的派生密钥长度,以字节为单位。派生键是隐式派生键完成哈希计算后哈希值的前n个字节。如果哈希不是SHA-2家族的成员,而所需的键是3DES或AES的,则该键派生如下:

  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. 通过重复常量0x3664次来形成一个64字节的缓冲区。设k是由输入参数hBaseData表示的哈希值的长度。将缓冲区的前k个字节设置为缓冲区的前k个字节的XOR操作的结果,并使用输入参数hBaseData表示的散列值。
  3. 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.
  4. 通过重复常量0x5C 64次,形成一个64字节的缓冲区。将缓冲区的前k个字节设置为缓冲区的前k个字节的XOR操作的结果,并使用输入参数hBaseData表示的散列值。
  5. 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.
  6. 使用与计算由hBaseData参数表示的哈希值相同的哈希算法对步骤1的结果进行哈希。
  7. 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.
  8. 使用与计算由hBaseData参数表示的哈希值相同的哈希算法对步骤2的结果进行哈希。
  9. Concatenate the result of step 3 with the result of step 4.
  10. 将步骤3的结果与步骤4的结果连接起来。
  11. Use the first n bytes of the result of step 5 as the derived key.
  12. 使用第5步结果的前n个字节作为派生键。

#2


1  

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.

由于您已经通过将CRYPT_EXPORTABLE传递给CryptDeriveKey函数使派生密钥可导出,因此可以使用CryptExportKey函数来导出派生的密钥材料。

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.

例外情况是,如果您使用的是不允许键导出的第三方CSP,即使是带有CRYPT_EXPORTABLE标志的CSP,那么请参见Maarten Bodewes在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.

如果您遵循这个CryptExportKey MSDN链接,您将看到一个示例函数,显示如何使用该函数来导出它们在普通文档中的关键材料。如果需要,也可以使用另一个密钥(即通过另一个密钥加密)导出密钥,通过将密钥句柄传递给CryptExportKey的hExpKey(第二个参数)。将NULL传递给平原中的这个参数导出。

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:

我更新了您的示例程序(如下),以便使用上面MSDN链接的示例代码导出密钥,并使用CryptBinaryToString函数将密钥材料打印为base64字符串。我把它导出为PLAINTEXTKEYBLOB类型,它使用数据结构BLOBHEADER|key length|key material所以我们还需要做一些工作从这个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;
}

#1


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.

一般来说,这些方法都是用这样一种方式创建的,如果不是不可能,那么检索结果的秘密是很困难的。但是,在CryptDeriveKey的API文档的备注部分中描述了导出密钥的方法。所以你可以重新创建你有基本数据。

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.

API没有描述如果使用SHA-2会发生什么,但是我假设它只是使用SHA-256的最左位作为键。

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:

让n为所需的派生密钥长度,以字节为单位。派生键是隐式派生键完成哈希计算后哈希值的前n个字节。如果哈希不是SHA-2家族的成员,而所需的键是3DES或AES的,则该键派生如下:

  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. 通过重复常量0x3664次来形成一个64字节的缓冲区。设k是由输入参数hBaseData表示的哈希值的长度。将缓冲区的前k个字节设置为缓冲区的前k个字节的XOR操作的结果,并使用输入参数hBaseData表示的散列值。
  3. 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.
  4. 通过重复常量0x5C 64次,形成一个64字节的缓冲区。将缓冲区的前k个字节设置为缓冲区的前k个字节的XOR操作的结果,并使用输入参数hBaseData表示的散列值。
  5. 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.
  6. 使用与计算由hBaseData参数表示的哈希值相同的哈希算法对步骤1的结果进行哈希。
  7. 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.
  8. 使用与计算由hBaseData参数表示的哈希值相同的哈希算法对步骤2的结果进行哈希。
  9. Concatenate the result of step 3 with the result of step 4.
  10. 将步骤3的结果与步骤4的结果连接起来。
  11. Use the first n bytes of the result of step 5 as the derived key.
  12. 使用第5步结果的前n个字节作为派生键。

#2


1  

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.

由于您已经通过将CRYPT_EXPORTABLE传递给CryptDeriveKey函数使派生密钥可导出,因此可以使用CryptExportKey函数来导出派生的密钥材料。

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.

例外情况是,如果您使用的是不允许键导出的第三方CSP,即使是带有CRYPT_EXPORTABLE标志的CSP,那么请参见Maarten Bodewes在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.

如果您遵循这个CryptExportKey MSDN链接,您将看到一个示例函数,显示如何使用该函数来导出它们在普通文档中的关键材料。如果需要,也可以使用另一个密钥(即通过另一个密钥加密)导出密钥,通过将密钥句柄传递给CryptExportKey的hExpKey(第二个参数)。将NULL传递给平原中的这个参数导出。

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:

我更新了您的示例程序(如下),以便使用上面MSDN链接的示例代码导出密钥,并使用CryptBinaryToString函数将密钥材料打印为base64字符串。我把它导出为PLAINTEXTKEYBLOB类型,它使用数据结构BLOBHEADER|key length|key material所以我们还需要做一些工作从这个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;
}