OpenSSL 3.1及以上版本基于私钥创建EVP_PKEY的方法
在OpenSSL 3.1中创建EVP_PKEY用于EVP_DigestSignInit的正确方式
OpenSSL 3.x开始废弃了直接操作RSA/EC等具体密钥结构并赋值到EVP_PKEY的API,取而代之的是算法无关的密钥读取API,直接从PEM/DER等格式加载出EVP_PKEY对象,无需中间转换。
核心替代方案:使用PEM_read_bio_PrivateKey直接加载私钥
这个函数是OpenSSL推荐的跨版本兼容方式,能直接从PEM格式读取私钥并返回EVP_PKEY*,完美适配EVP_DigestSignInit的需求。
完整代码示例
#include <openssl/evp.h> #include <openssl/pem.h> #include <openssl/err.h> int sign_data(const unsigned char *data, size_t data_len, const char *private_key_path, unsigned char **signature, size_t *signature_len) { EVP_PKEY *pkey = NULL; EVP_MD_CTX *md_ctx = NULL; int ret = 0; // 1. 打开私钥文件并创建BIO BIO *bio = BIO_new_file(private_key_path, "r"); if (!bio) { ERR_print_errors_fp(stderr); goto cleanup; } // 2. 直接读取私钥到EVP_PKEY(自动识别密钥类型:RSA/EC等) pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL); if (!pkey) { ERR_print_errors_fp(stderr); goto cleanup; } // 3. 初始化签名上下文 md_ctx = EVP_MD_CTX_new(); if (!md_ctx) { ERR_print_errors_fp(stderr); goto cleanup; } // 4. 设置签名算法(比如SHA256withRSA) if (EVP_DigestSignInit(md_ctx, NULL, EVP_sha256(), NULL, pkey) != 1) { ERR_print_errors_fp(stderr); goto cleanup; } // 5. 传入待签名数据 if (EVP_DigestSignUpdate(md_ctx, data, data_len) != 1) { ERR_print_errors_fp(stderr); goto cleanup; } // 6. 获取签名长度并分配内存 if (EVP_DigestSignFinal(md_ctx, NULL, signature_len) != 1) { ERR_print_errors_fp(stderr); goto cleanup; } *signature = OPENSSL_malloc(*signature_len); if (!*signature) { goto cleanup; } // 7. 生成签名 if (EVP_DigestSignFinal(md_ctx, *signature, signature_len) != 1) { OPENSSL_free(*signature); *signature = NULL; goto cleanup; } ret = 1; cleanup: // 释放资源 BIO_free(bio); EVP_PKEY_free(pkey); EVP_MD_CTX_free(md_ctx); return ret; }
关键说明
PEM_read_bio_PrivateKey会自动识别私钥的算法类型(RSA、ECDSA等),无需手动指定,返回的EVP_PKEY可以直接用于所有EVP层的签名/验签/加密操作。- 若需从内存中的PEM数据读取私钥,可使用
BIO_new_mem_buf替代BIO_new_file。 - 错误处理依赖
ERR_print_errors_fp打印OpenSSL错误栈,方便定位问题。 - 所有OpenSSL对象(BIO、EVP_PKEY、EVP_MD_CTX)使用后必须释放,避免内存泄漏。
进阶:使用OSSL_DECODER(OpenSSL 3.x原生API)
如果需要更灵活的密钥解码(如支持更多格式、自定义参数),可以用OpenSSL 3.x引入的OSSL_DECODER系列API,示例如下:
#include <openssl/decoder.h> // 从文件读取私钥到EVP_PKEY EVP_PKEY *load_private_key(const char *path) { EVP_PKEY *pkey = NULL; OSSL_DECODER_CTX *dctx = OSSL_DECODER_CTX_new_for_pkey(&pkey, OSSL_KEYMGMT_SELECT_KEYPAIR_PRIVATE, "PEM", NULL, NULL); if (!dctx) { ERR_print_errors_fp(stderr); return NULL; } BIO *bio = BIO_new_file(path, "r"); if (!bio) { ERR_print_errors_fp(stderr); OSSL_DECODER_CTX_free(dctx); return NULL; } if (OSSL_DECODER_from_bio(dctx, bio) != 1) { ERR_print_errors_fp(stderr); pkey = NULL; } BIO_free(bio); OSSL_DECODER_CTX_free(dctx); return pkey; }
这个API是OpenSSL 3.x的现代接口,支持更丰富的密钥格式和配置,适合复杂场景。
内容的提问来源于stack exchange,提问作者Farrokh Farivar
相关产品推荐
相关产品推荐

