如何在OpenSSL 3.0 EVP_PKEY API中仅初始化公钥?——EVP_PKEY_fromdata调用失败问题求解
修复RSA公钥构建失败的问题
我看了你的代码,发现几个关键问题导致EVP_PKEY_fromdata()调用失败,咱们一个个理清楚:
1. 参数名匹配错误
在OSSL_PARAM_BLD_push_BN()里你传了key作为参数名,但RSA公钥的模数参数名明确是"n"——得和你提取参数时用的名称完全对应才行,这是最核心的错误。
2. 密钥类型指定错误
你调用EVP_PKEY_fromdata()时用了EVP_PKEY_KEYPAIR,但签名验证端只需要公钥,应该用EVP_PKEY_PUBLIC_KEY。这个标志会告诉OpenSSL你要创建的是公钥而非完整密钥对,符合你的场景需求。
3. 资源泄漏与错误处理不完整
- 你创建的
BIGNUM没有释放,会导致内存泄漏; - 错误分支里的资源(比如
ctx、parameters)没有完整清理,容易引发后续问题。
修正后的完整代码
void SetPublicKey(const std::vector<unsigned char> &n_bytes) { // 先释放旧密钥(如果存在) if (keys_) { EVP_PKEY_free(keys_); keys_ = nullptr; } OSSL_PARAM_BLD *param_bld = OSSL_PARAM_BLD_new(); if (!param_bld) { throw std::runtime_error("Failed to create OSSL_PARAM builder"); } BIGNUM *n_bn = BN_bin2bn(n_bytes.data(), static_cast<int>(n_bytes.size()), nullptr); if (!n_bn) { OSSL_PARAM_BLD_free(param_bld); throw std::runtime_error("Failed to convert bytes to BIGNUM"); } // 推送公钥模数参数,参数名必须是"n" if (!OSSL_PARAM_BLD_push_BN(param_bld, "n", n_bn)) { BN_free(n_bn); OSSL_PARAM_BLD_free(param_bld); throw std::runtime_error("Failed to push 'n' parameter"); } // 推送默认公钥指数RSA_F4(0x10001) if (!OSSL_PARAM_BLD_push_long(param_bld, "e", RSA_F4)) { BN_free(n_bn); OSSL_PARAM_BLD_free(param_bld); throw std::runtime_error("Failed to push 'e' parameter"); } OSSL_PARAM *parameters = OSSL_PARAM_BLD_to_param(param_bld); OSSL_PARAM_BLD_free(param_bld); BN_free(n_bn); // BIGNUM已被参数构建器复制,此处可释放 if (!parameters) { throw std::runtime_error("Failed to create OSSL_PARAM array"); } EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr); if (!ctx) { OSSL_PARAM_free(parameters); throw std::runtime_error("Failed to create EVP_PKEY context"); } // 初始化公钥构建上下文 if (EVP_PKEY_fromdata_init(ctx) <= 0) { EVP_PKEY_CTX_free(ctx); OSSL_PARAM_free(parameters); throw std::runtime_error("Failed to initialize fromdata context"); } // 构建公钥,使用EVP_PKEY_PUBLIC_KEY类型 if (EVP_PKEY_fromdata(ctx, &keys_, EVP_PKEY_PUBLIC_KEY, parameters) <= 0) { EVP_PKEY_CTX_free(ctx); OSSL_PARAM_free(parameters); throw std::runtime_error("Failed to create public key from parameters"); } // 清理资源 EVP_PKEY_CTX_free(ctx); OSSL_PARAM_free(parameters); }
额外注意事项
- 确保你提取的
"n"是正确的大端字节序(BN_bn2bin()输出的就是大端,和BN_bin2bn()的输入要求一致); - 如果你的场景中公钥指数不是默认的RSA_F4,需要从原始密钥中提取
"e"参数并传入,不要硬编码; - 所有OpenSSL资源(
EVP_PKEY、EVP_PKEY_CTX、OSSL_PARAM、BIGNUM)都要在错误路径中正确释放,避免内存泄漏。
内容的提问来源于stack exchange,提问作者Anton Karton




