此篇整理了一下OpenSSL3.0+和CNG之间密钥的相互转换
ECC部分:
CNG导出密钥的内存BLOB结构为:BCRYPT_ECCPRIVATE_BLOB和BCRYPT_ECCPUBLIC_BLOB
椭圆曲线(ECC) 公钥 BLOB (BCRYPT_ECCPUBLIC_BLOB) 在连续内存中具有以下格式。 X 和 Y 坐标是以大端格式编码的无符号整数。
BCRYPT_ECCKEY_BLOB
BYTE X[cbKey] // Big-endian.
BYTE Y[cbKey] // Big-endian.
OpenSSL中ECC公钥为点格式,可以用EC_POINT_set_affine_coordinates函数转换一下即可
椭圆曲线(ECC) 私钥 BLOB (BCRYPT_ECCPRIVATE_BLOB) 在连续内存中具有以下格式。 X 和 Y 坐标和 d 值是以大端格式编码的无符号整数。
BCRYPT_ECCKEY_BLOB
BYTE X[cbKey] // Big-endian.
BYTE Y[cbKey] // Big-endian.
BYTE d[cbKey] // Big-endian.
而在讲OpenSSL3关于ECC密钥的结构前,很遗憾的说OpenSSL3开始已经放弃了对EC_KEY结构的直接构造和读写,转而需要通过对EVP_PKEY抽象的读写和构造,所以整个转换过程有点繁琐和抽象
为了方便转换定义了 一些辅助函数
//判断导出的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;
}
OpenSSL中关于ECC密钥内部参数(详见)
其中公钥主要包含group和pub
"group" (OSSL_PKEY_PARAM_GROUP_NAME) <UTF8 string>
"pub" (OSSL_PKEY_PARAM_PUB_KEY) <octet string>
私钥则是在公钥的基础上多了一个priv
"group" (OSSL_PKEY_PARAM_GROUP_NAME) <UTF8 string>
"pub" (OSSL_PKEY_PARAM_PUB_KEY) <octet string>
"priv" (OSSL_PKEY_PARAM_PRIV_KEY) <unsigned integer>
公钥格式转换
CNG->OpenSSL3
基本的流程就是
一、将CNG导出的BCRYPT_ECCPUBLIC_BLOB中的X和Y坐标格式转换为OpenSSL中的pub的点格式,也就是"pub" (OSSL_PKEY_PARAM_PUB_KEY)
二、获取椭圆曲线的SN名字,也就是"group" (OSSL_PKEY_PARAM_GROUP_NAME)
备注:虽说内部有独立的"qx" (OSSL_PKEY_PARAM_EC_PUB_X)和"qy" (OSSL_PKEY_PARAM_EC_PUB_Y)属性,但是这是只读的,并不能设置
上代码:
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
一、直接获取qx和qy,或者通过获取"pub"后转换为qx和py
二、获取椭圆曲线
1、通过EVP_PKEY_get_group_name获取椭圆曲线的名字(SN)
2、通过OBJ_sn2nid将名字转换为nid
3、通过辅助函数ECDH_NID_to_CNG_Magic或者ECDSA_NID_to_CNG_Magic转换为BCRYPT_ECCKEY_BLOB中的Magic
备注:当然也可以直接定义一个辅助函数将EVP_PKEY_get_group_name获取椭圆曲线的名字(SN)直接转换为BCRYPT_ECCKEY_BLOB中的Magic
//返回值需要用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
由于私钥只是比公钥多了一个priv,因此基本流程相同,多了一个将BCRYPT_ECCKEY_BLOB中的d转换为OpenSSL EVP_KEY中priv的过程而已
代码如下:
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
和上面情况一样,只是多了一个将OpenSSL EVP_KEY中priv转换为BCRYPT_ECCKEY_BLOB中的d
代码如下:
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;
}