CNG中NCrypt创建的密钥对会默认会保存在本地,而BCrypt创建的密钥对并不会保存
NCrypt创建的密钥默认不能导出私钥
此篇主要聊一下,
一、如何保存BCrypt创建并导出的私钥(公钥能导入CNG但不能保存)
二、RSA公钥加密私钥解密(BCRYPT_PAD_PKCS1填充)
三、导出(备份)NCrypt创建的密钥(私钥)
另附:RSA公钥加密私钥解密(BCRYPT_PAD_OAEP填充)代码
理论上基于BCrypt导出密钥的内存BLOB结构,任何其它加密导出的密钥都是可以被导入的,后面我会单独写一篇导入OpenSSL3生成的密钥(RSA和ECC)与CNG内存BLOB结构之间做转换
关于导入持久化密钥,MSDN中导入持久化密钥的流程为:
使用 NCryptCreatePersistedKey 函数创建持久密钥。
使用 NCryptSetProperty 函数设置键对象上的任何所需属性。
将导入密钥 BLOB 设置为密钥上的属性,将 BLOB 类型设置为属性名称。
使用 NCryptFinalizeKey 函数完成持久密钥导入。
此流程或许我解读有误,实际操作后没有成功,一下为我成功的流程
以RSA加密(公钥加密,私钥解密)为例,基本流程
1、生成密钥对(BCrypt)
2、加密数据
3、导出私钥
4、导入私钥(NCrypt),非私钥NCrypt不会保存
5、解密
相关函数和结构
//导入密钥函数
SECURITY_STATUS NCryptImportKey(
[in] NCRYPT_PROV_HANDLE hProvider,
[in, optional] NCRYPT_KEY_HANDLE hImportKey,
[in] LPCWSTR pszBlobType,
[in, optional] NCryptBufferDesc *pParameterList,
[out] NCRYPT_KEY_HANDLE *phKey,
[in] PBYTE pbData,
[in] DWORD cbData,
[in] DWORD dwFlags
);
//NCryptBufferDesc结构,NCryptBufferDesc结构为BCryptBufferDesc结构的别名
typedef struct _BCryptBufferDesc {
ULONG ulVersion;
ULONG cBuffers;
PBCryptBuffer pBuffers;
} BCryptBufferDesc, *PBCryptBufferDesc;
//BCryptBuffer结构,部分MSDN中使用NCryptBuffer来描述,实际NCryptBuffer为此结构的别名
typedef struct _BCryptBuffer {
ULONG cbBuffer;
ULONG BufferType;
PVOID pvBuffer;
} BCryptBuffer, *PBCryptBuffer;
NCryptBufferDesc结构很简单
ulVersion 结构的版本
BCRYPTBUFFER_VERSION 默认版本号。
cBuffers pBuffers数组中的元素数。
pBuffers 包含缓冲区的 BCryptBuffer 结构的数组的地址。 cBuffers 包含此数组中的元素数。
而NCryptBuffer(BCryptBuffer)就比较复杂了,根据不同应用场景,具体参数不同,此处主要填写BufferType、pvBuffer、cbBuffer,分别对应NCryptBuffer(BCryptBuffer)内容的类型、密钥名字和密钥名字长度(包含字符串最后的\0结束符)
BufferType = NCRYPTBUFFER_PKCS_KEY_NAME;//设置密钥名
pvBuffer = (PVOID)pszKeyName;
cbBuffer = (::lstrlenW(pszKeyName) + 1) * sizeof(wchar_t);//需要包含\0
实际代码如下:(只包含基本流程的代码,并未对每个函数的返回值进行判断)
#include <windows.h>
#include <stdio.h>
#include <bcrypt.h>
#include <ncrypt.h>
#pragma comment(lib,"Bcrypt.lib")
#pragma comment(lib,"Ncrypt.lib")
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
#define STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001L)
const BYTE rgbPlaintext[] =
{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F
};
int main()
{
BCRYPT_ALG_HANDLE hRSAAlg = NULL;
NCRYPT_PROV_HANDLE hProv = NULL;
BCRYPT_KEY_HANDLE hKey = NULL;
NCRYPT_KEY_HANDLE hTmpKey = NULL;
NTSTATUS status = STATUS_UNSUCCESSFUL;
DWORD cbData = 0,
cbBlob = 0,
cbPlainText = 0,
cbCipherText = 0,
cbKeyLength = 2048;
PBYTE pbPlainText = NULL,
pbBlob = NULL,
pbCipherText = NULL;
//加载并初始化BCrypt提供程序
BCryptOpenAlgorithmProvider(&hRSAAlg, BCRYPT_RSA_ALGORITHM, NULL, 0);
//创建密钥对
BCryptGenerateKeyPair(hRSAAlg, &hKey, cbKeyLength, 0);
//完成
BCryptFinalizeKeyPair(hKey, 0);
// 复制明文
cbPlainText = sizeof(rgbPlaintext);
pbPlainText = (PBYTE)HeapAlloc (GetProcessHeap (), 0, cbPlainText);
memcpy(pbPlainText, rgbPlaintext, sizeof(rgbPlaintext));
// 获取密文所需空间
BCryptEncrypt(hKey, pbPlainText, cbPlainText, NULL, NULL, 0, NULL, 0, &cbCipherText, BCRYPT_PAD_PKCS1);
// 分配密文所需空间
pbCipherText = (PBYTE)HeapAlloc (GetProcessHeap (), 0, cbCipherText);
// 加密数据(填充方式为BCRYPT_PAD_PKCS1)
BCryptEncrypt(hKey, pbPlainText, cbPlainText, NULL, NULL, 0, pbCipherText, cbCipherText, &cbData, BCRYPT_PAD_PKCS1);
// 获取导出密钥所需大小(此处导出私钥)
BCryptExportKey(hKey, NULL, BCRYPT_RSAPRIVATE_BLOB, NULL, 0, &cbBlob, 0);
// 为导出密钥分配空间
pbBlob = (PBYTE)HeapAlloc (GetProcessHeap (), 0, cbBlob);
// 导出密钥
BCryptExportKey(hKey, NULL, BCRYPT_RSAPRIVATE_BLOB, pbBlob, cbBlob, &cbBlob, 0);
// 销毁当前密钥
if(hKey) {
BCryptDestroyKey(hKey);
}
if(hRSAAlg) {
BCryptCloseAlgorithmProvider(hRSAAlg, 0);
}
//加载并初始化 CNG 密钥存储提供程序
NCryptOpenStorageProvider(&hProv, MS_KEY_STORAGE_PROVIDER, 0);
LPCWSTR pszKeyName = L"my rsa key";
NCryptBuffer cb[1];
cb[0].BufferType = NCRYPTBUFFER_PKCS_KEY_NAME;//设置密钥名
cb[0].pvBuffer = (PVOID)pszKeyName;
cb[0].cbBuffer = (::lstrlenW(pszKeyName) + 1) * sizeof(wchar_t);//需要包含\0
NCryptBufferDesc desc;
desc.ulVersion = NCRYPTBUFFER_VERSION;//版本,NCRYPTBUFFER_VERSION = 0
desc.pBuffers = cb;
desc.cBuffers = 1;
//导入密钥,默认自动保存,如果需要设置属性,可以这是最后的标志为NCRYPT_DO_NOT_FINALIZE_FLAG
NCryptImportKey(hProv, NULL, BCRYPT_RSAPRIVATE_BLOB, &desc, &hTmpKey, pbBlob, cbBlob, 0);
//如果设置了NCRYPT_DO_NOT_FINALIZE_FLAG,则需要手动调用NCryptFinalizeKey(此处不需要)
//NCryptFinalizeKey(hTmpKey, 0);
// 清理原先复制的明文空间
HeapFree(GetProcessHeap(), 0, pbPlainText);
pbPlainText = NULL;
// 获取解密后的明文大小,需要设置与加密时一样的填充方式
NCryptDecrypt(hTmpKey, pbCipherText, cbCipherText, NULL, NULL, 0, &cbPlainText, BCRYPT_PAD_PKCS1);
// 分配明文所需空间
pbPlainText = (PBYTE)HeapAlloc (GetProcessHeap (), 0, cbPlainText);
// 解密
NCryptDecrypt(hTmpKey, pbCipherText, cbCipherText, NULL, pbPlainText, cbPlainText, &cbData, BCRYPT_PAD_PKCS1);
//验证指定的签名是否与指定的哈希匹配
if (0 != memcmp(pbPlainText, (PBYTE)rgbPlaintext, sizeof(rgbPlaintext))) {
wprintf(L"Error Expected decrypted text comparison failed.\n");
}
else {
wprintf(L"Success!\n");
}
if(pbBlob) {
HeapFree(GetProcessHeap(), 0, pbBlob);
}
if(pbCipherText) {
HeapFree(GetProcessHeap(), 0, pbCipherText);
}
if(pbPlainText) {
HeapFree(GetProcessHeap(), 0, pbPlainText);
}
if (hTmpKey) {
NCryptDeleteKey(hTmpKey, 0);
}
if (hProv) {
NCryptFreeObject(hProv);
}
return 0;
}
最后关于NCrypt生成的密钥对导出,默认情况下NCrypt不允许导出私钥,如果需要导出则需要对NCRYPT_EXPORT_POLICY_PROPERTY设置对应属性,比如NCRYPT_ALLOW_ARCHIVING_FLAG | NCRYPT_ALLOW_PLAINTEXT_ARCHIVING_FLAG,这两个属性只能应用于原始密钥句柄。 关闭密钥句柄后,无法再出于存档目的导出密钥
NCryptCreatePersistedKey(hNCryptProv, &hKey, CryptType::get_alg_id(), pszKeyName, 0, 0);
DWORD dwPolicy = NCRYPT_ALLOW_ARCHIVING_FLAG | NCRYPT_ALLOW_PLAINTEXT_ARCHIVING_FLAG;
::NCryptSetProperty(hKey, NCRYPT_EXPORT_POLICY_PROPERTY, (PBYTE)&dwPolicy, sizeof(DWORD), 0);
NCryptFinalizeKey(hKey, 0);
NCRYPT_ALLOW_EXPORT_FLAG | NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG外加NCRYPT_PERSIST_FLAG标志的话,就可以将属性保存到密钥文件中去了,实现永久允许导出的目的
NCryptCreatePersistedKey(hNCryptProv, &hKey, CryptType::get_alg_id(), pszKeyName, 0, 0);
DWORD dwPolicy = NCRYPT_ALLOW_EXPORT_FLAG | NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG;
::NCryptSetProperty(hKey, NCRYPT_EXPORT_POLICY_PROPERTY, (PBYTE)&dwPolicy, sizeof(DWORD), NCRYPT_PERSIST_FLAG);
NCryptFinalizeKey(hKey, 0);
另附,使用BCRYPT_OAEP_PADDING作为加密填充的代码:
#include <windows.h>
#include <stdio.h>
#include <bcrypt.h>
#include <ncrypt.h>
#pragma comment(lib,"Bcrypt.lib")
#pragma comment(lib,"Ncrypt.lib")
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
#define STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001L)
const BYTE rgbPlaintext[] =
{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F
};
int main()
{
BCRYPT_ALG_HANDLE hRSAAlg = NULL;
NCRYPT_PROV_HANDLE hProv = NULL;
BCRYPT_KEY_HANDLE hKey = NULL;
NCRYPT_KEY_HANDLE hTmpKey = NULL;
NTSTATUS status = STATUS_UNSUCCESSFUL;
DWORD cbData = 0,
cbBlob = 0,
cbPlainText = 0,
cbCipherText = 0,
cbKeyLength = 2048;
PBYTE pbPlainText = NULL,
pbBlob = NULL,
pbCipherText = NULL;
//加载并初始化BCrypt提供程序
BCryptOpenAlgorithmProvider(&hRSAAlg, BCRYPT_RSA_ALGORITHM, NULL, 0);
//创建密钥对
BCryptGenerateKeyPair(hRSAAlg, &hKey, cbKeyLength, 0);
//完成
BCryptFinalizeKeyPair(hKey, 0);
// 复制明文
cbPlainText = sizeof(rgbPlaintext);
pbPlainText = (PBYTE)HeapAlloc (GetProcessHeap (), 0, cbPlainText);
memcpy(pbPlainText, rgbPlaintext, sizeof(rgbPlaintext));
//使用BCRYPT_OAEP_PADDING填充
BYTE buffer[32] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F};
BCRYPT_OAEP_PADDING_INFO padding;
padding.pszAlgId = BCRYPT_SHA256_ALGORITHM;
padding.pbLabel = buffer;
padding.cbLabel = sizeof(buffer);
// 获取密文所需空间
BCryptEncrypt(hKey, pbPlainText, cbPlainText, &padding, NULL, 0, NULL, 0, &cbCipherText, BCRYPT_PAD_OAEP);
// 分配密文所需空间
pbCipherText = (PBYTE)HeapAlloc (GetProcessHeap (), 0, cbCipherText);
// 加密数据(填充方式为BCRYPT_PAD_PKCS1)
BCryptEncrypt(hKey, pbPlainText, cbPlainText, &padding, NULL, 0, pbCipherText, cbCipherText, &cbData, BCRYPT_PAD_OAEP);
// 获取导出密钥所需大小
BCryptExportKey(hKey, NULL, BCRYPT_RSAPRIVATE_BLOB, NULL, 0, &cbBlob, 0);
// 为导出密钥分配空间
pbBlob = (PBYTE)HeapAlloc (GetProcessHeap (), 0, cbBlob);
// 导出密钥
BCryptExportKey(hKey, NULL, BCRYPT_RSAPRIVATE_BLOB, pbBlob, cbBlob, &cbBlob, 0);
// 销毁当前密钥
if(hKey) {
BCryptDestroyKey(hKey);
}
if(hRSAAlg) {
BCryptCloseAlgorithmProvider(hRSAAlg, 0);
}
//加载并初始化 CNG 密钥存储提供程序
NCryptOpenStorageProvider(&hProv, MS_KEY_STORAGE_PROVIDER, 0);
LPCWSTR pszKeyName = L"my rsa key";
NCryptBuffer cb[1];
cb[0].BufferType = NCRYPTBUFFER_PKCS_KEY_NAME;//设置密钥名
cb[0].pvBuffer = (PVOID)pszKeyName;
cb[0].cbBuffer = (::lstrlenW(pszKeyName) + 1) * sizeof(wchar_t);//需要包含\0
NCryptBufferDesc desc;
desc.ulVersion = NCRYPTBUFFER_VERSION;//版本,NCRYPTBUFFER_VERSION = 0
desc.pBuffers = cb;
desc.cBuffers = 1;
//导入密钥,默认自动保存,如果需要设置属性,可以这是最后的标志为NCRYPT_DO_NOT_FINALIZE_FLAG
NCryptImportKey(hProv, NULL, BCRYPT_RSAPRIVATE_BLOB, &desc, &hTmpKey, pbBlob, cbBlob, 0);
//如果设置了NCRYPT_DO_NOT_FINALIZE_FLAG,则需要手动调用NCryptFinalizeKey(此处不需要)
//NCryptFinalizeKey(hTmpKey, 0);
// 清理原先复制的明文空间
HeapFree(GetProcessHeap(), 0, pbPlainText);
pbPlainText = NULL;
// 获取解密后的明文大小,需要设置与加密时一样的填充方式
NCryptDecrypt(hTmpKey, pbCipherText, cbCipherText, &padding, NULL, 0, &cbPlainText, NCRYPT_PAD_OAEP_FLAG);
// 分配明文所需空间
pbPlainText = (PBYTE)HeapAlloc (GetProcessHeap (), 0, cbPlainText);
// 解密
NCryptDecrypt(hTmpKey, pbCipherText, cbCipherText, &padding, pbPlainText, cbPlainText, &cbData, NCRYPT_PAD_OAEP_FLAG);
//验证指定的签名是否与指定的哈希匹配
if (0 != memcmp(pbPlainText, (PBYTE)rgbPlaintext, sizeof(rgbPlaintext))) {
wprintf(L"Error Expected decrypted text comparison failed.\n");
}
else {
wprintf(L"Success!\n");
}
if(pbBlob) {
HeapFree(GetProcessHeap(), 0, pbBlob);
}
if(pbCipherText) {
HeapFree(GetProcessHeap(), 0, pbCipherText);
}
if(pbPlainText) {
HeapFree(GetProcessHeap(), 0, pbPlainText);
}
if (hTmpKey) {
NCryptDeleteKey(hTmpKey, 0);
}
if (hProv) {
NCryptFreeObject(hProv);
}
return 0;
}