OpenSSL 3.0迁移:字节数组转EVP_PKEY及废弃API替代疑问
问题与解决方案:替换废弃的
EC_KEY_set_private_key API 问题背景
我有一段使用废弃API的代码,其中EC_KEY_set_private_key已被标记为废弃,需要替换但文档未明确替代方案。我找到了EVP_PKEY_fromdata的示例,但不确定是否适用,尤其疑惑EVP_PKEY_fromdata中的EVP_PKEY_KEYPAIR参数是否适合将私钥字节数组加载到EVP_PKEY结构中。
原废弃代码
EVP_PKEY *convertByteArrayToEvpPkey(const uint8_t *i_KeyByteArray, size_t i_Length, bool isPrivateKey) { EC_KEY *ecKey = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); if (ecKey == NULL) { errMsg("Initialization of EC key failed"); } EVP_PKEY *key = EVP_PKEY_new(); if (key == NULL) { errMsg("Allocation of public key failed"); } if (EVP_PKEY_set1_EC_KEY(key, ecKey) != 1) { errMsg("Setting EC key in public key failed"); } if (isPrivateKey) { BIGNUM *bignum = BN_bin2bn(i_KeyByteArray, i_Length, NULL); if (bignum == NULL) { errMsg("Conversion to BIGNUM failed"); } // EVP_PKEY points to ecKey, so changing ecKey changes the returned value if (EC_KEY_set_private_key(ecKey, bignum) != 1) { errMsg("Setting private key failed"); } BN_free(bignum); } else { if (d2i_PublicKey(EVP_PKEY_EC, &key, &i_KeyByteArray, i_Length) == NULL) { errMsg("Loading public key failed"); } } return key; }
找到的替代示例代码
int main() { EVP_PKEY_CTX *ctx; EVP_PKEY *pkey = NULL; BIGNUM *priv; OSSL_PARAM_BLD *param_bld; OSSL_PARAM *params = NULL; int exitcode = 0; priv = BN_bin2bn(priv_data, sizeof(priv_data), NULL); param_bld = OSSL_PARAM_BLD_new(); if (priv != NULL && param_bld != NULL && OSSL_PARAM_BLD_push_utf8_string(param_bld, "group", "prime256v1", 0) && OSSL_PARAM_BLD_push_BN(param_bld, "priv", priv) && OSSL_PARAM_BLD_push_octet_string(param_bld, "pub", pub_data, sizeof(pub_data))) params = OSSL_PARAM_BLD_to_param(param_bld); ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL); if (ctx == NULL || params == NULL || EVP_PKEY_fromdata_init(ctx) <= 0 || EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_KEYPAIR, params) <= 0) { exitcode = 1; } else { /* Do what you want with |pkey| */ } //clear then exit }
解决方案:该方案完全适用
关于EVP_PKEY_KEYPAIR的说明
EVP_PKEY_KEYPAIR表示要创建包含公私钥对的EVP_PKEY结构。对于ECC密钥,OpenSSL可以仅通过私钥字节数组自动推导出对应的公钥,因此这个参数完全适配你的场景——即使只传入私钥数据,也能生成有效的带私钥的EVP_PKEY实例。
适配原函数的完整替换代码
下面是基于EVP_PKEY_fromdata重写的函数,同时修正了原代码中错误处理不完整(资源泄漏)的问题:
EVP_PKEY *convertByteArrayToEvpPkey(const uint8_t *i_KeyByteArray, size_t i_Length, bool isPrivateKey) { EVP_PKEY *pkey = NULL; EVP_PKEY_CTX *ctx = NULL; OSSL_PARAM_BLD *param_bld = NULL; OSSL_PARAM *params = NULL; BIGNUM *priv_bn = NULL; // 初始化参数构建器 param_bld = OSSL_PARAM_BLD_new(); if (param_bld == NULL) { errMsg("Failed to create OSSL_PARAM_BLD"); goto cleanup; } // 添加曲线参数(prime256v1) if (!OSSL_PARAM_BLD_push_utf8_string(param_bld, "group", "prime256v1", 0)) { errMsg("Failed to push group parameter"); goto cleanup; } if (isPrivateKey) { // 将私钥字节数组转换为BIGNUM priv_bn = BN_bin2bn(i_KeyByteArray, i_Length, NULL); if (priv_bn == NULL) { errMsg("Conversion to BIGNUM failed"); goto cleanup; } // 添加私钥参数 if (!OSSL_PARAM_BLD_push_BN(param_bld, "priv", priv_bn)) { errMsg("Failed to push private key parameter"); goto cleanup; } } else { // 适配原函数的d2i_PublicKey逻辑,加载DER格式公钥 if (!OSSL_PARAM_BLD_push_octet_string(param_bld, "der", i_KeyByteArray, i_Length)) { errMsg("Failed to push public key DER parameter"); goto cleanup; } } // 生成参数数组 params = OSSL_PARAM_BLD_to_param(param_bld); if (params == NULL) { errMsg("Failed to create OSSL_PARAM array"); goto cleanup; } // 创建EVP_PKEY上下文 ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL); if (ctx == NULL) { errMsg("Failed to create EVP_PKEY_CTX"); goto cleanup; } // 初始化fromdata操作 if (EVP_PKEY_fromdata_init(ctx) <= 0) { errMsg("EVP_PKEY_fromdata_init failed"); goto cleanup; } // 根据类型生成EVP_PKEY int key_type = isPrivateKey ? EVP_PKEY_KEYPAIR : EVP_PKEY_PUBLIC_KEY; if (EVP_PKEY_fromdata(ctx, &pkey, key_type, params) <= 0) { errMsg("EVP_PKEY_fromdata failed"); goto cleanup; } cleanup: // 统一释放资源,避免泄漏 BN_free(priv_bn); OSSL_PARAM_free(params); OSSL_PARAM_BLD_free(param_bld); EVP_PKEY_CTX_free(ctx); return pkey; }
关键说明
- 私钥处理:传入私钥字节数组时,使用
EVP_PKEY_KEYPAIR类型,OpenSSL自动从私钥推导公钥,生成完整密钥对结构。 - 公钥处理:原函数使用
d2i_PublicKey加载DER格式公钥,重写后改用"der"参数传入DER数据,类型指定为EVP_PKEY_PUBLIC_KEY,行为与原代码一致。若你的公钥输入是原始未编码的字节(仅X、Y坐标),需将参数名改为"pub"直接传入字节数组。 - 资源管理:通过
goto cleanup统一释放所有分配的资源,解决原代码中出错时的内存泄漏问题。
内容的提问来源于stack exchange,提问作者James
相关产品推荐
相关产品推荐

