Cryptography API: Next Generation(CNG)使用梳理——非对称加密算法应用(五)与OpenSSL数字签名认证的互通(ECDSA)

本文详细介绍了如何在CryptographyAPI:NextGeneration(CNG)和OpenSSL3之间进行ECDSA数字签名的互验证。主要涉及CNG的签名格式转换为OpenSSL3兼容的DER格式,以及OpenSSL3的签名转换回CNG的格式。同时,文章还涵盖了密钥对的转换,包括CNG公钥和私钥转换为OpenSSL格式,反之亦然。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

CNG中的数字签名算法主要包括RSA、DSA和ECDSA,其中RSA算法所得的签名值是可以直接与OpenSSL相互验证的,而DSA和ECDSA则不行,这是因为输出的格式不同

OpenSSL采用DER,而ECDSA在OpenSSL3之前可以使用ECDSA_do_sign获得ECDSA_SIG格式,但OpenSSL3以后的版本因为隐藏了底部实现,所以其数字签名函数只能使用EVP_SignXXXXX、EVP_Digest和EVP_PKEY_sign这些函数了,其输出的签名格式为DER

而CNG的格式就比较直接了,以ECDSA为例,其签名格式就是r值和s值的拼接

r[size / 2]
s[size / 2]

其中size代表签名的长度,以BCRYPT_ECDSA_P256_ALGORITHM(即ECDSA_P256)为例,其计算出的签名大小为64个字节,其中前32个字节为r的值(大端数值),后32个字节为s的值(大端数值)

转换方式就是解析DER格式提取r值和s值,而后拼接。

OpenSSL3->CNG

基本思路和方法有两种:

一、直接解码转换

1、解析DER格式,读取r值和s值

2、拼接转换成CNG格式

二、间接转换

1、将签名的DER格式,转换成ECDSA_SIG格式

2、读取ECDSA_SIG格式中的r值和s值

3、拼接转换成CNG格式

方法一、效率高,但是需要对DER格式有足够的认识,如果只是简单读取两个大数值的话,实现还是比较简单的,但考虑到容错、查错和后续格式变化等问题,无异于要间接实现一个DER解析库,处理起来其它挺繁琐的

简单介绍一下,DER格式,其数据以TLV表示数据

EVP_Sign:
	0x30, 0x45, 0x02, 0x21, 0x00, 0xa0, 0xd3, 0x0b, 0x0b, 0xda,
	0xb1, 0x74, 0x97, 0x15, 0x76, 0xa2, 0x0b, 0x34, 0x42, 0xb4,
	0xa3, 0xbe, 0xac, 0x20, 0xc2, 0xbb, 0x4c, 0x01, 0xcc, 0x86,
	0x88, 0x2d, 0xdb, 0x66, 0x24, 0x3e, 0x17, 0x02, 0x20, 0x74,
	0xd7, 0xa1, 0xad, 0x88, 0xb7, 0x0b, 0xb7, 0x3e, 0x96, 0xcc,
	0x60, 0x02, 0x54, 0x16, 0xd4, 0x20, 0xec, 0x0b, 0xbe, 0xa5,
	0x82, 0xbc, 0x78, 0xf9, 0x60, 0x85, 0xd7, 0x50, 0xb3, 0x75,
	0x4b,

0x30[total-length]0x02[R-length][R]0x02[S-length][S][sighash]

total-length: 共一字节,表示其后的字节序列长度,不包括[sighash]部分。注意这里的total-length表示的不是整个DER数字签名的长度,而是该[total-length]字段之后的字节长度,但是为了表述方便,下文中就用该字段表示整个签名长度来叙述。
R-length: 共一字节,表示r部分长度。
R: 任意长度的大端序编码R值。它必须对正整数使用尽可能短的编码,这意味着在开始时没有空字节,除非r的第一个字节大于等于0x80,在r前置0x00。
S-length: 共一字节,表示s部分长度。
S: 任意长度的大端序编码S值,和R用同样的规则。
sighash: 一字节长度,该标志指定签名签署交易的哪个部分。

● 0x30表示DER序列的开始
● 0x45 - 序列的长度(69字节)
● 0x02 - 一个整数值
● 0x21 - 整数的长度(33字节)
● 0x00如果R值的第一个字节大于等于0x80,则需要前缀补0x00,长度加1
● R-00a0d30b0bdab174971576a20b3442b4a3beac20c2bb4c01cc86882ddb66243e17
● 0x02 - 接下来是一个整数
● 0x20 - 整数的长度(32字节)
● 如果S值的第一个字节大于等于0x80,则需要前缀补0x00(此处没有),长度加1
● S-74d7a1ad88b70bb73e96cc60025416d420ec0bbea582bc78f96085d750b3754b
● 后缀(此处没有)(如0x01)指示使用的哈希的类型(SIGHASH_ALL)

其中长度当小于127时,用一个字节表示,而大于127时,会另外前置一个字节用于表示长度所占用的字节大小,并在此字节最高位设为1

如468 对应二进制【0000 0001 1101 0100】
在当前二进制高位前加8位,最高位为1,低位表示数据长度字节数,实际长度为2字节,二进制编码为0010(十进制为2),所以前置新增的8位表示为【1000 0010】,其第7bit为1,而0~6bit的000 0010=2,代码此后用2个字节代表数据长度,对应DER编码格式长度为【1000 0010 0000 0001 1101 0100】,十六进制为0x8201D4

方法二、效率略低,但OpenSSL内部已经提供了相关函数,不用我们再担心后续的维护的问题

此处就以方法二的代码做演示,代码如下:

int ECDSA_openssl_to_CNG(const unsigned char* DER, long DER_len, unsigned char** CNG_SIG)
{
	//数据转换的算法,将ECDSA_SIG二进制编码转换成ECDSA_SIG结构,再转换成DER二进制格式
	ECDSA_SIG* sig = d2i_ECDSA_SIG(NULL, &DER, DER_len);
	if (sig == NULL) return 0;

	const BIGNUM *r = NULL, *s = NULL;
	ECDSA_SIG_get0(sig, &r, &s);
	if (r == NULL || s == NULL) return 0;

	int rlen = BN_num_bytes(r);
	int slen = BN_num_bytes(s);

	int vlen = 0, sig_len = 0;
	//CNG对签名大小有严格的要求,而rlen或slen实际可能略小于CNG签名所需大小(size / 2),所以需要补足
	//int vlen = rlen > slen ? rlen : slen;//rlen和slen同时小于CNG签名大小(size / 2)

	if (rlen <= 32) {//DER_len <= 72,ECDSA_P256签名的大小
		vlen = 32;
		sig_len = 64;
	}
	else if (rlen <= 48) {//DER_len <= 104,ECDSA_P384签名的大小
		vlen = 48;
		sig_len = 96;
	}
	else if (rlen <= 66) {//DER_len <= 139,ECDSA_P521签名的大小
		vlen = 66;
		sig_len = 132;
	}

	//CNG_SIG为NULL时,返回所需空间大小
	if(NULL == CNG_SIG) {
		return sig_len;
	}

	if(NULL == *CNG_SIG) {
		*CNG_SIG = (PBYTE)HeapAlloc(GetProcessHeap (), 0, sig_len);
	}
	unsigned char* buf = *CNG_SIG;
	BN_bn2binpad(r, buf, vlen);
	BN_bn2binpad(s, &buf[vlen], vlen);

	ECDSA_SIG_free(sig);

	return sig_len;
}

CNG->OpenSSL

int ECDSA_CNG_to_openssl(const unsigned char* CNG_SIG, DWORD CNG_SIG_SIZE, unsigned char** DER)
{
	//数据转换的算法,将ECDSA_SIG二进制编码转换成ECDSA_SIG结构,再转换成DER二进制格式
	ECDSA_SIG* sig = ECDSA_SIG_new();

	DWORD vlen = CNG_SIG_SIZE / 2;

	BIGNUM* r = BN_bin2bn(CNG_SIG, vlen, NULL);
	BIGNUM* s = BN_bin2bn(&CNG_SIG[vlen], vlen, NULL);

	//此时,r和s的所有权会被转移到sig中去,所以不能BN_free
	ECDSA_SIG_set0(sig, r, s);

	int ret = i2d_ECDSA_SIG(sig, DER);

	ECDSA_SIG_free(sig);

	return ret;
}

备注:此处i2d_ECDSA_SIG(ECDSA_SIG *a, unsigned char **ppout)函数有个大坑

i2d_TYPE() encodes the structure pointed to by a into DER format. If ppout is not NULL, it writes the DER encoded data to the buffer at *ppout, and increments it to point after the data just written. If the return value is negative an error occurred, otherwise it returns the length of the encoded data.

If *ppout is NULL memory will be allocated for a buffer and the encoded data written to it. In this case *ppout is not incremented and it points to the start of the data just written.

当*ppout为NULL时,i2d_ECDSA_SIG会内部构造空间,并且运行后*ppout的值指向该空间的头部,此时可以用OPENSSL_free正常释放,如果*ppout不为NULL,则运行后*ppout的值指向该空间写入数据的尾部。如果*ppout是被动态分配的,那此时不能直接调用OPENSSL_free或其它释放空间的函数释放,也不可以直接把它当成DER格式的数据直接使用,因为*ppout已经被改写了

unsigned char* DER = NULL;
int ret = i2d_ECDSA_SIG(sig, &DER);
...
OPENSSL_free(DER);//可以正常使用和释放

//但是

unsigned char* DER = NULL;
int ret = i2d_ECDSA_SIG(sig, NULL);
DER = OPENSSL_malloc(ret);
ret = i2d_ECDSA_SIG(sig, &DER);
...//不可以直接使用DER,因为i2d_ECDSA_SIG改变了DER最后的指向
OPENSSL_free(DER);//发送错误

//需要
unsigned char* DER = NULL;
int ret = i2d_ECDSA_SIG(sig, NULL);
DER = OPENSSL_malloc(ret);
unsigned char* buf = DER;
ret = i2d_ECDSA_SIG(sig, &buf);
...//可以直接使用DER
OPENSSL_free(DER);

结合前面章节《Cryptography API: Next Generation(CNG)使用梳理——非对称加密算法应用(五)与OpenSSL数字签名认证的互通(ECDSA)》密钥转换的内容,实现CNG与OpenSSL3直接的签名互验

基本流程:

一、生成密钥对

二、导出公钥和私钥

三、转换为OpenSSL的密钥对(转换后的公钥用于验证CNG生成的签名,而转换后的私钥则用于生成OpenSSL的签名)

四、生成信息摘要的HASH值(CNG)

五、生成CNG的签名

六、转换CNG的签名格式为OpenSSL3的格式

七、OpenSSL3用转换后的公钥,验证签名

八、OpenSSL3用转换后的私钥,生成新的签名

九、转换OpenSSL3的签名格式为CNG的格式

十、CNG导入并生成新的公钥

十一、验证OpenSSL3生成的签名

代码如下:

ECC_Helper.h

#ifndef ECC_HELPER_H
#define ECC_HELPER_H

#include <windows.h>
#include <bcrypt.h>

bool Is_CNG_EC_Public_Key(PBCRYPT_ECCKEY_BLOB pubKey);
//判断导出的BLOB是否为EC私钥
bool Is_CNG_EC_Private_Key(PBCRYPT_ECCKEY_BLOB pvtKey);
//将OpenSSL的nid转换为ECCKEY_BLOB中的Magic
int ECDH_NID_to_CNG_Magic(int nid, bool isPrivate);
//同上
int ECDSA_NID_to_CNG_Magic(int nid, bool isPrivate);
//将ECCKEY_BLOB中的Magic转换为OpenSSL的nid
int CNG_Magic_to_NID (int magic);

//将OpenSSL的nid转换为OpenSSL的SN
char* OpenSSL_NID_to_SN(int nid);

//将ECCKEY_BLOB中的Magic转换为OpenSSL的sn
char* CNG_Magic_to_SN(int magic);
#endif//ECC_HELPER_H

ECC_Helper.cpp

#include "ECC_Helper.h"
#include <openssl/evp.h>
#include <openssl/ec.h>
#include <openssl/core_names.h>
#include <openssl/param_build.h>

//判断导出的BLOB是否为EC公钥
bool Is_CNG_EC_Public_Key(PBCRYPT_ECCKEY_BLOB pubKey)
{
	switch(pubKey->dwMagic) {
	case BCRYPT_ECDH_PUBLIC_P256_MAGIC:
	case BCRYPT_ECDSA_PUBLIC_P256_MAGIC:
	case BCRYPT_ECDH_PUBLIC_P384_MAGIC:
	case BCRYPT_ECDSA_PUBLIC_P384_MAGIC:
	case BCRYPT_ECDH_PUBLIC_P521_MAGIC:
	case BCRYPT_ECDSA_PUBLIC_P521_MAGIC:
		return true;
	}
	return false;
}
//判断导出的BLOB是否为EC私钥
bool Is_CNG_EC_Private_Key(PBCRYPT_ECCKEY_BLOB pvtKey)
{
	switch(pvtKey->dwMagic) {
	case BCRYPT_ECDH_PRIVATE_P256_MAGIC:
	case BCRYPT_ECDSA_PRIVATE_P256_MAGIC:
	case BCRYPT_ECDH_PRIVATE_P384_MAGIC:
	case BCRYPT_ECDSA_PRIVATE_P384_MAGIC:
	case BCRYPT_ECDH_PRIVATE_P521_MAGIC:
	case BCRYPT_ECDSA_PRIVATE_P521_MAGIC:
		return true;
	}
	return false;
}
//将OpenSSL的nid转换为ECCKEY_BLOB中的Magic
int ECDH_NID_to_CNG_Magic(int nid, bool isPrivate)
{
	if (isPrivate) {
		switch(nid) {
		case NID_X9_62_prime256v1:
			return BCRYPT_ECDH_PRIVATE_P256_MAGIC;
		case NID_secp384r1:
			return BCRYPT_ECDH_PRIVATE_P384_MAGIC;
		case NID_secp521r1:
			return BCRYPT_ECDH_PRIVATE_P521_MAGIC;
		}
	}
	else {
		switch(nid) {
		case NID_X9_62_prime256v1:
			return BCRYPT_ECDH_PUBLIC_P256_MAGIC;
		case NID_secp384r1:
			return BCRYPT_ECDH_PUBLIC_P384_MAGIC;
		case NID_secp521r1:
			return BCRYPT_ECDH_PUBLIC_P521_MAGIC;
		}
	}
	return 0;
}
//同上
int ECDSA_NID_to_CNG_Magic(int nid, bool isPrivate)
{
	if (isPrivate) {
		switch(nid) {
		case NID_X9_62_prime256v1:
			return BCRYPT_ECDSA_PRIVATE_P256_MAGIC;
		case NID_secp384r1:
			return BCRYPT_ECDSA_PRIVATE_P384_MAGIC;
		case NID_secp521r1:
			return BCRYPT_ECDSA_PRIVATE_P521_MAGIC;
		}
	}
	else {
		switch(nid) {
		case NID_X9_62_prime256v1:
			return BCRYPT_ECDSA_PUBLIC_P256_MAGIC;
		case NID_secp384r1:
			return BCRYPT_ECDSA_PUBLIC_P384_MAGIC;
		case NID_secp521r1:
			return BCRYPT_ECDSA_PUBLIC_P521_MAGIC;
		}
	}
	return 0;
}
//将ECCKEY_BLOB中的Magic转换为OpenSSL的nid
int CNG_Magic_to_NID (int magic)
{
	switch(magic) {
	case BCRYPT_ECDH_PRIVATE_P256_MAGIC:
	case BCRYPT_ECDSA_PRIVATE_P256_MAGIC:
	case BCRYPT_ECDH_PUBLIC_P256_MAGIC:
	case BCRYPT_ECDSA_PUBLIC_P256_MAGIC:
		return NID_X9_62_prime256v1;
	case BCRYPT_ECDH_PRIVATE_P384_MAGIC:
	case BCRYPT_ECDSA_PRIVATE_P384_MAGIC:
	case BCRYPT_ECDH_PUBLIC_P384_MAGIC:
	case BCRYPT_ECDSA_PUBLIC_P384_MAGIC:
		return NID_secp384r1;
	case BCRYPT_ECDH_PRIVATE_P521_MAGIC:
	case BCRYPT_ECDSA_PRIVATE_P521_MAGIC:
	case BCRYPT_ECDH_PUBLIC_P521_MAGIC:
	case BCRYPT_ECDSA_PUBLIC_P521_MAGIC:
		return NID_secp521r1;
	}

	return 0;
}

//将OpenSSL的nid转换为OpenSSL的SN
char* OpenSSL_NID_to_SN(int nid)
{
	switch(nid) {
	case NID_X9_62_prime256v1:
		return SN_X9_62_prime256v1;
	case NID_secp384r1:
		return SN_secp384r1;
	case NID_secp521r1:
		return SN_secp521r1;
	}
	return NULL;
}

//将ECCKEY_BLOB中的Magic转换为OpenSSL的sn
char* CNG_Magic_to_SN(int magic)
{
	switch(magic) {
	case BCRYPT_ECDH_PRIVATE_P256_MAGIC:
	case BCRYPT_ECDSA_PRIVATE_P256_MAGIC:
	case BCRYPT_ECDH_PUBLIC_P256_MAGIC:
	case BCRYPT_ECDSA_PUBLIC_P256_MAGIC:
		return SN_X9_62_prime256v1;
	case BCRYPT_ECDH_PRIVATE_P384_MAGIC:
	case BCRYPT_ECDSA_PRIVATE_P384_MAGIC:
	case BCRYPT_ECDH_PUBLIC_P384_MAGIC:
	case BCRYPT_ECDSA_PUBLIC_P384_MAGIC:
		return SN_secp384r1;
	case BCRYPT_ECDH_PRIVATE_P521_MAGIC:
	case BCRYPT_ECDSA_PRIVATE_P521_MAGIC:
	case BCRYPT_ECDH_PUBLIC_P521_MAGIC:
	case BCRYPT_ECDSA_PUBLIC_P521_MAGIC:
		return SN_secp521r1;
	}
	return NULL;
}

main.cpp

#include "ECC_Helper.h"

#include <windows.h>
#include <bcrypt.h>

#include <openssl/evp.h>
#include <openssl/ec.h>
#include <openssl/core_names.h>
#include <openssl/param_build.h>
#include <openssl/bn.h>
#include <openssl/sha.h>
#include <string.h>

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

//公钥格式转换
//CNG->OpenSSL3
EVP_PKEY* CNG_to_EVP_EC_pub_key(EVP_PKEY_CTX* ctx, PBCRYPT_ECCKEY_BLOB ecPubKey)
{
	if(!Is_CNG_EC_Public_Key(ecPubKey)) return NULL;
	EVP_PKEY* pkey = NULL;
	BIGNUM* qx = NULL;
	BIGNUM* qy = NULL;

	OSSL_PARAM_BLD *param_bld;
	OSSL_PARAM *params = NULL;

	BN_CTX* bctx = BN_CTX_secure_new();//BN_CTX_new();
	BN_CTX_start(bctx);
	qx = BN_CTX_get(bctx);
	qy = BN_CTX_get(bctx);

	//定位公钥中的X坐标值数据的位置
	BYTE* EC_X_blob = (BYTE*)ecPubKey + sizeof(BCRYPT_ECCKEY_BLOB);
	//定位公钥中的Y坐标值数据的位置
	BYTE* EC_Y_blob = EC_X_blob + ecPubKey->cbKey;
	//获取公钥中的X坐标值
	BN_bin2bn(EC_X_blob, ecPubKey->cbKey, qx);
	//获取公钥中的Y坐标值
	BN_bin2bn(EC_Y_blob, ecPubKey->cbKey, qy);

	unsigned char *pbuf = NULL;
	//将坐标qx、qy转化为点格式
	//借助CNG_Magic_to_NID辅助函数动态获取椭圆曲线的nid
	EC_GROUP *ecgroup = EC_GROUP_new_by_curve_name(CNG_Magic_to_NID(ecPubKey->dwMagic));
	EC_POINT *pub = EC_POINT_new(ecgroup);
	//映射坐标到椭圆曲线,获取点
	EC_POINT_set_affine_coordinates(ecgroup, pub, qx, qy, bctx);
	//将点转化为非压缩(POINT_CONVERSION_UNCOMPRESSED)二进制格式
	size_t pub_out_len = EC_POINT_point2buf(ecgroup, pub, POINT_CONVERSION_UNCOMPRESSED, &pbuf, bctx);

	param_bld = OSSL_PARAM_BLD_new();
	if (qx != NULL && qy != NULL && param_bld != NULL
		&& OSSL_PARAM_BLD_push_utf8_string(param_bld, OSSL_PKEY_PARAM_GROUP_NAME/*"group"*/,
		CNG_Magic_to_SN(ecPubKey->dwMagic), 0)
		&& OSSL_PARAM_BLD_push_octet_string(param_bld, OSSL_PKEY_PARAM_PUB_KEY/*"pub"*/,
		pbuf, pub_out_len))
		params = OSSL_PARAM_BLD_to_param(param_bld);

	if (params == NULL
		|| EVP_PKEY_fromdata_init(ctx) <= 0
		|| EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params) <= 0) {
			printf("new_get_pub_key error\n");
	}

	OSSL_PARAM_free(params);
	OSSL_PARAM_BLD_free(param_bld);

	OPENSSL_free(pbuf);
	EC_POINT_free(pub);
	EC_GROUP_free(ecgroup);

	BN_CTX_end(bctx);
	BN_CTX_free(bctx);

	return pkey;
}

//OpenSSL3->CNG
//返回值需要用OPENSSL_free释放
PBCRYPT_ECCKEY_BLOB EVP_to_CNG_EC_pub_key(EVP_PKEY* ecPubKey)
{
	EVP_PKEY* pkey = NULL;
	BIGNUM* qx = NULL;
	BIGNUM* qy = NULL;
	BN_CTX* bctx = NULL;

	//OpenSSL3 中没有找到直接获取EC_GROUP的方法,可以变相使用EVP_PKEY_get_group_name获取group的curve_name
	char curve_name[256] = {0};
	size_t curve_name_len = sizeof(curve_name);
	EVP_PKEY_get_group_name(ecPubKey, curve_name, curve_name_len, &curve_name_len);
	//从EVP_PKEY获取NID值
	int nid = OBJ_sn2nid(curve_name);

#ifdef GET_PARAM_EC_PUB_X_AND_Y
	//直接获取qx和qy元素,
	if (1 == EVP_PKEY_get_bn_param(ecPubKey, OSSL_PKEY_PARAM_EC_PUB_X/*"qx"*/, &qx)) {
		EVP_PKEY_get_bn_param(ecPubKey, OSSL_PKEY_PARAM_EC_PUB_Y/*"qy"*/, &qy);
	}
	else {//如果不能直接获取,则通过获取pub元素,而后转化为坐标
#else
		size_t out = 0;
		EVP_PKEY_get_octet_string_param(ecPubKey, OSSL_PKEY_PARAM_PUB_KEY/*"pub"*/, NULL, out, &out);
		if (!out) return NULL;

		bctx = BN_CTX_secure_new();//BN_CTX_new();
		BN_CTX_start(bctx);
		qx = BN_CTX_get(bctx);
		qy = BN_CTX_get(bctx);

		unsigned char* pub_data = (unsigned char*)OPENSSL_malloc(out);
		EVP_PKEY_get_octet_string_param(ecPubKey, OSSL_PKEY_PARAM_PUB_KEY/*"pub"*/, pub_data, out, &out);

		//将pub的点格式转换为坐标格式
		EC_GROUP *ecgroup = EC_GROUP_new_by_curve_name(nid);
		EC_POINT *pub = EC_POINT_new(ecgroup);
		EC_POINT_oct2point(ecgroup, pub, pub_data, out, bctx);
		EC_POINT_get_affine_coordinates(ecgroup, pub, qx, qy, bctx);

		EC_GROUP_free(ecgroup);
		EC_POINT_free(pub);
		OPENSSL_free(pub_data);
#endif
#ifdef GET_PARAM_EC_PUB_X_AND_Y
	}
#endif
	//获取qx和qy的大小
	ULONG cbX = BN_num_bytes(qx);
	ULONG cbY = BN_num_bytes(qy);

	ULONG cbKey = max(cbX, cbY);

	PBCRYPT_ECCKEY_BLOB pub_blob = (PBCRYPT_ECCKEY_BLOB)OPENSSL_malloc(sizeof(BCRYPT_ECCKEY_BLOB) + cbKey * 2);
	//借助辅助函数将nid转换为BCRYPT_ECCKEY_BLOB的Magic
	//此处为ECDH用于密钥交换ECDH_NID_to_CNG_Magic
	//如果是签名则使用ECDSA_NID_to_CNG_Magic
	pub_blob->dwMagic = ECDH_NID_to_CNG_Magic(nid, false);
	pub_blob->cbKey = cbKey;

	//使用BN_bn2binpad可以在缓冲区大于所需空间的情况下,自动在高位填充0
	BYTE* EC_X_blob = (BYTE*)pub_blob + sizeof(BCRYPT_ECCKEY_BLOB);
	BYTE* EC_Y_blob = EC_X_blob + cbKey;

	BN_bn2binpad(qx, EC_X_blob, cbKey);
	BN_bn2binpad(qy, EC_Y_blob, cbKey);

#ifdef GET_PARAM_EC_PUB_X_AND_Y
	if (bctx) {
#endif
		BN_CTX_end(bctx);
		BN_CTX_free(bctx);
#ifdef GET_PARAM_EC_PUB_X_AND_Y
	}
	else {
		BN_free(qx);
		BN_free(qy);
	}
#endif

	return pub_blob;
}

//私钥格式转换
//CNG->OpenSSL3
EVP_PKEY* CNG_to_EVP_EC_pvt_key(EVP_PKEY_CTX* ctx, PBCRYPT_ECCKEY_BLOB ecPvtKey)
{
	if(!Is_CNG_EC_Private_Key(ecPvtKey)) return NULL;
	EVP_PKEY* pkey = NULL;
	BIGNUM* qx = NULL;
	BIGNUM* qy = NULL;
	BIGNUM* d = NULL;

	OSSL_PARAM_BLD *param_bld;
	OSSL_PARAM *params = NULL;

	BN_CTX* bctx = BN_CTX_secure_new();//BN_CTX_new();
	BN_CTX_start(bctx);
	qx = BN_CTX_get(bctx);
	qy = BN_CTX_get(bctx);
	d = BN_CTX_get(bctx);

	BYTE* EC_X_blob = (BYTE*)ecPvtKey + sizeof(BCRYPT_ECCKEY_BLOB);
	BYTE* EC_Y_blob = EC_X_blob + ecPvtKey->cbKey;
	BYTE* EC_d_blob = EC_Y_blob + ecPvtKey->cbKey;//priv

	BN_bin2bn(EC_X_blob, ecPvtKey->cbKey, qx);
	BN_bin2bn(EC_Y_blob, ecPvtKey->cbKey, qy);
	BN_bin2bn(EC_d_blob, ecPvtKey->cbKey, d);

	unsigned char *pbuf = NULL;
	//将坐标qx、qy转化为点格式

	EC_GROUP *ecgroup = EC_GROUP_new_by_curve_name(CNG_Magic_to_NID(ecPvtKey->dwMagic));
	EC_POINT *pvt = EC_POINT_new(ecgroup);
	//映射坐标到椭圆曲线,获取点
	EC_POINT_set_affine_coordinates(ecgroup, pvt, qx, qy, bctx);
	//将点转化为非压缩(POINT_CONVERSION_UNCOMPRESSED)二进制格式
	size_t pvt_out_len = EC_POINT_point2buf(ecgroup, pvt, POINT_CONVERSION_UNCOMPRESSED, &pbuf, bctx);

	param_bld = OSSL_PARAM_BLD_new();
	if (qx != NULL && qy != NULL && param_bld != NULL
		&& OSSL_PARAM_BLD_push_utf8_string(param_bld, OSSL_PKEY_PARAM_GROUP_NAME/*"group"*/,
		CNG_Magic_to_SN(ecPvtKey->dwMagic), 0)
		&& OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_PRIV_KEY/*"priv"*/,
		d)
		&& OSSL_PARAM_BLD_push_octet_string(param_bld, OSSL_PKEY_PARAM_PUB_KEY/*"pub"*/,
		pbuf, pvt_out_len))
		params = OSSL_PARAM_BLD_to_param(param_bld);


	if (params == NULL
		|| EVP_PKEY_fromdata_init(ctx) <= 0
		|| EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_KEYPAIR, params) <= 0) {
			printf("new_get_pub_key error\n");
	}

	OSSL_PARAM_free(params);
	OSSL_PARAM_BLD_free(param_bld);

	OPENSSL_free(pbuf);
	EC_POINT_free(pvt);
	EC_GROUP_free(ecgroup);

	BN_CTX_end(bctx);
	BN_CTX_free(bctx);

	return pkey;
}

//OpenSSL3->CNG
PBCRYPT_ECCKEY_BLOB EVP_to_CNG_EC_pvt_key(EVP_PKEY* ecPvtKey)
{
	EVP_PKEY* pkey = NULL;
	BIGNUM* qx = NULL;
	BIGNUM* qy = NULL;
	BIGNUM* d = NULL;
	BN_CTX* bctx = NULL;

	//OpenSSL3 中没有找到直接获取EC_GROUP的方法,可以变相使用EVP_PKEY_get_group_name获取group的curve_name
	char curve_name[256] = {0};
	size_t curve_name_len = sizeof(curve_name);
	EVP_PKEY_get_group_name(ecPvtKey, curve_name, curve_name_len, &curve_name_len);
	int nid = OBJ_sn2nid(curve_name);

#ifdef GET_PARAM_EC_PVT_X_AND_Y
	//直接获取qx和qy元素
	if (1 == EVP_PKEY_get_bn_param(ecPubKey, OSSL_PKEY_PARAM_EC_PUB_X/*"qx"*/, &qx)) {
		EVP_PKEY_get_bn_param(ecPubKey, OSSL_PKEY_PARAM_EC_PUB_Y/*"qy"*/, &qy);
		EVP_PKEY_get_bn_param(ecPvtKey, OSSL_PKEY_PARAM_PRIV_KEY/*"priv"*/, &d);
	}
	else {//如果不能直接获取,则通过获取pub元素,而后转化为坐标
#else
		size_t out = 0;
		//可以使用i2d_PublicKey,此函数为通用函数,内部会按EC_KEY的类型再调用i2o_ECPublicKey(\crypto\asn1\i2d_evp.c OppenSSL 3.0.5 p123)
		//i2o_ECPublicKey可以从EC_KEY中读取公钥二进制结构(\crypto\ec\ec_asn1.c OppenSSL 3.0.5 p1159)
		EVP_PKEY_get_octet_string_param(ecPvtKey, OSSL_PKEY_PARAM_PUB_KEY/*"pub"*/, NULL, out, &out);
		if (!out) return NULL;

		bctx = BN_CTX_new();//BN_CTX_secure_new();
		BN_CTX_start(bctx);
		qx = BN_CTX_get(bctx);
		qy = BN_CTX_get(bctx);
		d = BN_CTX_get(bctx);

		unsigned char* pvt_data = (unsigned char*)OPENSSL_malloc(out);
		EVP_PKEY_get_octet_string_param(ecPvtKey, OSSL_PKEY_PARAM_PUB_KEY/*"pub"*/, pvt_data, out, &out);//qx和qy在OSSL_PKEY_PARAM_PUB_KEY中

		EC_GROUP *ecgroup = EC_GROUP_new_by_curve_name(nid);
		EC_POINT *pvt = EC_POINT_new(ecgroup);
		EC_POINT_oct2point(ecgroup, pvt, pvt_data, out, bctx);
		EC_POINT_get_affine_coordinates(ecgroup, pvt, qx, qy, bctx);

		//获取私钥部分
		EVP_PKEY_get_bn_param(ecPvtKey, OSSL_PKEY_PARAM_PRIV_KEY/*"priv"*/, &d);

		EC_GROUP_free(ecgroup);
		EC_POINT_free(pvt);
		OPENSSL_free(pvt_data);
#endif
#ifdef GET_PARAM_EC_PUB_X_AND_Y
	}
#endif
	ULONG cbX = BN_num_bytes(qx);
	ULONG cbY = BN_num_bytes(qy);
	ULONG cbd = BN_num_bytes(d);

	ULONG cbKey = max(cbX, cbY);
	cbKey = max(cbKey, cbd);

	PBCRYPT_ECCKEY_BLOB pvt_blob = (PBCRYPT_ECCKEY_BLOB)OPENSSL_malloc(sizeof(BCRYPT_ECCKEY_BLOB) + cbKey * 3);
	//借助辅助函数将nid转换为BCRYPT_ECCKEY_BLOB的Magic
	//此处为ECDH用于密钥交换ECDH_NID_to_CNG_Magic
	//如果是签名则使用ECDSA_NID_to_CNG_Magic
	pvt_blob->dwMagic = ECDH_NID_to_CNG_Magic(nid, true);
	pvt_blob->cbKey = cbKey;

	//使用BN_bn2binpad可以在缓冲区大于所需空间的情况下,自动在高位填充0
	BYTE* EC_X_blob = (BYTE*)pvt_blob + sizeof(BCRYPT_ECCKEY_BLOB);
	BYTE* EC_Y_blob = EC_X_blob + cbKey;
	BYTE* EC_d_blob = EC_Y_blob + cbKey;

	BN_bn2binpad(qx, EC_X_blob, cbKey);
	BN_bn2binpad(qy, EC_Y_blob, cbKey);
	BN_bn2binpad(d, EC_d_blob, cbKey);

#ifdef GET_PARAM_EC_PVT_X_AND_Y
	if (bctx) {
	#endif
		BN_CTX_end(bctx);
		BN_CTX_free(bctx);
#ifdef GET_PARAM_EC_PVT_X_AND_Y
	}
	else {
		BN_free(qx);
		BN_free(qy);
		BN_free(d);
	}
#endif

return pvt_blob;
}

//签名格式转换
//OpenSSL->CNG
int ECDSA_openssl_to_CNG(const unsigned char* DER, long DER_len, unsigned char** CNG_SIG)
{
	//数据转换的算法,将ECDSA_SIG二进制编码转换成ECDSA_SIG结构,再转换成DER二进制格式
	ECDSA_SIG* sig = d2i_ECDSA_SIG(NULL, &DER, DER_len);
	if (sig == NULL) return 0;

	const BIGNUM *r = NULL, *s = NULL;
	ECDSA_SIG_get0(sig, &r, &s);
	if (r == NULL || s == NULL) return 0;

	int rlen = BN_num_bytes(r);
	int slen = BN_num_bytes(s);

	int vlen = 0, sig_len = 0;
	//CNG对签名大小有严格的要求,而rlen或slen实际可能略小于CNG签名所需大小(size / 2),所以需要补足
	//int vlen = rlen > slen ? rlen : slen;//rlen和slen同时小于CNG签名大小(size / 2)

	if (rlen <= 32) {//DER_len <= 72,ECDSA_P256签名的大小
		vlen = 32;
		sig_len = 64;
	}
	else if (rlen <= 48) {//DER_len <= 104,ECDSA_P384签名的大小
		vlen = 48;
		sig_len = 96;
	}
	else if (rlen <= 66) {//DER_len <= 139,ECDSA_P521签名的大小
		vlen = 66;
		sig_len = 132;
	}

	if(NULL == CNG_SIG) {
		return sig_len;
	}

	if(NULL == *CNG_SIG) {
		*CNG_SIG = (PBYTE)HeapAlloc(GetProcessHeap (), 0, sig_len);
	}
	unsigned char* buf = *CNG_SIG;
	BN_bn2binpad(r, buf, vlen);
	BN_bn2binpad(s, &buf[vlen], vlen);

	ECDSA_SIG_free(sig);

	return sig_len;
}

//CNG->OpenSSL
int ECDSA_CNG_to_openssl(const unsigned char* CNG_SIG, DWORD CNG_SIG_SIZE, unsigned char** DER)
{
	//数据转换的算法,将ECDSA_SIG二进制编码转换成ECDSA_SIG结构,再转换成DER二进制格式
	ECDSA_SIG* sig = ECDSA_SIG_new();

	DWORD vlen = CNG_SIG_SIZE / 2;

	BIGNUM* r = BN_bin2bn(CNG_SIG, vlen, NULL);
	BIGNUM* s = BN_bin2bn(&CNG_SIG[vlen], vlen, NULL);

	//此时,r和s的所有权会被转移到sig中去,所以不能BN_free
	ECDSA_SIG_set0(sig, r, s);

	int ret = i2d_ECDSA_SIG(sig, DER);

	ECDSA_SIG_free(sig);

	return ret;
}

//BCRYPT_SHA256_ALGORITHM
int Hash(LPCWSTR pszAlgId, PBYTE in, DWORD in_len, PBYTE* out, DWORD* out_len)
{
	BCRYPT_ALG_HANDLE       hAlg            = NULL;
	BCRYPT_HASH_HANDLE      hHash           = NULL;
	DWORD                   cbData          = 0,
							cbHash          = 0;
	PBYTE                   pbHash          = NULL;

	//打开支持所需算法的算法提供程序,获取算法句柄
	BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_SHA256_ALGORITHM, NULL, 0);

	//获取Hash值所需空间大小
	BCryptGetProperty(hAlg, BCRYPT_HASH_LENGTH, (PBYTE)&cbHash, sizeof(DWORD), &cbData, 0);

	//分配Hash值所需空间
	pbHash = (PBYTE)HeapAlloc (GetProcessHeap (), 0, cbHash);

	//创建Hash对象
	BCryptCreateHash(hAlg, &hHash, NULL, 0, NULL, 0, 0);

	//对数据进行哈希处理
	BCryptHashData(hHash, in, in_len, 0);

	//获取哈希值
	BCryptFinishHash(hHash, pbHash, cbHash, 0);

	//关闭Hash对象
	BCryptDestroyHash(hHash);

	//关闭算法提供程序
	BCryptCloseAlgorithmProvider(hAlg,0);

	//清理

	//HeapFree(GetProcessHeap(), 0, pbHash);
	*out = pbHash;
	*out_len = cbHash;

	return 0;
}

//pszAlgId=BCRYPT_ECDSA_P256_ALGORITHM
//dwLength=256
bool CreateKeyPair(BCRYPT_ALG_HANDLE* phAlg, BCRYPT_KEY_HANDLE* phKey, LPCWSTR pszAlgId, ULONG dwLength)
{
	BCRYPT_ALG_HANDLE hAlg = NULL;
	BCRYPT_KEY_HANDLE hKey = NULL;

	NTSTATUS status = STATUS_UNSUCCESSFUL;

	if (!NT_SUCCESS(status = BCryptOpenAlgorithmProvider(&hAlg, pszAlgId, NULL, 0))) {
		printf("**** Error code returned by BCryptOpenAlgorithmProvider: 0x%02x\n", status);
		return false;
	}

	if (!NT_SUCCESS(status = BCryptGenerateKeyPair(hAlg, &hKey, dwLength, 0))) {
		printf("**** Error code returned by BCryptGenerateKeyPair: 0x%02x\n", status);
		if (hAlg) {
			BCryptCloseAlgorithmProvider(hAlg, 0);
		}
		return false;
	}

	// create key pair
	if (!NT_SUCCESS(status = BCryptFinalizeKeyPair(hKey, 0))) {
		printf("**** Error code returned by BCryptFinalizeKeyPair: 0x%02x\n", status);
		if (hAlg) {
			BCryptCloseAlgorithmProvider(hAlg, 0);
		}
		if (hKey) {
			BCryptDestroyKey(hKey);
		}
		return false;
	}

	*phAlg = hAlg;
	*phKey = hKey;

	return true;
}

void CloseAndDestroyKey(BCRYPT_ALG_HANDLE hAlg, BCRYPT_KEY_HANDLE hKey)
{
	if (hAlg) {
		BCryptCloseAlgorithmProvider(hAlg, 0);
	}
	if (hKey) {
		BCryptDestroyKey(hKey);
	}
}

//BCRYPT_ECCPRIVATE_BLOB
DWORD ExportKey(BCRYPT_KEY_HANDLE hKey, LPCWSTR pszBlobType, PBYTE* ppbBlob)
{
	PBYTE pbBlob = NULL;
	DWORD cbBlob = 0;
	NTSTATUS status = STATUS_UNSUCCESSFUL;

	if (!NT_SUCCESS(status = BCryptExportKey(hKey, NULL, pszBlobType, NULL, 0, &cbBlob, 0))) {
		printf("**** Error code returned by BCryptExportKey: 0x%02x\n", status);
		return 0;
	}
	//如果ppbBlob为NULL,返回所需空间大小
	if(NULL == ppbBlob) return cbBlob;

	if(NULL == *ppbBlob) {
		pbBlob = (PBYTE)HeapAlloc(GetProcessHeap(), 0, cbBlob);
		if (NULL == pbBlob) {
			printf("**** memory allocation failed!");
			return 0;
		}
		*ppbBlob = pbBlob;
	}

	if (!NT_SUCCESS(status = BCryptExportKey(hKey, NULL, pszBlobType, pbBlob, cbBlob, &cbBlob, 0))) {
		printf("**** Error code returned by BCryptExportKey: 0x%02x\n", status);
		if (pbBlob) {
			HeapFree(GetProcessHeap(), 0, pbBlob);
		}
		return 0;
	}

	return cbBlob;
}

void FreeExportKey(PBYTE pbBlob)
{
	if (pbBlob) {
		HeapFree(GetProcessHeap(), 0, pbBlob);
	}
}

BCRYPT_KEY_HANDLE ImportKey(BCRYPT_ALG_HANDLE hAlg, LPCWSTR pszBlobType, PBYTE pbBlob, DWORD cbBlob)
{
	BCRYPT_KEY_HANDLE hKey = NULL;
	NTSTATUS status = STATUS_UNSUCCESSFUL;
	//加载并初始化BCrypt提供程序
	//BCryptOpenAlgorithmProvider(&hAlg, pszBlobType, 0);

	//导入公钥
	if (!NT_SUCCESS(status = BCryptImportKeyPair(hAlg, NULL, pszBlobType, &hKey, pbBlob, cbBlob, 0))) {
		printf("**** Error code returned by BCryptImportKeyPair: 0x%02x\n", status);
		return NULL;
	}
	return hKey;
}

bool SignHash(BCRYPT_KEY_HANDLE hKey, unsigned char *hashPointer, unsigned long hashLength, PBYTE* signPointer, DWORD* signLength)
{
	NTSTATUS status = STATUS_UNSUCCESSFUL;

	if (!NT_SUCCESS(status = BCryptSignHash(hKey, NULL, hashPointer, hashLength, NULL, 0, signLength, 0))) {
		printf("**** Error code returned by BCryptSignHash1: 0x%02x\n", status);
		return false;
	}

	*signPointer = (PBYTE)HeapAlloc(GetProcessHeap(), 0, *signLength);
	if (NULL == *signPointer) {
		printf("**** memory allocation failed!");
		return false;
	}

	if (!NT_SUCCESS(status = BCryptSignHash(hKey, NULL, hashPointer, hashLength, *signPointer, *signLength, signLength, 0))) {
		printf("**** Error code returned by BCryptSignHash2: 0x%02x\n", status);
		return false;
	}

	return true;
}

void FreeSign(PBYTE sig)
{
	if(sig) {
		HeapFree(GetProcessHeap(), 0, sig);
	}
}

bool VerifySignature(BCRYPT_KEY_HANDLE hKey, unsigned char *hash, unsigned long hash_len, unsigned char *sign, unsigned long sign_len)
{
	NTSTATUS status = STATUS_UNSUCCESSFUL;

	if (!NT_SUCCESS(status = BCryptVerifySignature(hKey, NULL, hash, hash_len, sign, sign_len, 0))) {
		printf("**** Error code returned by BCryptVerifySignature: 0x%02x\n", status);
		return false;
	}

	return true;
}

int main()
{
	BCRYPT_ALG_HANDLE hAlg = NULL;
	BCRYPT_KEY_HANDLE hKey = NULL;

	PBYTE bpPubBlob = NULL;
	PBYTE bpPvtBlob = NULL;

	//用CNG生成ECDAS密钥对
	CreateKeyPair(&hAlg, &hKey, BCRYPT_ECDSA_P256_ALGORITHM, 256);
	//导出公钥用于验证
	DWORD cbPubBlob = ExportKey(hKey, BCRYPT_ECCPUBLIC_BLOB, &bpPubBlob);
	//导出私钥用于转换为OpenSSL的密钥,并生成签名
	DWORD cbPvtBlob = ExportKey(hKey, BCRYPT_ECCPRIVATE_BLOB, &bpPvtBlob);

	unsigned char *hash = NULL;
	unsigned long hash_len = 0;
	unsigned char* cng_sig = NULL;
	unsigned long cng_sig_len = 0;

	Hash(BCRYPT_SHA256_ALGORITHM, (PBYTE)rgbMsg, sizeof(rgbMsg), &hash, &hash_len);
	//用CNG签名
	SignHash(hKey, hash, hash_len, &cng_sig, &cng_sig_len);

	EVP_PKEY_CTX *gctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL);
	EVP_PKEY* pubkey =  CNG_to_EVP_EC_pub_key(gctx, (PBCRYPT_ECCKEY_BLOB)bpPubBlob);
	EVP_PKEY* pvtkey =  CNG_to_EVP_EC_pvt_key(gctx, (PBCRYPT_ECCKEY_BLOB)bpPvtBlob);

	EVP_MD_CTX* md = EVP_MD_CTX_new();
	const EVP_MD *sha256 = EVP_sha256();


	unsigned char* der_sig = NULL;
	int der_sig_len = ECDSA_CNG_to_openssl(cng_sig, cng_sig_len, &der_sig);//格式转换,CNG用的是ECDSA_SIG格式,而EVP_Sign(EVP_Verify)默认使用的是DER格式
	
	//用OpenSSL验证CNG的签名
	EVP_VerifyInit(md, sha256);
	EVP_VerifyUpdate(md, rgbMsg, sizeof(rgbMsg));
	if(1 != EVP_VerifyFinal(md, der_sig, der_sig_len, pubkey)) {
		printf("**** Error returned by EVP_VerifyFinal CNG->OpenSSL\n");
	}
	else {
		printf("OK!>CNG->OpenSSL Verify Signature Success!\n");
	}

	if (cng_sig) {
		HeapFree(GetProcessHeap(), 0, cng_sig);
		cng_sig = NULL;
	}

	//用OpenSSL签名
	unsigned char openssl_sig[1024] = {0};
	uint32_t openssl_sig_len = sizeof(openssl_sig);
	EVP_SignInit(md, sha256);
	EVP_SignUpdate(md, rgbMsg, sizeof(rgbMsg));
	EVP_SignFinal(md, openssl_sig, &openssl_sig_len, pvtkey);

	cng_sig_len = ECDSA_openssl_to_CNG(openssl_sig, openssl_sig_len, &cng_sig);

	BCRYPT_KEY_HANDLE hPubKey = ImportKey(hAlg, BCRYPT_ECCPUBLIC_BLOB, bpPubBlob, cbPubBlob);

	//用CNG验证OpenSSL的签名
	if(VerifySignature(hPubKey, hash, hash_len, cng_sig, cng_sig_len)) {
		wprintf(L"OK!>OpenSSL->CNG Verify Signature Success!\n");
	}


	if (hash) {
		HeapFree(GetProcessHeap(), 0, hash);
	}
	if(der_sig) {
		OPENSSL_free(der_sig);
	}
	if (cng_sig) {
		HeapFree(GetProcessHeap(), 0, cng_sig);
	}
	if (hPubKey) {
		BCryptDestroyKey(hPubKey);
	}

	FreeExportKey(bpPubBlob);
	FreeExportKey(bpPvtBlob);
	CloseAndDestroyKey(hAlg, hKey);
	
	return 0;
}

备注:此处只用CNG生成了一个密钥对,而后通过同时导出公钥和私钥的方式,来完成互验,如有兴趣可以修改一下代码用OpenSSL再生成一个密钥对,而后两个密钥对都只导出公钥来互验

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值