You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

关键说明

  1. 私钥处理:传入私钥字节数组时,使用EVP_PKEY_KEYPAIR类型,OpenSSL自动从私钥推导公钥,生成完整密钥对结构。
  2. 公钥处理:原函数使用d2i_PublicKey加载DER格式公钥,重写后改用"der"参数传入DER数据,类型指定为EVP_PKEY_PUBLIC_KEY,行为与原代码一致。若你的公钥输入是原始未编码的字节(仅X、Y坐标),需将参数名改为"pub"直接传入字节数组。
  3. 资源管理:通过goto cleanup统一释放所有分配的资源,解决原代码中出错时的内存泄漏问题。

内容的提问来源于stack exchange,提问作者James

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 09:55:06