OpenSSL3.5.2实现SM3数据摘要生成

OpenSSL库中使用EVP(Enhanced Verification Package增强验证包)进行SM3相关的摘要生成。

其逻辑为:可分为 初始化上下文→配置算法→分块处理数据→生成最终哈希→释放资源 五个步骤。

完整代码如下:

cpp 复制代码
#include <QCoreApplication>
#include <iostream>
#include <openssl/opensslv.h>
#include <openssl/crypto.h>
#include <openssl/evp.h>
#include <openssl/err.h>
using namespace std;


// 计算 SM3 哈希值
int sm3_hash(const unsigned char *data, size_t data_len, unsigned char *hash, unsigned int *hash_len) {
    // 创建 EVP 上下文
    EVP_MD_CTX *ctx = EVP_MD_CTX_new();
    if (ctx == NULL) {
        fprintf(stderr, "无法创建 EVP 上下文\n");
        return 0;
    }

    // 初始化 SM3 哈希计算
    if (EVP_DigestInit_ex(ctx, EVP_sm3(), NULL) != 1) {
        fprintf(stderr, "SM3 初始化失败\n");
        ERR_print_errors_fp(stderr);
        EVP_MD_CTX_free(ctx);
        return 0;
    }

    // 更新哈希计算(可以分多次调用更新不同的数据块)
    if (EVP_DigestUpdate(ctx, data, data_len) != 1) {
        fprintf(stderr, "SM3 数据更新失败\n");
        ERR_print_errors_fp(stderr);
        EVP_MD_CTX_free(ctx);
        return 0;
    }

    // 完成哈希计算并获取结果
    if (EVP_DigestFinal_ex(ctx, hash, hash_len) != 1) {
        fprintf(stderr, "SM3 计算完成失败\n");
        ERR_print_errors_fp(stderr);
        EVP_MD_CTX_free(ctx);
        return 0;
    }

    // 释放上下文
    EVP_MD_CTX_free(ctx);
    return 1;
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    // 待计算哈希的数据
     const char *input = "Hello, SM3!";
     unsigned char hash[EVP_MAX_MD_SIZE];
     unsigned int hash_len;

     // 初始化 OpenSSL 错误信息
     ERR_load_crypto_strings();

     // 计算 SM3 哈希
     if (sm3_hash((const unsigned char *)input, strlen(input), hash, &hash_len)) {
         printf("input data: %s\n", input);
         printf("SM3 hash: ");
         for (unsigned int i = 0; i < hash_len; i++) {
             printf("%02x", hash[i]);
         }
         printf("\nhash length: %u byte\n", hash_len);
     }

     // 清理错误信息
     ERR_free_strings();

    return a.exec();
}

这里有一个需要注意的地方:

EVP_DigestUpdate 支持分多次调用,核心原因是为了处理大数据或流式数据:

  • 对于大文件(如几个 GB 的视频)或网络流(如实时接收的数据),无法一次性将所有数据加载到内存中(会导致内存溢出)。
  • 分多次调用时,每次只需传入一部分数据(例如每次 4KB),上下文 ctx 会保存中间状态,最终合并计算结果,与一次性处理的结果完全一致。
  • 这种设计既节省内存,又支持流式处理(边接收数据边计算哈希),是处理大数据场景的必需特性。

那么问题来了

为什么需要分块更新哈希计算?

分块调用 EVP_DigestUpdate 与 "哈希长度是否固定" 无关,核心原因是哈希算法的底层设计原理和实际工程需求:

  1. 哈希算法的 "迭代式计算" 本质

SM3(以及几乎所有现代哈希算法,如 SHA256、MD5 等)的计算过程是迭代式的,而非 "一次性计算"。其底层逻辑是:

  • 将输入数据按固定大小(SM3 中是 512 位,即 64 字节)分成若干块(最后一块不足时会进行补位处理)。
  • 从初始状态开始,每处理一块数据,就用该块数据更新内部状态(一个 256 位的中间变量)。
  • 所有块处理完成后,将最终的内部状态转换为 32 字节的哈希值。

例如:

  • 输入 "abc" 时,数据量小于 512 位,补位后形成一个块,一次处理完成。
  • 输入 1GB 文件时,数据会被分成约 200 万个 512 位块,必须分块处理,每块更新一次内部状态,最终合并为同一个 32 字节哈希值。

EVP_DigestUpdate 的作用就是向算法传递这些分块数据,让算法逐步更新内部状态,它是哈希算法迭代特性的直接体现。

  1. 工程上的 "内存限制" 需求

即使哈希算法支持一次性处理数据,实际工程中也必须分块:

  • 对于大文件(如 10GB 的视频),无法将全部数据一次性加载到内存(会导致内存溢出)。
  • 分块处理时,只需在内存中保留一个小块缓冲区(如 4KB),每次读取一块数据并传递给 EVP_DigestUpdate,内存占用始终保持在极低水平。

这种方式既符合哈希算法的迭代逻辑,又能高效处理任意大小的输入(从几字节到几十 GB)。

分块计算的例子。

cpp 复制代码
#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>
#include <openssl/err.h>

// 计算文件的 SM3 哈希值(分块处理大文件)
int sm3_file_hash(const char *file_path, unsigned char *hash, unsigned int *hash_len) {
    // 打开文件(二进制模式,避免文本模式下的换行符转换)
    FILE *file = fopen(file_path, "rb");
    if (!file) {
        fprintf(stderr, "无法打开文件: %s\n", file_path);
        return 0;
    }

    // 创建 EVP 上下文
    EVP_MD_CTX *ctx = EVP_MD_CTX_new();
    if (!ctx) {
        fprintf(stderr, "无法创建 EVP 上下文\n");
        fclose(file);
        return 0;
    }

    // 初始化 SM3 算法
    if (EVP_DigestInit_ex(ctx, EVP_sm3(), NULL) != 1) {
        fprintf(stderr, "SM3 初始化失败\n");
        ERR_print_errors_fp(stderr);
        EVP_MD_CTX_free(ctx);
        fclose(file);
        return 0;
    }

    // 分块读取文件并更新哈希(每次读取 4KB 块)
    unsigned char buffer[4096];  // 缓冲区大小,可根据需求调整(如 8192、16384 等)
    size_t bytes_read;
    while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0) {
        // 每次读取一块数据,更新哈希计算
        if (EVP_DigestUpdate(ctx, buffer, bytes_read) != 1) {
            fprintf(stderr, "哈希更新失败(文件读取到 %zu 字节时)\n", bytes_read);
            ERR_print_errors_fp(stderr);
            EVP_MD_CTX_free(ctx);
            fclose(file);
            return 0;
        }
    }

    // 检查文件读取是否出错
    if (ferror(file)) {
        fprintf(stderr, "文件读取错误\n");
        EVP_MD_CTX_free(ctx);
        fclose(file);
        return 0;
    }

    // 完成哈希计算并获取结果
    if (EVP_DigestFinal_ex(ctx, hash, hash_len) != 1) {
        fprintf(stderr, "SM3 计算完成失败\n");
        ERR_print_errors_fp(stderr);
        EVP_MD_CTX_free(ctx);
        fclose(file);
        return 0;
    }

    // 释放资源
    EVP_MD_CTX_free(ctx);
    fclose(file);
    return 1;
}

int main(int argc, char *argv[]) {

    const char *file_path = "D:/Neo4j/neo4j-community-3.5.5-windows.zip";
    unsigned char hash[EVP_MAX_MD_SIZE];
    unsigned int hash_len;

    // 初始化 OpenSSL 错误信息
    ERR_load_crypto_strings();

    // 计算文件的 SM3 哈希
    if (sm3_file_hash(file_path, hash, &hash_len)) {
        printf("文件: %s\n", file_path);
        printf("SM3 哈希值: ");
        for (unsigned int i = 0; i < hash_len; i++) {
            printf("%02x", hash[i]);
        }
        printf("\n哈希长度: %u 字节\n", hash_len);
    }

    // 清理错误信息
    ERR_free_strings();
    return 0;
}

运行截图如下:

相关推荐
Excuse_lighttime4 小时前
排序数组(快速排序算法)
java·数据结构·算法·leetcode·eclipse·排序算法
潘小安4 小时前
『译』迄今为止最强的 RAG 技术?Anthropic 的上下文检索与混合搜索
算法·llm·claude
kessy14 小时前
安全与续航兼备的“国密芯”——LKT6810U
算法
leo__5204 小时前
基于经验模态分解的去趋势波动分析(EMD-DFA)方法
人工智能·算法·机器学习
lzptouch5 小时前
AdaBoost(Adaptive Boosting)算法
算法·集成学习·boosting
南方的狮子先生5 小时前
【数据结构】(C++数据结构)查找算法与排序算法详解
数据结构·c++·学习·算法·排序算法·1024程序员节
前进的李工5 小时前
LeetCode hot100:560 和为k的子数组:快速统计法
python·算法·leetcode·前缀和·哈希表
在等晚安么6 小时前
力扣面试经典150题打卡
java·数据结构·算法·leetcode·面试·贪心算法
AndrewHZ6 小时前
【图像处理基石】图像滤镜的算法原理:从基础到进阶的技术解析
图像处理·python·opencv·算法·计算机视觉·滤镜·cv