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; }
问题排查与修正
核心问题分析
重复哈希导致签名不匹配
你手动对消息做了SHA256哈希,再把哈希值传给EVP_PKEY_sign,但OpenSSL在PSS签名模式下,会对传入的数据再执行一次指定的哈希(这里是SHA256)。相当于最终签名的是SHA256(SHA256(原消息)),而在线工具通常直接对原消息做一次SHA256哈希后验证,逻辑完全不匹配。未显式对齐PSS盐长度
OpenSSL默认PSS盐长度等于哈希输出长度(SHA256为32字节),但部分在线工具可能使用不同的默认值(比如最大允许盐长度),不显式指定可能导致验证失败。缺少关键步骤的错误检查
文件打开、私钥读取等操作没有错误判断,如果私钥读取失败,后续操作会静默异常,无法快速定位问题。
修正后的代码
#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
相关产品推荐
相关产品推荐

