Message Digest 是一个将任意长度的数据“压缩”成一个固定长度的、独一无二的“指纹”字符串的算法,这个指纹具有以下特性:

(图片来源网络,侵删)
- 不可逆性:无法从摘要反推出原始数据。
- 抗碰撞性:找到两个不同的输入数据,使其摘要相同,在计算上是不可行的。
- 确定性:相同的输入数据,无论计算多少次,得到的摘要总是相同的。
- 雪崩效应:输入数据的微小改变(修改一个比特位),会导致摘要发生巨大的、不可预测的变化。
为什么在 C 语言中需要 Message Digest?
C 语言本身不提供内置的哈希函数库,因为它更接近底层,我们需要借助第三方库来实现,Message Digest 在 C 语言中常用于:
- 密码存储:存储用户密码的哈希值,而不是明文密码,即使数据库泄露,攻击者也无法轻易获取原始密码。
- 数据完整性校验:下载文件后,计算其哈希值并与官方公布的哈希值对比,以确保文件在传输过程中没有被篡改或损坏。
- 数字签名:在非对称加密中,对消息生成摘要,然后用私钥加密摘要,形成签名,接收方用公钥解密签名并验证摘要,确保消息的来源和完整性。
- 哈希表:实现高效的键值对存储,通过哈希函数将键映射到数组的索引位置。
实现方式:使用 OpenSSL 库
在 C 语言生态中,OpenSSL 是最著名、最广泛使用的加密库,它提供了全套的哈希算法实现。
第 1 步:安装 OpenSSL
在使用之前,你需要确保你的系统上安装了 OpenSSL 开发库。
- 在 Debian/Ubuntu 上:
sudo apt-get update sudo apt-get install libssl-dev
- 在 CentOS/RHEL/Fedora 上:
sudo yum install openssl-devel # 或者对于较新版本 sudo dnf install openssl-devel
- 在 macOS 上 (使用 Homebrew):
brew install openssl
第 2 步:编写 C 代码
OpenSSL 提供了两种主要的 API 风格来使用哈希函数:

(图片来源网络,侵删)
- 高级 EVP (Enveloped) API:推荐使用,它是 OpenSSL 提供的现代、统一、更安全的接口,可以轻松地在不同的哈希算法之间切换。
- 低级 MD/SHA API:旧式的、针对特定算法的 API,虽然更直接,但不够灵活,且 OpenSSL 官方不推荐在新代码中使用。
我们将重点介绍 EVP API,因为它更实用、更安全。
示例 1:使用 EVP API 计算字符串的 SHA-256 摘要
这是一个完整的、可编译运行的示例。
#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>
#include <openssl/sha.h> // 包含SHA256常量,虽然EVP API不直接需要,但方便
// 辅助函数:将字节数组转换为十六进制字符串
void bytes_to_hex(const unsigned char *bytes, size_t len, char *hex_str) {
for (size_t i = 0; i < len; i++) {
sprintf(hex_str + (i * 2), "%02x", bytes[i]);
}
hex_str[len * 2] = '\0';
}
int main() {
// 1. 准备要哈希的数据
const char *data = "Hello, Message Digest!";
// 2. 创建并初始化一个EVP_MD_CTX (Message Digest Context) 结构体
// 这个结构体保存了哈希计算过程中的所有状态
EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
if (mdctx == NULL) {
fprintf(stderr, "Error: EVP_MD_CTX_new failed\n");
return 1;
}
// 3. 初始化哈希操作
// EVP_sha256() 返回一个指向 SHA-256 算法描述符的指针
if (1 != EVP_DigestInit_ex(mdctx, EVP_sha256(), NULL)) {
fprintf(stderr, "Error: EVP_DigestInit_ex failed\n");
EVP_MD_CTX_free(mdctx);
return 1;
}
// 4. 更新哈希计算
// 可以被多次调用,以处理流式数据或大数据块
if (1 != EVP_DigestUpdate(mdctx, data, strlen(data))) {
fprintf(stderr, "Error: EVP_DigestUpdate failed\n");
EVP_MD_CTX_free(mdctx);
return 1;
}
// 5. 最终化哈希计算并获取结果
// 计算结果将被存放在一个 unsigned char 数组中
// SHA256_DIGEST_LENGTH 是 32
unsigned char hash[EVP_MAX_MD_SIZE];
unsigned int hash_len;
if (1 != EVP_DigestFinal_ex(mdctx, hash, &hash_len)) {
fprintf(stderr, "Error: EVP_DigestFinal_ex failed\n");
EVP_MD_CTX_free(mdctx);
return 1;
}
// 6. 清理资源
EVP_MD_CTX_free(mdctx);
// 7. 输出结果
// 哈希结果是二进制数据,通常我们将其表示为十六进制字符串
char hex_hash[65]; // 32 bytes * 2 chars/byte + null terminator
bytes_to_hex(hash, hash_len, hex_hash);
printf("Original Data: %s\n", data);
printf("SHA-256 Digest: %s\n", hex_hash);
printf("Digest Length: %u bytes\n", hash_len);
return 0;
}
如何编译和运行
将上述代码保存为 md_example.c,然后使用以下命令进行编译:
# -I 指定头文件路径,-l 指定要链接的库 gcc md_example.c -o md_example -I /usr/include/openssl -lssl -lcrypto
-I /usr/include/openssl:告诉编译器去哪里找 OpenSSL 的头文件(#include <openssl/...>),在大多数系统上,这个路径是默认的,可以省略。-lssl -lcrypto:非常重要,链接两个核心的 OpenSSL 库:libssl.so:包含高级协议(如 TLS/SSL)的实现。libcrypto.so:包含底层的加密算法,包括哈希、对称/非对称加密等。
运行编译后的程序:

(图片来源网络,侵删)
./md_example
预期输出:
Original Data: Hello, Message Digest!
SHA-256 Digest: 1e5e6fd9a9835f5d0b9c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7
Digest Length: 32 bytes
(注意:每次运行,只要输入数据不变,输出摘要就完全一致)
示例 2:计算文件的 SHA-256 摘要
这个例子演示了如何处理文件数据,它分块读取文件,非常适合大文件。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/evp.h>
#define CHUNK_SIZE 4096 // 4KB 的块大小
void bytes_to_hex(const unsigned char *bytes, size_t len, char *hex_str) {
for (size_t i = 0; i < len; i++) {
sprintf(hex_str + (i * 2), "%02x", bytes[i]);
}
hex_str[len * 2] = '\0';
}
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "Usage: %s <filename>\n", argv[0]);
return 1;
}
FILE *file = fopen(argv[1], "rb");
if (file == NULL) {
perror("Error opening file");
return 1;
}
EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
if (mdctx == NULL) {
fprintf(stderr, "Error: EVP_MD_CTX_new failed\n");
fclose(file);
return 1;
}
if (1 != EVP_DigestInit_ex(mdctx, EVP_sha256(), NULL)) {
fprintf(stderr, "Error: EVP_DigestInit_ex failed\n");
EVP_MD_CTX_free(mdctx);
fclose(file);
return 1;
}
unsigned char buffer[CHUNK_SIZE];
size_t bytes_read;
while ((bytes_read = fread(buffer, 1, CHUNK_SIZE, file)) > 0) {
if (1 != EVP_DigestUpdate(mdctx, buffer, bytes_read)) {
fprintf(stderr, "Error: EVP_DigestUpdate failed\n");
EVP_MD_CTX_free(mdctx);
fclose(file);
return 1;
}
}
// 检查文件读取是否出错(非EOF导致的错误)
if (ferror(file)) {
perror("Error reading file");
EVP_MD_CTX_free(mdctx);
fclose(file);
return 1;
}
unsigned char hash[EVP_MAX_MD_SIZE];
unsigned int hash_len;
if (1 != EVP_DigestFinal_ex(mdctx, hash, &hash_len)) {
fprintf(stderr, "Error: EVP_DigestFinal_ex failed\n");
EVP_MD_CTX_free(mdctx);
fclose(file);
return 1;
}
EVP_MD_CTX_free(mdctx);
fclose(file);
char hex_hash[65];
bytes_to_hex(hash, hash_len, hex_hash);
printf("File: %s\n", argv[1]);
printf("SHA-256 Digest: %s\n", hex_hash);
return 0;
}
编译和运行:
gcc file_hash.c -o file_hash -lssl -lcrypto ./file_hash your_file.txt
常见的 Message Digest 算法
| 算法 | 摘要长度 (位) | 摘要长度 (字节) | OpenSSL EVP 函数 | 安全性 |
|---|---|---|---|---|
| MD5 | 128 | 16 | EVP_md5() |
已破解,不安全,仅用于兼容旧系统 |
| SHA-1 | 160 | 20 | EVP_sha1() |
已破解,不安全,不应再使用 |
| SHA-224 | 224 | 28 | EVP_sha224() |
安全,但不如 SHA-256 流行 |
| SHA-256 | 256 | 32 | EVP_sha256() |
当前推荐,安全、高效 |
| SHA-384 | 384 | 48 | EVP_sha384() |
安全,用于需要更高安全性的场景 |
| SHA-512 | 512 | 64 | EVP_sha512() |
安全,同上 |
在 C 语言中进行 Message Digest 操作,OpenSSL 的 EVP API 是标准且推荐的方法。
- 安装 OpenSSL:确保开发库已安装。
- 包含头文件:
#include <openssl/evp.h>。 - 创建上下文:
EVP_MD_CTX *ctx = EVP_MD_CTX_new();。 - 初始化:
EVP_DigestInit_ex(ctx, EVP_YourHashAlgorithm(), NULL);。 - 更新数据:
EVP_DigestUpdate(ctx, data, len);(可多次调用)。 - 获取结果:
EVP_DigestFinal_ex(ctx, hash_buffer, &len);。 - 释放资源:
EVP_MD_CTX_free(ctx);。 - 编译链接:使用
-lssl -lcrypto选项。
对于新的项目,优先选择 SHA-256 或更高级的 SHA-2/SHA-3 算法,避免使用 MD5 和 SHA-1。
