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;
}

运行截图如下:

相关推荐
风筝在晴天搁浅几秒前
字节高频题 小于n的最大数
算法
LabVIEW开发3 分钟前
LabVIEW水力机组空蚀在线监测
算法·labview·labview知识·labview功能·labview程序
AI科技星8 分钟前
科幻艺术书本封面:《全域数学》第一部·数术本源 第三卷 代数原本(P95-141)完整五级目录【乖乖数学】
算法·机器学习·数学建模·数据挖掘·量子计算
风筝在晴天搁浅10 分钟前
LeetCode 92.反转链表Ⅱ
算法·leetcode·链表
王老师青少年编程24 分钟前
csp信奥赛C++高频考点专项训练之贪心算法 --【贪心与二分判定】:数列分段 Section II
c++·算法·贪心·csp·信奥赛·二分判定·数列分段 section ii
V搜xhliang02461 小时前
OpenClaw科研全场景用法:从文献到实验室的完整自动化方案
运维·开发语言·人工智能·python·算法·microsoft·自动化
汉克老师1 小时前
GESP2025年3月认证C++五级( 第三部分编程题(2、原根判断))
c++·算法·模运算·gesp5级·gesp五级·原根·分解质因数
数据皮皮侠1 小时前
上市公司创新韧性数据(2000-2024)|顶刊同款 EIR 指数
大数据·人工智能·算法·智慧城市·制造
WL_Aurora1 小时前
Python 算法基础篇之链表
python·算法·链表
科研前沿2 小时前
纯视觉无感解算 + 动态数字孪生:室内外无感定位技术全新升级
大数据·人工智能·算法·重构·空间计算