基于OpenSSL RSASSA-PSS签名API:如何同时获取摘要与签名?
同时获取RSASSA-PSS签名与原始摘要的OpenSSL实现方案
刚好之前遇到过一模一样的需求,给你两个实用的解决方案,看你项目的现有代码情况选就行:
方案一:分开计算摘要 + 直接签名摘要(推荐)
这个方案逻辑最清晰,先单独算出原始数据的摘要,再用OpenSSL的EVP_PKEY_sign接口对摘要做RSASSA-PSS签名,完全避开EVP_digestsignXXX系列的“合并操作”限制。
步骤说明
- 用标准的EVP摘要上下文计算原始数据的哈希值(也就是你要的摘要);
- 初始化签名上下文,配置RSASSA-PSS的填充规则、盐长度等参数;
- 直接对已计算好的摘要执行签名,得到最终签名值。
代码示例
#include <openssl/evp.h> #include <openssl/rsa.h> // 假设你已经有: // - 原始数据:unsigned char *data,长度size_t data_len // - RSA私钥:EVP_PKEY *pkey(已正确加载) // 存储摘要的变量 unsigned char digest[EVP_MAX_MD_SIZE]; unsigned int digest_len; // 第一步:计算原始数据的摘要(这里用SHA256,可根据需求替换为SHA1/SHA3等) EVP_MD_CTX *md_ctx = EVP_MD_CTX_new(); EVP_DigestInit_ex(md_ctx, EVP_sha256(), NULL); EVP_DigestUpdate(md_ctx, data, data_len); EVP_DigestFinal_ex(md_ctx, digest, &digest_len); EVP_MD_CTX_free(md_ctx); // 第二步:对摘要执行RSASSA-PSS签名 unsigned char *signature = NULL; size_t sig_len; // 初始化签名上下文 EVP_PKEY_CTX *pkey_ctx = EVP_PKEY_CTX_new(pkey, NULL); EVP_PKEY_sign_init(pkey_ctx); // 配置RSASSA-PSS参数 EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING); // 设置盐长度为-1,表示与摘要长度一致(这是PSS的推荐配置) EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, -1); // 指定签名使用的摘要算法(必须和第一步的摘要算法一致) EVP_PKEY_CTX_set_signature_md(pkey_ctx, EVP_sha256()); // 先获取签名所需的内存长度 EVP_PKEY_sign(pkey_ctx, NULL, &sig_len, digest, digest_len); signature = OPENSSL_malloc(sig_len); // 执行签名 EVP_PKEY_sign(pkey_ctx, signature, &sig_len, digest, digest_len); // 现在: // digest 就是原始数据的摘要,长度是digest_len // signature 就是RSASSA-PSS签名,长度是sig_len // 记得释放资源 OPENSSL_free(signature); EVP_PKEY_CTX_free(pkey_ctx);
方案二:给EVP_digestsignXXX加回调获取摘要
如果你的项目已经大量使用EVP_digestsignXXX系列函数,不想重构代码,可以通过给EVP摘要上下文设置回调函数的方式,在内部计算摘要完成时,把摘要值复制出来留存。
步骤说明
- 定义一个用于保存摘要的结构体,以及回调函数;
- 初始化
EVP_DigestSign上下文时,绑定这个回调; - 正常调用
EVP_DigestSignUpdate和EVP_DigestSignFinal,回调会在摘要计算完成时自动触发,你就能拿到摘要值。
代码示例
#include <openssl/evp.h> #include <openssl/rsa.h> #include <string.h> // 用于保存摘要的结构体 typedef struct { unsigned char *digest; unsigned int digest_len; } DigestHolder; // 自定义回调:当内部计算完摘要时,会调用这个函数把摘要数据传出来 static int digest_save_callback(const void *md_data, size_t md_len, void *arg) { DigestHolder *holder = (DigestHolder*)arg; // 分配内存保存摘要 holder->digest = OPENSSL_malloc(md_len); if (!holder->digest) return 0; memcpy(holder->digest, md_data, md_len); holder->digest_len = md_len; return 1; } // 使用示例 int main() { // 假设你已经有: // - 原始数据:unsigned char *data,长度size_t data_len // - RSA私钥:EVP_PKEY *pkey(已正确加载) unsigned char *signature = NULL; size_t sig_len; DigestHolder holder = {NULL, 0}; // 初始化签名上下文 EVP_MD_CTX *sign_ctx = EVP_MD_CTX_new(); EVP_DigestSignInit(sign_ctx, NULL, EVP_sha256(), NULL, pkey); // 配置RSASSA-PSS参数 EVP_PKEY_CTX *pkey_ctx = EVP_MD_CTX_get_pkey_ctx(sign_ctx); EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING); EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, -1); // 绑定回调函数,用于获取摘要 EVP_MD_CTX_set_md_data_callback(sign_ctx, digest_save_callback, &holder); // 传入原始数据 EVP_DigestSignUpdate(sign_ctx, data, data_len); // 获取签名长度并分配内存 EVP_DigestSignFinal(sign_ctx, NULL, &sig_len); signature = OPENSSL_malloc(sig_len); // 完成签名,此时回调已经触发,holder里已经保存了摘要 EVP_DigestSignFinal(sign_ctx, signature, &sig_len); // 现在: // holder.digest 就是原始数据的摘要,长度是holder.digest_len // signature 就是RSASSA-PSS签名,长度是sig_len // 释放资源 OPENSSL_free(signature); OPENSSL_free(holder.digest); EVP_MD_CTX_free(sign_ctx); return 0; }
注意事项
- 两个方案中,摘要算法必须前后一致(比如第一步用SHA256,签名配置里也要指定SHA256),否则签名会无效;
- 盐长度推荐设置为
-1(与摘要长度相同),这是RSASSA-PSS的标准安全配置; - 确保你的OpenSSL版本是1.1.0及以上,示例中用的
EVP_MD_CTX_new等接口是1.1.0引入的,旧版本需要替换为EVP_MD_CTX_create等兼容接口; - 所有动态分配的内存(比如
signature、holder.digest)记得手动释放,避免内存泄漏。
内容的提问来源于stack exchange,提问作者PJA
相关产品推荐
相关产品推荐

