当前位置: 首页 > web >正文

OpenSSL 文件验签与字符串验签原理及 C 语言实现详解

OpenSSL 文件验签与字符串验签原理及 C 语言实现详解

摘要

本文深入探讨了基于 OpenSSL 的文件验签与字符串验签原理及 C 语言实现方法。先阐述数字签名验证原理,包括基于非对称加密算法和哈希算法的签名生成与验证流程,着重介绍 OpenSSL 中利用 EVP 高级接口实现数字签名验证的关键函数及其作用。随后分别提供文件验签和字符串验签的完整 C 语言代码示例,详细解析各步骤要点,如公钥加载、数据读取、验证上下文初始化、更新验证数据及完成验证等,并给出代码步骤详解。此外,还涵盖错误处理与调试技巧,通过 OpenSSL 错误报告机制排查验证失败原因,列举常见错误类型及处理方式。同时对比文件验签与字符串验签特性,从数据来源、加载方式、大数据处理、适用场景及性能角度分析差异。最后总结最佳实践,涵盖算法选择、密钥管理、错误处理、资源管理和性能优化等方面,助力开发者在 OpenSSL 中高效实现文件和字符串签名验证功能,保障数据完整性和真实性。

数字签名验证原理

签名生成原理

  • 哈希计算 :对原始数据使用哈希算法(如 SHA - 256)生成固定长度的消息摘要。
  • 私钥加密 :使用签名者的私钥对消息摘要进行加密,生成数字签名。
  • 签名存储 :将数字签名与原始数据一起存储或传输。

签名验证原理

  • 哈希计算 :对接收到的原始数据使用相同的哈希算法生成消息摘要。
  • 公钥解密 :使用签名者的公钥对数字签名进行解密,得到原始消息摘要。
  • 摘要比对 :比较计算得到的消息摘要和解密得到的消息摘要,如果一致则验证通过。

在 OpenSSL 中,借助 EVP(Envelope)高级接口实现上述流程,主要涉及以下函数:

  • EVP_DigestVerifyInit() :初始化验证上下文。
  • EVP_DigestVerifyUpdate() :输入待验证数据。
  • EVP_DigestVerifyFinal() :完成验证并返回结果。

文件验签 C 语言实现

以下是使用 OpenSSL EVP 接口实现文件验签的完整 C 代码示例:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/err.h>// 验证文件签名
int verify_file_signature(const char* pubkey_file, const char* data_file,const char* sig_file) {EVP_MD_CTX* mdctx = NULL;EVP_PKEY* pubkey = NULL;FILE* pubkey_fp = NULL;FILE* data_fp = NULL;FILE* sig_fp = NULL;unsigned char* data = NULL;unsigned char* sig = NULL;size_t data_len = 0;size_t sig_len = 0;int result = -1;// 1. 读取公钥文件pubkey_fp = fopen(pubkey_file, "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;}// 2. 读取数据文件data_fp = fopen(data_file, "rb");if (!data_fp) {fprintf(stderr, "Error opening data file\n");goto cleanup;}fseek(data_fp, 0, SEEK_END);data_len = ftell(data_fp);fseek(data_fp, 0, SEEK_SET);data = (unsigned char*)malloc(data_len);if (!data) {fprintf(stderr, "Memory allocation error\n");goto cleanup;}if (fread(data, 1, data_len, data_fp) != data_len) {fprintf(stderr, "Error reading data file\n");goto cleanup;}// 3. 读取签名文件sig_fp = fopen(sig_file, "rb");if (!sig_fp) {fprintf(stderr, "Error opening signature file\n");goto cleanup;}fseek(sig_fp, 0, SEEK_END);sig_len = ftell(sig_fp);fseek(sig_fp, 0, SEEK_SET);sig = (unsigned char*)malloc(sig_len);if (!sig) {fprintf(stderr, "Memory allocation error\n");goto cleanup;}if (fread(sig, 1, sig_len, sig_fp) != sig_len) {fprintf(stderr, "Error reading signature file\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, EVP_sha256(), NULL, pubkey) != 1) {fprintf(stderr, "Error initializing verification\n");goto cleanup;}// 5. 更新验证数据if (EVP_DigestVerifyUpdate(mdctx, data, data_len) != 1) {fprintf(stderr, "Error updating verification data\n");goto cleanup;}// 6. 完成验证result = EVP_DigestVerifyFinal(mdctx, sig, sig_len);if (result == 1) {printf("Signature verification successful\n");} else if (result == 0) {printf("Signature verification failed\n");} else {fprintf(stderr, "Error during verification\n");result = -1;}cleanup:// 7. 清理资源if (mdctx) EVP_MD_CTX_free(mdctx);if (pubkey) EVP_PKEY_free(pubkey);if (pubkey_fp) fclose(pubkey_fp);if (data_fp) fclose(data_fp);if (sig_fp) fclose(sig_fp);if (data) free(data);if (sig) free(sig);return result;
}int main(int argc, char* argv[]) {if (argc != 4) {printf("Usage: %s <public_key.pem> <data_file> <signature_file>\n", argv[0]);return 1;}return verify_file_signature(argv[1], argv[2], argv[3]) != 1;
}

代码步骤详解

  1. 加载公钥 :利用 PEM_read_PUBKEY 从 PEM 格式的公钥文件中读取公钥。
  2. 读取数据文件 :将待验证的文件内容完整读入内存缓冲区。
  3. 读取签名文件 :把签名数据读入内存缓冲区。
  4. 初始化验证上下文 :创建 EVP_MD_CTX 结构,并借助 EVP_DigestVerifyInit 进行初始化。
  5. 更新验证数据 :调用 EVP_DigestVerifyUpdate 将文件数据输入验证过程。
  6. 完成验证 :通过 EVP_DigestVerifyFinal 进行最终验证。
  7. 资源清理 :释放所有分配的内存和文件资源。

字符串验签 C 语言实现

字符串验签与文件验签主要区别在于数据来源不同,以下是处理内存中字符串数据的验签实现:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/err.h>// 验证字符串签名
int verify_string_signature(const char* pubkey_file, const char* message,const unsigned char* sig,size_t sig_len) {EVP_MD_CTX* mdctx = NULL;EVP_PKEY* pubkey = NULL;FILE* pubkey_fp = NULL;int result = -1;// 1. 读取公钥文件pubkey_fp = fopen(pubkey_file, "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;}// 2. 初始化验证上下文mdctx = EVP_MD_CTX_new();if (!mdctx) {fprintf(stderr, "Error creating EVP_MD_CTX\n");goto cleanup;}if (EVP_DigestVerifyInit(mdctx, NULL, EVP_sha256(), NULL, pubkey) != 1) {fprintf(stderr, "Error initializing verification\n");goto cleanup;}// 3. 更新验证数据(直接使用字符串)if (EVP_DigestVerifyUpdate(mdctx, message, strlen(message)) != 1) {fprintf(stderr, "Error updating verification data\n");goto cleanup;}// 4. 完成验证result = EVP_DigestVerifyFinal(mdctx, sig, sig_len);if (result == 1) {printf("Signature verification successful\n");} else if (result == 0) {printf("Signature verification failed\n");} else {fprintf(stderr, "Error during verification\n");result = -1;}cleanup:// 5. 清理资源if (mdctx) EVP_MD_CTX_free(mdctx);if (pubkey) EVP_PKEY_free(pubkey);if (pubkey_fp) fclose(pubkey_fp);return result;
}// Base64解码函数
int base64_decode(const char* encoded, unsigned char** decoded, size_t* decoded_len) {BIO* bio = NULL;BIO* b64 = NULL;int len = strlen(encoded);*decoded = (unsigned char*)malloc(len);if (!*decoded) {return -1;}b64 = BIO_new(BIO_f_base64());bio = BIO_new_mem_buf((void*)encoded, len);bio = BIO_push(b64, bio);BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL);*decoded_len = BIO_read(bio, *decoded, len);BIO_free_all(bio);return (*decoded_len > 0) ? 0 : -1;
}int main() {const char* pubkey_file = "public_key.pem";const char* message = "This is a test message to verify";const char* base64_sig = "MEUCIQD..."; // Base64编码的签名unsigned char* sig = NULL;size_t sig_len = 0;// 解码Base64签名if (base64_decode(base64_sig, &sig, &sig_len) != 0) {fprintf(stderr, "Error decoding base64 signature\n");return 1;}int ret = verify_string_signature(pubkey_file, message, sig, sig_len);free(sig);return ret != 1;
}

代码步骤详解

  1. 加载公钥 :与文件验签相同,运用 PEM_read_PUBKEY 加载公钥。
  2. 初始化验证上下文 :创建并初始化 EVP_MD_CTX 结构。
  3. 更新验证数据 :直接使用字符串数据调用 EVP_DigestVerifyUpdate
  4. Base64 解码 :提供辅助函数处理 Base64 编码的签名数据。
  5. 完成验证 :调用 EVP_DigestVerifyFinal 进行最终验证。

错误处理与调试

OpenSSL 提供详细的错误报告机制,可在验证失败时获取更多信息:

void print_openssl_error() {unsigned long err;const char* file = NULL;const char* data = NULL;int line, flags;while ((err = ERR_get_error_line_data(&file, &line, &data, &flags)) != 0) {char buf[256];ERR_error_string_n(err, buf, sizeof(buf));fprintf(stderr, "OpenSSL error: %s\n", buf);fprintf(stderr, "  at %s:%d\n", file, line);if (data && (flags & ERR_TXT_STRING)) {fprintf(stderr, "  data: %s\n", data);}}
}// 在验证函数中使用示例
if (EVP_DigestVerifyInit(mdctx, NULL, EVP_sha256(), NULL, pubkey) != 1) {fprintf(stderr, "Error initializing verification\n");print_openssl_error();goto cleanup;
}

常见错误处理

  • 初始化失败 :检查公钥格式是否正确,算法是否支持。
  • 更新数据失败 :检查数据缓冲区是否有效。
  • 验证失败 :检查签名数据是否正确,公钥是否匹配私钥。
  • 内存错误 :确保所有分配的资源都被正确释放。

文件验签与字符串验签对比

特性文件验签字符串验签
数据来源文件系统内存中的字符串
数据加载需要文件 I/O 操作直接使用内存数据
大数据处理可分块读取处理通常一次性处理
适用场景验证文件完整性验证 API 消息、配置数据等
性能考虑受磁盘 I/O 影响纯内存操作,速度更快

总结与最佳实践

  • 算法选择 :推荐使用 SHA - 256 或更强的哈希算法,避免 MD5 或 SHA1 等不安全算法。
  • 密钥管理 :妥善保管私钥,公钥可通过证书链验证真实性。
  • 错误处理 :始终检查 OpenSSL 函数返回值,实现全面错误处理。
  • 资源管理 :确保分配资源正确释放,防止内存泄漏。
  • 性能优化 :大文件可分块调用 EVP_DigestVerifyUpdate,避免一次性加载。
http://www.xdnf.cn/news/9071.html

相关文章:

  • 编程中优秀大模型推荐:特点与应用场景深度分析
  • Pycharm的简单介绍
  • 002大模型-提示词工程,少样本提示,角色扮演,思维链
  • 基于python+Django+Mysql的校园二手交易市场
  • 在 Windows 上使用 WSL 安装 Ansible详细步骤
  • x86 与 ARM 汇编深度对比:聚焦 x86 汇编的独特魅力
  • 利用python爬虫获取淘宝天猫商品评论封装API实战演示
  • 【生物信息学】k-mer的基本概念及应用
  • python打卡day37@浙大疏锦行
  • tc3975开发板上有ft2232这块的电路,我想知道这个开发板有哪些升级方式,重点关注是怎样通过ft2232实现的烧录升级的
  • 单片机上按键功能通常都是用什么方法写?
  • 《DeepSeek行业应用全景指南(视频微课版)》:从入门到精通的AI落地实践手册
  • 2025年文件加密软件——数据保险箱,为您的文件上锁
  • DIY 自己的 MCP 服务-核心概念、基本协议、一个例子(Python)
  • 在 Windows 系统下使用 Qt 配置 OpenCV 和 MySql
  • 游戏引擎学习第310天:利用网格划分完成排序加速优化
  • 小土堆pytorch--优化器
  • Spring AI系列之Spring AI 集成 ChromaDB 向量数据库
  • 【C++进阶篇】初识哈希
  • FFmpeg 4.3 H265 二十二.4,使用计算机摄像头,通过VCL软件, 模拟 监控摄像头 的 RTSP 流
  • @MySQL升级8.0.42(Ubuntu 22.04)-SOP
  • Flink核心概念小结
  • Spring AI 系列之一个很棒的 Spring AI 功能——Advisors
  • WeakAuras Lua Script [ICC BOSS 11 - Sindragosa]
  • 博图软件块的概述-块的结构详解
  • VR 展厅开启一场穿越时空的邂逅​
  • Java常用API
  • React从基础入门到高级实战:React 核心技术 - React 状态管理:Context 与 Redux
  • uniapp-商城-71-shop(4-商品列表,详情页中添加商品到购物车的处理)
  • 机器人工具中心点标定