OpenSSL 签名验证详解:PKCS7* p7、cafile 与 RSA 验签实现

OpenSSL 签名验证详解:PKCS7* p7、cafile 与 RSA 验签实现

摘要

本文深入剖析 OpenSSL 中 PKCS7* p7 数据结构和 cafile 的作用及相互关系,详细讲解基于 OpenSSL 的 RSA 验签字符串的 C 语言实现,涵盖签名解析、证书加载、验证流程及关键要点,助力开发者掌握数字签名验证技术,确保数据完整性和来源可靠性。

一、PKCS7* p7 与 cafile 的关键作用

(一)PKCS7* p7:签名数据的核心载体

  1. 结构与内容

    • PKCS#7(Public Key Cryptography Standards #7)标准涵盖数字签名、证书、数据加密及消息认证。PKCS7* p7 是处理 PKCS#7 格式数字签名的关键数据结构,通过 d2i_PKCS7_bio() 函数将 DER 编码的 PKCS#7 数据解析为该结构体。
    • p7 包含丰富的签名信息,如签名者信息、签名算法、签名数据及证书链等。在验证签名时,PKCS7_verify() 函数利用这些信息验证签名有效性、检查证书链完整性,确保数据未被篡改。
  2. 缺失影响

    c 复制代码
    PKCS7* p7 = d2i_PKCS7_bio(signp7_mem, 0);
    if (p7 == NULL) {
        // 无法获取签名信息,验证过程无法进行
    }
    • 若缺失 p7,将无法读取签名数据,整个验证过程直接失败,相当于没有验证对象。
  3. 释放内存

    • 使用完毕后,需调用 PKCS7_free(p7); 释放结构体,避免内存泄漏。

(二)cafile:信任链的根基

  1. 作用与使用

    • cafile 是包含根证书或中间证书的 CA(Certificate Authority,证书颁发机构)证书文件,用于建立信任链,验证签名者证书合法性,是数字签名验证的信任锚点。
    • 在代码中,通过 X509_STORE_load_locations(store, cafile, NULL) 将 CA 证书加载到证书库。PKCS7_verify() 函数利用加载的 CA 证书验证签名者证书,检查证书链完整性,确认签名者证书由可信 CA 签发。
  2. 缺失影响

    c 复制代码
    if (!X509_STORE_load_locations(store, cafile, NULL)) {
        LOG_ERR("Load CA file %s fail\n", cafile);
        // 无法建立信任链,验证过程无法完成
        return CRYPTO_FAIL;
    }
    • cafile 缺失或无效,无法验证签名者证书真实性,无法确认签名者身份,相当于有签名但无法确认其真实性,验证过程无法完成。
  3. 两者关系

    • p7 是验证对象(要验证什么),cafile 是验证依据(如何验证),二者缺一不可,只有结合才能完成完整的签名验证过程。

二、OpenSSL RSA 验签字符串的 C 语言实现

(一)完整代码

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <openssl/bio.h>
#include <openssl/buffer.h>

// 错误处理函数
void handle_openssl_error() {
    ERR_print_errors_fp(stderr);
    exit(EXIT_FAILURE);
}

// Base64解码函数
int base64_decode(const char *base64_data, unsigned char **decoded_data, size_t *decoded_len) {
    BIO *bio, *b64;
    int len = strlen(base64_data);
    
    b64 = BIO_new(BIO_f_base64());
    BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
    bio = BIO_new_mem_buf((void*)base64_data, len);
    bio = BIO_push(b64, bio);
    
    *decoded_data = (unsigned char*)malloc(len);
    if (!*decoded_data) {
        BIO_free_all(bio);
        return -1;
    }
    
    *decoded_len = BIO_read(bio, *decoded_data, len);
    BIO_free_all(bio);
    
    return (*decoded_len > 0) ? 0 : -1;
}

// RSA验签函数
int rsa_verify_string(const char *pubkey_path, 
                     const char *message,
                     const char *base64_signature,
                     const char *hash_alg) {
    EVP_MD_CTX *mdctx = NULL;
    EVP_PKEY *pubkey = NULL;
    FILE *pubkey_fp = NULL;
    unsigned char *signature = NULL;
    size_t sig_len = 0;
    int ret = -1;
    const EVP_MD *md = NULL;

    // 1. 根据算法名称获取哈希算法
    if (strcmp(hash_alg, "sha1") == 0) {
        md = EVP_sha1();
    } else if (strcmp(hash_alg, "sha256") == 0) {
        md = EVP_sha256();
    } else {
        fprintf(stderr, "Unsupported hash algorithm: %s\n", hash_alg);
        goto cleanup;
    }

    // 2. 加载公钥
    pubkey_fp = fopen(pubkey_path, "r");
    if (!pubkey_fp) {
        fprintf(stderr, "Error opening public key file\n");
        goto cleanup;
    }
    
    pubkey = PEM_read_PUBKEY(pubkey_fp, NULL, NULL, NULL);
    if (!pubkey) {
        fprintf(stderr, "Error reading public key\n");
        goto cleanup;
    }

    // 3. Base64解码签名
    if (base64_decode(base64_signature, &signature, &sig_len) != 0) {
        fprintf(stderr, "Error decoding base64 signature\n");
        goto cleanup;
    }

    // 4. 初始化验签上下文
    mdctx = EVP_MD_CTX_new();
    if (!mdctx) {
        fprintf(stderr, "Error creating EVP_MD_CTX\n");
        goto cleanup;
    }
    
    if (EVP_DigestVerifyInit(mdctx, NULL, md, NULL, pubkey) != 1) {
        fprintf(stderr, "Error initializing verification\n");
        goto cleanup;
    }

    // 5. 更新验签数据
    if (EVP_DigestVerifyUpdate(mdctx, message, strlen(message)) != 1) {
        fprintf(stderr, "Error updating verification data\n");
        goto cleanup;
    }

    // 6. 完成验签
    ret = EVP_DigestVerifyFinal(mdctx, signature, sig_len);
    if (ret == 1) {
        printf("Signature verification successful (%s)\n", hash_alg);
    } else if (ret == 0) {
        printf("Signature verification failed (%s)\n", hash_alg);
    } else {
        fprintf(stderr, "Error during verification\n");
        ret = -1;
    }

cleanup:
    // 7. 清理资源
    if (mdctx) EVP_MD_CTX_free(mdctx);
    if (pubkey) EVP_PKEY_free(pubkey);
    if (pubkey_fp) fclose(pubkey_fp);
    if (signature) free(signature);
    
    return ret;
}

int main(int argc, char *argv[]) {
    if (argc != 5) {
        printf("Usage: %s <public_key.pem> <message> <base64_signature> <sha1|sha256>\n", argv[0]);
        return 1;
    }

    // 初始化OpenSSL
    OpenSSL_add_all_algorithms();
    ERR_load_crypto_strings();

    int result = rsa_verify_string(argv[1], argv[2], argv[3], argv[4]);
    
    // 清理OpenSSL
    EVP_cleanup();
    ERR_free_strings();
    
    return (result != 1);
}

(二)代码详解

  1. 哈希算法选择

    c 复制代码
    if (strcmp(hash_alg, "sha1") == 0) {
        md = EVP_sha1();
    } else if (strcmp(hash_alg, "sha256") == 0) {
        md = EVP_sha256();
    }
    • 根据传入的哈希算法名称("sha1" 或 "sha256"),获取对应的哈希算法结构体指针。也可使用 EVP_get_digestbyname("sha256") 动态获取算法。
  2. 公钥加载

    c 复制代码
    pubkey = PEM_read_PUBKEY(pubkey_fp, NULL, NULL, NULL);
    • 从 PEM 格式的公钥文件中读取公钥,支持 RSA、DSA、ECDSA 等多种公钥类型。
  3. Base64 解码

    c 复制代码
    BIO *b64 = BIO_new(BIO_f_base64());
    BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
    • 利用 OpenSSL 的 BIO 接口进行 Base64 解码,BIO_FLAGS_BASE64_NO_NL 标志表示不处理换行符。
  4. 验签流程

    c 复制代码
    EVP_DigestVerifyInit(mdctx, NULL, md, NULL, pubkey);
    EVP_DigestVerifyUpdate(mdctx, message, strlen(message));
    EVP_DigestVerifyFinal(mdctx, signature, sig_len);
    • EVP_DigestVerifyInit:初始化验签上下文,指定哈希算法和公钥。
    • EVP_DigestVerifyUpdate:输入待验证的数据,可多次调用以处理大消息。
    • EVP_DigestVerifyFinal:完成验签并返回结果。
  5. 错误处理

    c 复制代码
    ERR_print_errors_fp(stderr);
    • OpenSSL 错误处理机制可输出详细错误信息,每个 OpenSSL 函数调用后都应检查返回值。

(三)使用示例

  1. 编译命令

    bash 复制代码
    gcc rsa_verify.c -o rsa_verify -lssl -lcrypto
  2. 运行示例

    • 使用 SHA256 验证:

      bash 复制代码
      ./rsa_verify public_key.pem "message to verify" "base64_signature" sha256
    • 使用 SHA1 验证:

      bash 复制代码
      ./rsa_verify public_key.pem "message to verify" "base64_signature" sha1

(四)关键点说明

  1. 哈希算法选择

    • SHA1 产生 160 位(20 字节)摘要,SHA256 产生 256 位(32 字节)摘要。现代应用推荐使用 SHA256,SHA1 已逐渐被淘汰。
  2. 签名格式

    • RSA 签名通常是 PKCS#1 v1.5 格式,签名长度等于 RSA 密钥长度(如 2048 位 = 256 字节)。
  3. 性能考虑

    • 对于大消息,可分块调用 EVP_DigestVerifyUpdate。SHA256 计算比 SHA1 稍慢但更安全。
  4. 资源管理

    • 必须正确释放所有 OpenSSL 对象,使用 goto cleanup 模式集中处理资源释放。
  5. Base64 处理

    • 签名通常以 Base64 编码传输,验签前需要解码为二进制格式。

通过上述内容,开发者可以全面了解 OpenSSL 中 PKCS7* p7 和 cafile 的作用、关系以及 RSA 验签的 C 语言实现细节,从而在实际项目中灵活应用数字签名验证技术,保障数据的安全性和完整性。

相关推荐
weixin_527550403 分钟前
VMware 安装 Ubuntu 实战教程
linux·运维·ubuntu
白总Server9 分钟前
AxumStatusCode细化Rust Web标准格式响应
java·linux·运维·服务器·开发语言·http·rust
鬼才血脉9 分钟前
ubuntu国内镜像源手动配置
linux·运维·ubuntu
运维老曾1 小时前
linux 性能优化CPU
运维·linux 优化
新时代牛马1 小时前
linux使用服务添加一个开机启动脚本
linux·运维·服务器
AWS官方合作商1 小时前
AWS EC2 实例告警的创建与删除
运维·服务器·云计算·aws
@comefly2 小时前
本地github ssh多账号问题
运维·ssh·github
闲人-闲人2 小时前
HTTP Accept简介
运维·网络协议·http
皓月盈江2 小时前
阿里云服务器采用crontab定时任务使acme.sh全自动化申请续签免费SSL证书,并部署在Linux宝塔网站和雷池WAF
服务器·阿里云·crontab·雷池waf·linux宝塔面板·acme.h·续签ssl证书
病树前头2 小时前
服务器tty2终端如何关机
linux·运维·服务器