如何导出使用 CryptoAPI 派生的 AES 密钥
How to export AES key derived using CryptoAPI
我想使用 Windows CryptoAPI 函数进行 AES 加密。
如您所知,API 具有 a lot of functions 来创建、散列和更改输入的密钥;它派生出密钥,你就得到了它的句柄。
我的问题是我想知道派生密钥是什么?
#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;
}
编辑:如果密钥推导是在软件中进行的,那么 可能更好。所以这是一个更通用的答案,应该是首选。如果软件方法失败,您可以使用此重放方法。如果令牌不允许明文导出,则可能是这种情况。
一般来说,这些方法的创建方式即使不是不可能,也很难检索生成的秘密。但是,描述了导出密钥的方法in the Remarks section of the API documentation of CryptDeriveKey
。因此,您可以重播您拥有基础数据的创建过程。
API 没有描述如果使用 SHA-2 会发生什么,但我认为它只是使用 SHA-256 结果的最左边的位作为密钥。
推导之后你当然可以通过encrypting/decrypting或者一些数据来测试
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:
- 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
.
- 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
.
- 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.
- 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.
- Concatenate the result of step 3 with the result of step 4.
- Use the first n bytes of the result of step 5 as the derived key.
因为您已通过将 CRYPT_EXPORTABLE
传递给 CryptDeriveKey
function, it is possible to use the CryptExportKey
function 来导出派生密钥 material.
,从而使派生密钥可导出
如果您使用的第三方 CSP 不允许密钥导出,即使带有 CRYPT_EXPORTABLE
标志,这种情况也是例外,在这种情况下,请参阅 在 CSP 之外重播密钥推导步骤。
如果您按照 CryptExportKey
MSDN link 进行操作,您会看到一个示例函数,展示了如何使用该函数导出它们的键值 material 。如果需要,也可以使用另一个密钥(即由另一个密钥加密)导出密钥 wrapped,方法是将密钥句柄传递给 hExpKey
(第二个参数) CryptExportKey
。将 NULL
传递给此参数以普通形式导出。
我已经更新了您的示例程序(如下)以使用上面 MSDN link 中的示例代码导出密钥,并使用 CryptBinaryToString
function to print the key material as a base64 string. I've exported as the PLAINTEXTKEYBLOB
blob 类型,它使用数据结构 BLOBHEADER|key length|key material
所以还需要做一些工作来从那个 blob 中提取我们感兴趣的原始密钥 material:
#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;
}
我想使用 Windows CryptoAPI 函数进行 AES 加密。
如您所知,API 具有 a lot of functions 来创建、散列和更改输入的密钥;它派生出密钥,你就得到了它的句柄。
我的问题是我想知道派生密钥是什么?
#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;
}
编辑:如果密钥推导是在软件中进行的,那么
一般来说,这些方法的创建方式即使不是不可能,也很难检索生成的秘密。但是,描述了导出密钥的方法in the Remarks section of the API documentation of CryptDeriveKey
。因此,您可以重播您拥有基础数据的创建过程。
API 没有描述如果使用 SHA-2 会发生什么,但我认为它只是使用 SHA-256 结果的最左边的位作为密钥。
推导之后你当然可以通过encrypting/decrypting或者一些数据来测试
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:
- 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 parameterhBaseData
. 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 parameterhBaseData
.- 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 parameterhBaseData
.- 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.- 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.- Concatenate the result of step 3 with the result of step 4.
- Use the first n bytes of the result of step 5 as the derived key.
因为您已通过将 CRYPT_EXPORTABLE
传递给 CryptDeriveKey
function, it is possible to use the CryptExportKey
function 来导出派生密钥 material.
如果您使用的第三方 CSP 不允许密钥导出,即使带有 CRYPT_EXPORTABLE
标志,这种情况也是例外,在这种情况下,请参阅
如果您按照 CryptExportKey
MSDN link 进行操作,您会看到一个示例函数,展示了如何使用该函数导出它们的键值 material 。如果需要,也可以使用另一个密钥(即由另一个密钥加密)导出密钥 wrapped,方法是将密钥句柄传递给 hExpKey
(第二个参数) CryptExportKey
。将 NULL
传递给此参数以普通形式导出。
我已经更新了您的示例程序(如下)以使用上面 MSDN link 中的示例代码导出密钥,并使用 CryptBinaryToString
function to print the key material as a base64 string. I've exported as the PLAINTEXTKEYBLOB
blob 类型,它使用数据结构 BLOBHEADER|key length|key material
所以还需要做一些工作来从那个 blob 中提取我们感兴趣的原始密钥 material:
#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;
}