非对称加密算是CNG的重头戏,主要包括,数字验证、信息加密、共享密钥协议等,其中引入了NCtypto系列的函数用于非对称密钥的持久化保存与读取,这篇先以数字验证为例梳理一下,非对称密钥的使用,及导入导出。下一篇同样以数字验证为案例讲一下,非对称密钥导入导出时的持久化保存问题(即BCrypto生成的密钥导出后通过NCrypto保存),之后会再出一篇谈谈与OpenSSL3之间的密钥格式转换及相互签名及验证
一、一般流程
(一)对需要签名的数据进行哈希算法,具体参照,这里不做详述《Cryptography API: Next Generation(CNG)使用梳理——Hash及随机数算法应用》
(二)签名流程(NCtypto签名,BCtypto验证签名)
1、获取算法提供者句柄(NCtypto)
2、创建非对称密钥对
3、完成(NCtypto系列可以在本地固定目录保存密钥对)密钥对
4、对已经Hash的数据进行签名
5、导出公钥(NCtypto)
6、获取算法提供程序(BCtypto)
7、导入公钥(BCtypto)
8、验证签名
需要使用到的函数:
//打开支持所需算法的算法提供程序(NCrypt)
SECURITY_STATUS NCryptOpenStorageProvider(
[out] NCRYPT_PROV_HANDLE *phProvider,
[in, optional] LPCWSTR pszProviderName,
[in] DWORD dwFlags
);
//创建一个新的密钥对,并将其存储在指定的密钥存储提供程序中
SECURITY_STATUS NCryptCreatePersistedKey(
[in] NCRYPT_PROV_HANDLE hProvider,
[out] NCRYPT_KEY_HANDLE *phKey,
[in] LPCWSTR pszAlgId,
[in, optional] LPCWSTR pszKeyName,
[in] DWORD dwLegacyKeySpec,
[in] DWORD dwFlags
);
//设置密钥对属性,但在调用 NCryptFinalizeKey 函数之前,不能使用该键
//比如:设置私钥可以导出
SECURITY_STATUS NCryptSetProperty(
[in] NCRYPT_HANDLE hObject,
[in] LPCWSTR pszProperty,
[in] PBYTE pbInput,
[in] DWORD cbInput,
[in] DWORD dwFlags
);
//完成密钥存储。 在调用此函数之前,不能使用该密钥对
SECURITY_STATUS NCryptFinalizeKey(
[in] NCRYPT_KEY_HANDLE hKey,
[in] DWORD dwFlags
);
//创建哈希值的签名
SECURITY_STATUS NCryptSignHash(
[in] NCRYPT_KEY_HANDLE hKey,
[in, optional] VOID *pPaddingInfo,
[in] PBYTE pbHashValue,
[in] DWORD cbHashValue,
[out] PBYTE pbSignature,
[in] DWORD cbSignature,
[out] DWORD *pcbResult,
[in] DWORD dwFlags
);
//密钥导出到内存BLOB
SECURITY_STATUS NCryptExportKey(
[in] NCRYPT_KEY_HANDLE hKey,
[in, optional] NCRYPT_KEY_HANDLE hExportKey,
[in] LPCWSTR pszBlobType,
[in, optional] NCryptBufferDesc *pParameterList,
[out, optional] PBYTE pbOutput,
[in] DWORD cbOutput,
[out] DWORD *pcbResult,
[in] DWORD dwFlags
);
//打开支持所需算法的算法提供程序(BCrypt)
NTSTATUS BCryptOpenAlgorithmProvider(
[out] BCRYPT_ALG_HANDLE *phAlgorithm,
[in] LPCWSTR pszAlgId,
[in] LPCWSTR pszImplementation,
[in] ULONG dwFlags
);
//从 BLOB 中导入公钥或私钥
NTSTATUS BCryptImportKeyPair(
[in] BCRYPT_ALG_HANDLE hAlgorithm,
[in, out] BCRYPT_KEY_HANDLE hImportKey,
[in] LPCWSTR pszBlobType,
[out] BCRYPT_KEY_HANDLE *phKey,
[in] PUCHAR pbInput,
[in] ULONG cbInput,
[in] ULONG dwFlags
);
//删除保存在本地的密钥对文件,同时释放句柄
//如果 NCryptDeleteKey 失败,应用程序可以使用 NCryptFreeObject 函数释放句柄。
SECURITY_STATUS NCryptDeleteKey(
[in] NCRYPT_KEY_HANDLE hKey,
[in] DWORD dwFlags
);
//验证指定的签名是否与指定的哈希匹配
NTSTATUS BCryptVerifySignature(
[in] BCRYPT_KEY_HANDLE hKey,
[in, optional] VOID *pPaddingInfo,
[in] PUCHAR pbHash,
[in] ULONG cbHash,
[in] PUCHAR pbSignature,
[in] ULONG cbSignature,
[in] ULONG dwFlags
);
实例:(代码根据官方演示案例修改而来,用于演示流程,因此去掉了返回校验过程)
#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)
static const BYTE rgbMsg[] =
{
0x04, 0x87, 0xec, 0x66, 0xa8, 0xbf, 0x17, 0xa6,
0xe3, 0x62, 0x6f, 0x1a, 0x55, 0xe2, 0xaf, 0x5e,
0xbc, 0x54, 0xa4, 0xdc, 0x68, 0x19, 0x3e, 0x94,
};
int main()
{
NCRYPT_PROV_HANDLE hProv = NULL;
NCRYPT_KEY_HANDLE hKey = NULL;
BCRYPT_KEY_HANDLE hTmpKey = NULL;
BCRYPT_ALG_HANDLE hHashAlg = NULL,
hSignAlg = NULL;
BCRYPT_HASH_HANDLE hHash = NULL;
NTSTATUS status = STATUS_UNSUCCESSFUL;
DWORD cbData = 0,
cbHash = 0,
cbBlob = 0,
cbSignature = 0,
cbHashObject = 0;
PBYTE pbHashObject = NULL;
PBYTE pbHash = NULL,
pbBlob = NULL,
pbSignature = NULL;
//获取数据的哈希值,具体参照《Cryptography API: Next Generation(CNG)使用梳理——Hash及随机数算法应用》
BCryptOpenAlgorithmProvider(&hHashAlg, BCRYPT_SHA1_ALGORITHM, NULL, 0);
BCryptGetProperty(hHashAlg, BCRYPT_OBJECT_LENGTH, (PBYTE)&cbHashObject, sizeof(DWORD), &cbData, 0);
pbHashObject = (PBYTE)HeapAlloc (GetProcessHeap (), 0, cbHashObject);
BCryptGetProperty(hHashAlg, BCRYPT_HASH_LENGTH, (PBYTE)&cbHash, sizeof(DWORD), &cbData, 0);
pbHash = (PBYTE)HeapAlloc (GetProcessHeap (), 0, cbHash);
BCryptCreateHash(hHashAlg, &hHash, pbHashObject, cbHashObject, NULL, 0, 0);
BCryptHashData(hHash, (PBYTE)rgbMsg, sizeof(rgbMsg), 0);
BCryptFinishHash(hHash, pbHash, cbHash, 0);
//完成获取数据的哈希值
//加载并初始化 CNG 密钥存储提供程序
NCryptOpenStorageProvider(&hProv, MS_KEY_STORAGE_PROVIDER, 0);
//创建一个新密钥对
NCryptCreatePersistedKey(hProv, &hKey, NCRYPT_ECDSA_P256_ALGORITHM, L"my ecc key", 0, 0);
//完成密钥对(并存储)
NCryptFinalizeKey(hKey, 0);
//创建哈希值的签名
NCryptSignHash(hKey, NULL, pbHash, cbHash, NULL, 0, &cbSignature, 0);
//分配存储签名所需空间
pbSignature = (PBYTE)HeapAlloc (GetProcessHeap (), 0, cbSignature);
//对哈希值签名
NCryptSignHash(hKey, NULL, pbHash, cbHash, pbSignature, cbSignature, &cbSignature, 0);
//导出公钥,获取导出公钥所需空间大小
NCryptExportKey(hKey, NULL, BCRYPT_ECCPUBLIC_BLOB, NULL, NULL, 0, &cbBlob, 0);
//分配公钥所需要空间
pbBlob = (PBYTE)HeapAlloc (GetProcessHeap (), 0, cbBlob);
//导出公钥
NCryptExportKey(hKey, NULL, BCRYPT_ECCPUBLIC_BLOB, NULL, pbBlob, cbBlob, &cbBlob, 0);
//加载并初始化BCrypt提供程序
BCryptOpenAlgorithmProvider(&hSignAlg, BCRYPT_ECDSA_P256_ALGORITHM, NULL, 0);
//导入公钥
BCryptImportKeyPair(hSignAlg, NULL, BCRYPT_ECCPUBLIC_BLOB, &hTmpKey, pbBlob, cbBlob, 0);
//验证指定的签名是否与指定的哈希匹配
if(!NT_SUCCESS(status = BCryptVerifySignature(hTmpKey,
NULL,
pbHash,
cbHash,
pbSignature,
cbSignature,
0)))
{
wprintf(L"**** Error 0x%x returned by BCryptVerifySignature\n", status);
}
else {
wprintf(L"Success!\n");
}
if(hHashAlg) {
BCryptCloseAlgorithmProvider(hHashAlg,0);
}
if(hSignAlg) {
BCryptCloseAlgorithmProvider(hSignAlg,0);
}
if (hHash) {
BCryptDestroyHash(hHash);
}
if(pbHashObject) {
HeapFree(GetProcessHeap(), 0, pbHashObject);
}
if(pbHash) {
HeapFree(GetProcessHeap(), 0, pbHash);
}
if(pbSignature) {
HeapFree(GetProcessHeap(), 0, pbSignature);
}
if(pbBlob) {
HeapFree(GetProcessHeap(), 0, pbBlob);
}
if (hTmpKey) {
BCryptDestroyKey(hTmpKey);
}
if (hKey) {
NCryptDeleteKey(hKey, 0);
}
if (hProv) {
NCryptFreeObject(hProv);
}
return 0;
}
这小单元最后以RSA签名为例,说一下加密填充的情况
NCryptSignHash和BCryptSignHash中pPaddingInfo参数用于设置填充信息的结构的指针。 此参数指向的实际结构类型取决于 dwFlags 参数的值。 此参数仅用于非对称密钥,否则必须为 NULL 。
dwFlags 参数的值
BCRYPT_PAD_PKCS1使用 PKCS1 填充方案。 pPaddingInfo 参数是指向BCRYPT_PKCS1_PADDING_INFO结构的指针。
BCRYPT_PAD_PSS使用概率签名方案 (PSS) 填充方案。 pPaddingInfo 参数是指向BCRYPT_PSS_PADDING_INFO结构的指针。
BCRYPT_PAD_PKCS1和BCRYPT_PAD_PSS都是为RSA准备的,而其它,如ECDH_P256,不能使用填充
BCRYPT_PAD_PKCS1为PKCS#1 v1.5标准,BCRYPT_PAD_PSS为PKCS#1 v2.1标准,前者有安全隐患,后者更好
RSA签名时,内部已经默认使用BCRYPT_PAD_PKCS1填充了填充算法需要与签名的哈希算法相匹配,如果哈希为SHA256时,则BCRYPT_PAD_PKCS1填充SHA256哈希函数
以BCRYPT_PAD_PKCS1为例,只需将BCRYPT_PKCS1_PADDING_INFO中的pszAlgId设置为一个哈希算法
typedef struct _BCRYPT_PKCS1_PADDING_INFO {
LPCWSTR pszAlgId;
} BCRYPT_PKCS1_PADDING_INFO;
代码如下:
#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)
static const BYTE rgbMsg[] =
{
0x04, 0x87, 0xec, 0x66, 0xa8, 0xbf, 0x17, 0xa6,
0xe3, 0x62, 0x6f, 0x1a, 0x55, 0xe2, 0xaf, 0x5e,
0xbc, 0x54, 0xa4, 0xdc, 0x68, 0x19, 0x3e, 0x94,
};
int main()
{
NCRYPT_PROV_HANDLE hProv = NULL;
NCRYPT_KEY_HANDLE hKey = NULL;
BCRYPT_KEY_HANDLE hTmpKey = NULL;
BCRYPT_ALG_HANDLE hHashAlg = NULL,
hSignAlg = NULL;
BCRYPT_HASH_HANDLE hHash = NULL;
NTSTATUS status = STATUS_UNSUCCESSFUL;
DWORD cbData = 0,
cbHash = 0,
cbBlob = 0,
cbSignature = 0,
cbHashObject = 0,
cbKeyLength = 2048;
PBYTE pbHashObject = NULL;
PBYTE pbHash = NULL,
pbBlob = NULL,
pbSignature = NULL;
//获取数据的哈希值,具体参照《Cryptography API: Next Generation(CNG)使用梳理——Hash及随机数算法应用》
BCryptOpenAlgorithmProvider(&hHashAlg, BCRYPT_SHA256_ALGORITHM, NULL, 0);
BCryptGetProperty(hHashAlg, BCRYPT_OBJECT_LENGTH, (PBYTE)&cbHashObject, sizeof(DWORD), &cbData, 0);
pbHashObject = (PBYTE)HeapAlloc (GetProcessHeap (), 0, cbHashObject);
BCryptGetProperty(hHashAlg, BCRYPT_HASH_LENGTH, (PBYTE)&cbHash, sizeof(DWORD), &cbData, 0);
pbHash = (PBYTE)HeapAlloc (GetProcessHeap (), 0, cbHash);
BCryptCreateHash(hHashAlg, &hHash, pbHashObject, cbHashObject, NULL, 0, 0);
BCryptHashData(hHash, (PBYTE)rgbMsg, sizeof(rgbMsg), 0);
BCryptFinishHash(hHash, pbHash, cbHash, 0);
//完成获取数据的哈希值
//加载并初始化 CNG 密钥存储提供程序
NCryptOpenStorageProvider(&hProv, MS_KEY_STORAGE_PROVIDER, 0);
//创建一个新密钥对
NCryptCreatePersistedKey(hProv, &hKey, NCRYPT_RSA_ALGORITHM, L"my rsa key", 0, 0);
//设置密钥长度
NCryptSetProperty(hKey, NCRYPT_LENGTH_PROPERTY, (PBYTE)&cbKeyLength, sizeof(DWORD), 0);
//完成密钥对(并存储)
NCryptFinalizeKey(hKey, 0);
//设置填充,需要和上面对数据获取哈希值的算法相同,不然无法通过
BCRYPT_PKCS1_PADDING_INFO bPaddingInfo = {BCRYPT_SHA256_ALGORITHM};
//获取哈希值签名所需空间大小
NCryptSignHash(hKey, &bPaddingInfo, pbHash, cbHash, NULL, 0, &cbSignature, BCRYPT_PAD_PKCS1);
//分配存储签名所需空间
pbSignature = (PBYTE)HeapAlloc (GetProcessHeap (), 0, cbSignature);
//对哈希值签名
NCryptSignHash(hKey, &bPaddingInfo, pbHash, cbHash, pbSignature, cbSignature, &cbSignature, BCRYPT_PAD_PKCS1);
//导出公钥,获取导出公钥所需空间大小
NCryptExportKey(hKey, NULL, BCRYPT_RSAPUBLIC_BLOB, NULL, NULL, 0, &cbBlob, 0);
//分配公钥所需要空间
pbBlob = (PBYTE)HeapAlloc (GetProcessHeap (), 0, cbBlob);
//导出公钥
NCryptExportKey(hKey, NULL, BCRYPT_RSAPUBLIC_BLOB, NULL, pbBlob, cbBlob, &cbBlob, 0);
//加载并初始化BCrypt提供程序
BCryptOpenAlgorithmProvider(&hSignAlg, BCRYPT_RSA_ALGORITHM, NULL, 0);
//导入公钥
BCryptImportKeyPair(hSignAlg, NULL, BCRYPT_RSAPUBLIC_BLOB, &hTmpKey, pbBlob, cbBlob, 0);
//验证指定的签名是否与指定的哈希匹配
if(!NT_SUCCESS(status = BCryptVerifySignature(hTmpKey,
&bPaddingInfo,
pbHash,
cbHash,
pbSignature,
cbSignature,
BCRYPT_PAD_PKCS1)))
{
wprintf(L"**** Error 0x%x returned by BCryptVerifySignature\n", status);
}
else {
wprintf(L"Success!\n");
}
if(hHashAlg) {
BCryptCloseAlgorithmProvider(hHashAlg,0);
}
if(hSignAlg) {
BCryptCloseAlgorithmProvider(hSignAlg,0);
}
if (hHash) {
BCryptDestroyHash(hHash);
}
if(pbHashObject) {
HeapFree(GetProcessHeap(), 0, pbHashObject);
}
if(pbHash) {
HeapFree(GetProcessHeap(), 0, pbHash);
}
if(pbSignature) {
HeapFree(GetProcessHeap(), 0, pbSignature);
}
if(pbBlob) {
HeapFree(GetProcessHeap(), 0, pbBlob);
}
if (hTmpKey) {
BCryptDestroyKey(hTmpKey);
}
if (hKey) {
NCryptDeleteKey(hKey, 0);
}
if (hProv) {
NCryptFreeObject(hProv);
}
return 0;
}