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再生成一个密钥对,而后两个密钥对都只导出公钥来互验