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

基于OpenSSL RSASSA-PSS签名API:如何同时获取摘要与签名?

同时获取RSASSA-PSS签名与原始摘要的OpenSSL实现方案

刚好之前遇到过一模一样的需求,给你两个实用的解决方案,看你项目的现有代码情况选就行:

方案一:分开计算摘要 + 直接签名摘要(推荐)

这个方案逻辑最清晰,先单独算出原始数据的摘要,再用OpenSSL的EVP_PKEY_sign接口对摘要做RSASSA-PSS签名,完全避开EVP_digestsignXXX系列的“合并操作”限制。

步骤说明

  1. 用标准的EVP摘要上下文计算原始数据的哈希值(也就是你要的摘要);
  2. 初始化签名上下文,配置RSASSA-PSS的填充规则、盐长度等参数;
  3. 直接对已计算好的摘要执行签名,得到最终签名值。

代码示例

#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摘要上下文设置回调函数的方式,在内部计算摘要完成时,把摘要值复制出来留存。

步骤说明

  1. 定义一个用于保存摘要的结构体,以及回调函数;
  2. 初始化EVP_DigestSign上下文时,绑定这个回调;
  3. 正常调用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.27 18:37:49