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

OPENSSL 1.1.1d RSASSA-PSS签名生成后在线验证失败求助

问题:RSASSA-PSS签名生成后无法通过在线工具验证

我使用OpenSSL 1.1.1d生成RSASSA-PSS签名,代码生成的签名每次运行结果不同(符合PSS签名的特性),但用在线验证工具始终无法通过,麻烦帮忙排查代码问题。

原代码:

int main()
{
    EVP_MD_CTX *mdctx = NULL;
    int ret = 0;
    unsigned char *sig;
    EVP_PKEY *key = NULL;
    EVP_PKEY_CTX *ctx = NULL;
    char *msg = "This is a nice message to be signed.";
    int len;
    size_t siglen;
    FILE *fp;
    char sig64[512];
    unsigned char md[SHA256_DIGEST_LENGTH];
    SHA256_CTX sha256;
    SHA256_Init(&sha256);
    SHA256_Update(&sha256, msg, strlen(msg));
    SHA256_Final(md, &sha256);

    sig = NULL;
    len = strlen(msg);

    fp = fopen("MYPRIVATEKEY.KEY", "r");
    key = EVP_PKEY_new();
    PEM_read_PrivateKey(fp, &key, NULL, NULL);
    ctx = EVP_PKEY_CTX_new(key, NULL);

    if (EVP_PKEY_sign_init(ctx) <= 0)
        goto err;
    if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PSS_PADDING) <= 0)
        goto err;
    if (EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha256()) <= 0)
        goto err;


    /* Determine buffer length */
    if (EVP_PKEY_sign(ctx, NULL, &siglen, md, SHA256_DIGEST_LENGTH) <= 0)
        goto err;

    sig = OPENSSL_malloc(siglen);

    if (!sig)
        goto err;

    if (EVP_PKEY_sign(ctx, sig, &siglen, md, SHA256_DIGEST_LENGTH) <= 0)
        goto err;

    /* Signature is siglen bytes written to buffer sig */

    /* Success */
    ret = 1;
    Encode_B64(sig, siglen, sig64);
    printf("Signature: %s\n", sig64);

err:
    if(ret != 1)
    {
        /* Do some error handling */
    }

    /* Clean up */
    if(mdctx) EVP_MD_CTX_free(mdctx);
    EVP_PKEY_free(key);
    EVP_PKEY_CTX_free(ctx);
    if(sig && !ret) OPENSSL_free(sig);

    return 0;
}

问题排查与修正

核心问题分析

  1. 重复哈希导致签名不匹配
    你手动对消息做了SHA256哈希,再把哈希值传给EVP_PKEY_sign,但OpenSSL在PSS签名模式下,会对传入的数据再执行一次指定的哈希(这里是SHA256)。相当于最终签名的是SHA256(SHA256(原消息)),而在线工具通常直接对原消息做一次SHA256哈希后验证,逻辑完全不匹配。

  2. 未显式对齐PSS盐长度
    OpenSSL默认PSS盐长度等于哈希输出长度(SHA256为32字节),但部分在线工具可能使用不同的默认值(比如最大允许盐长度),不显式指定可能导致验证失败。

  3. 缺少关键步骤的错误检查
    文件打开、私钥读取等操作没有错误判断,如果私钥读取失败,后续操作会静默异常,无法快速定位问题。

修正后的代码

#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>
#include <openssl/rsa.h>
#include <openssl/sha.h>
#include <openssl/bio.h>
#include <openssl/buffer.h>

// 替换为你的Base64实现,或直接使用OpenSSL的BIO接口
int Encode_B64(const unsigned char* input, size_t input_len, char* output) {
    BIO *bio, *b64;
    BUF_MEM *buf_ptr;

    b64 = BIO_new(BIO_f_base64());
    bio = BIO_new(BIO_s_mem());
    bio = BIO_push(b64, bio);

    BIO_write(bio, input, input_len);
    BIO_flush(bio);
    BIO_get_mem_ptr(bio, &buf_ptr);

    memcpy(output, buf_ptr->data, buf_ptr->length - 1); // 移除Base64默认的末尾换行
    output[buf_ptr->length - 1] = '\0';

    BIO_free_all(bio);
    return 1;
}

int main()
{
    int ret = 0;
    unsigned char *sig = NULL;
    EVP_PKEY *key = NULL;
    EVP_PKEY_CTX *ctx = NULL;
    const char *msg = "This is a nice message to be signed.";
    size_t siglen;
    FILE *fp;
    char sig64[512];

    // 读取私钥并添加错误检查
    fp = fopen("MYPRIVATEKEY.KEY", "r");
    if (!fp) {
        fprintf(stderr, "私钥文件打开失败\n");
        goto err;
    }

    key = PEM_read_PrivateKey(fp, NULL, NULL, NULL);
    fclose(fp);
    if (!key) {
        fprintf(stderr, "私钥读取失败\n");
        goto err;
    }

    ctx = EVP_PKEY_CTX_new(key, NULL);
    if (!ctx) {
        fprintf(stderr, "签名上下文创建失败\n");
        goto err;
    }

    // 初始化签名上下文
    if (EVP_PKEY_sign_init(ctx) <= 0) {
        fprintf(stderr, "签名初始化失败\n");
        goto err;
    }

    // 设置PSS填充与哈希算法
    if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PSS_PADDING) <= 0) {
        fprintf(stderr, "设置PSS填充失败\n");
        goto err;
    }
    if (EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha256()) <= 0) {
        fprintf(stderr, "设置签名哈希算法失败\n");
        goto err;
    }

    // 显式设置盐长度为哈希输出长度(-1表示使用哈希长度)
    if (EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, -1) <= 0) {
        fprintf(stderr, "设置PSS盐长度失败\n");
        goto err;
    }

    // 计算签名缓冲区大小
    if (EVP_PKEY_sign(ctx, NULL, &siglen, (const unsigned char*)msg, strlen(msg)) <= 0) {
        fprintf(stderr, "获取签名缓冲区长度失败\n");
        goto err;
    }

    sig = OPENSSL_malloc(siglen);
    if (!sig) {
        fprintf(stderr, "内存分配失败\n");
        goto err;
    }

    // 直接对原消息签名,由OpenSSL内部完成哈希计算
    if (EVP_PKEY_sign(ctx, sig, &siglen, (const unsigned char*)msg, strlen(msg)) <= 0) {
        fprintf(stderr, "签名生成失败\n");
        goto err;
    }

    // Base64编码签名
    if (!Encode_B64(sig, siglen, sig64)) {
        fprintf(stderr, "Base64编码失败\n");
        goto err;
    }

    printf("Signature: %s\n", sig64);
    ret = 1;

err:
    if (ret != 1) {
        fprintf(stderr, "签名生成流程失败\n");
    }

    // 资源清理
    EVP_PKEY_free(key);
    EVP_PKEY_CTX_free(ctx);
    if (sig) OPENSSL_free(sig);

    return ret ? 0 : 1;
}

关键修正点说明

  • 移除手动哈希:直接将原消息传入EVP_PKEY_sign,让OpenSSL内部完成哈希计算,和在线工具的验证逻辑对齐。
  • 显式指定盐长度:用EVP_PKEY_CTX_set_rsa_pss_saltlen固定盐长度,确保和在线工具的参数一致(如果在线工具要求最大盐长度,可改为RSA_PSS_SALTLEN_MAX)。
  • 添加错误检查:对文件操作、上下文创建等步骤增加错误判断,方便快速定位问题。
  • 及时关闭文件:读取私钥后立即关闭文件,避免资源泄漏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 08:06:52