计算文件MD5值,判断唯一性(win32-API)

举报
DS小龙哥 发表于 2024/08/26 14:17:01 2024/08/26
【摘要】 在Windows环境下,尤其是在Win32 API中,计算文件的MD5值是判断文件唯一性的一种常见做法。这是因为MD5值可以视为文件的一个“指纹”,只要文件内容有任何变化,其MD5值就会改变,从而可以迅速识别出文件是否被修改过。这一特性在软件分发、数据备份、数字签名以及密码存储等场景中尤为重要。例如,软件开发者在分发软件包时,会提供相应的MD5值,用户下载后可以计算下载文件的MD5并与官方提供的值

一、前言

在当今数字化世界中,数据的完整性和安全性变得日益重要。MD5(Message-Digest Algorithm 5)作为一种被广泛接受的密码散列函数,它能够生成一个固定长度的128位(16字节)散列值,也称为摘要,用于确保信息传输的完整一致。MD5算法的特点在于它能够将任意长度的信息转换为一个固定长度的输出,这个过程是单向的,即很难从散列值反推原始信息,同时即使是微小的变化也会导致完全不同的散列值,这使得MD5成为检测文件篡改和数据一致性验证的有力工具。

在Windows环境下,尤其是在Win32 API中,计算文件的MD5值是判断文件唯一性的一种常见做法。这是因为MD5值可以视为文件的一个“指纹”,只要文件内容有任何变化,其MD5值就会改变,从而可以迅速识别出文件是否被修改过。这一特性在软件分发、数据备份、数字签名以及密码存储等场景中尤为重要。例如,软件开发者在分发软件包时,会提供相应的MD5值,用户下载后可以计算下载文件的MD5并与官方提供的值进行对比,以确认下载文件未被篡改。

image-20240715162615131

实现文件MD5值的计算在C语言中可以通过调用Windows的CryptoAPI来完成。CryptoAPI提供了加密和散列功能,其中包括MD5算法的实现。

下面是一个基于Win32 API的C语言示例,展示如何读取文件并计算其MD5值:

#include <windows.h>
#include <stdio.h>
#include <wincrypt.h>
​
void print_hex(const BYTE *data, DWORD len) {
    for (DWORD i = 0; i < len; ++i) {
        printf("%02X", data[i]);
    }
}
​
int main() {
    HANDLE hFile;
    HCRYPTPROV hCryptProv;
    HCRYPTHASH hHash;
    DWORD dwDataLen;
    BYTE *pbData;
    DWORD dwHashSize = 0;
    BYTE *pbHash;
​
    // 打开文件
    hFile = CreateFile("testfile.txt", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
        printf("CreateFile failed (%d)\n", GetLastError());
        return 1;
    }
​
    // 获取文件大小
    dwDataLen = GetFileSize(hFile, NULL);
    pbData = (BYTE *)malloc(dwDataLen);
​
    // 读取文件
    ReadFile(hFile, pbData, dwDataLen, &dwDataLen, NULL);
​
    // 初始化CryptoAPI
    if (!CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
        printf("CryptAcquireContext failed (%d)\n", GetLastError());
        return 1;
    }
​
    // 创建散列对象
    if (!CryptCreateHash(hCryptProv, CALG_MD5, 0, 0, &hHash)) {
        printf("CryptCreateHash failed (%d)\n", GetLastError());
        return 1;
    }
​
    // 计算散列值
    if (!CryptHashData(hHash, pbData, dwDataLen, 0)) {
        printf("CryptHashData failed (%d)\n", GetLastError());
        return 1;
    }
​
    // 获取散列值大小
    CryptGetHashParam(hHash, HP_HASHSIZE, (BYTE *)&dwHashSize, 0, 0);
    pbHash = (BYTE *)malloc(dwHashSize);
​
    // 获取散列值
    CryptGetHashParam(hHash, HP_HASHVAL, pbHash, &dwHashSize, 0);
​
    // 输出MD5值
    print_hex(pbHash, dwHashSize);
    printf("\n");
​
    // 清理
    CryptDestroyHash(hHash);
    CryptReleaseContext(hCryptProv, 0);
    free(pbData);
    free(pbHash);
    CloseHandle(hFile);
​
    return 0;
}

此程序先打开并读取指定文件的内容,然后利用CryptoAPI的函数初始化一个安全上下文,创建一个MD5散列对象,并将文件数据传递给CryptHashData函数来计算散列值。最后,CryptGetHashParam函数用于获取散列值,然后将其输出。


二、代码实操

2.1 函数接口介绍

在Windows平台下,使用Win32 API计算MD5值主要依赖于CryptoAPI(Cryptographic Application Programming Interface)。CryptoAPI是Windows操作系统提供的一组用于加密和散列操作的函数集,其中包括计算MD5散列值的能力。

以下是使用Win32 API计算MD5值涉及到的主要函数接口:

(1)CryptAcquireContext()

  • 功能:获取一个加密服务提供者(CSP)的句柄。CSP是用于执行加密操作的软件模块。

  • 参数

    • PHCRYPTPROV phCryptProv:返回的CSP句柄。

    • LPCSTR pszContainer:容器名称,通常为NULL表示使用默认容器。

    • LPCSTR pszProvider:提供者的名称,如MS_ENH_RSA_AES_PROVPROV_RSA_FULL

    • DWORD dwProvType:提供者类型。

    • DWORD dwFlags:标志,如CRYPT_VERIFYCONTEXT

(2)CryptCreateHash()

  • 功能:使用指定的算法创建一个散列对象。

  • 参数

    • HCRYPTPROV hCryptProv:从CryptAcquireContext()获得的CSP句柄。

    • ALG_ID Algid:算法ID,对于MD5是CALG_MD5

    • HCRYPTHASH hHash:返回的散列对象句柄。

    • DWORD dwFlags:标志,通常为0

(3)CryptHashData()

  • 功能:向散列对象中添加数据。

  • 参数

    • HCRYPTHASH hHash:散列对象句柄。

    • const BYTE *pbData:指向要散列的数据的指针。

    • DWORD dwDataLen:数据的长度。

    • DWORD dwFlags:标志,通常为0

(4)CryptGetHashParam()

  • 功能:从散列对象中获取参数,如散列值。

  • 参数

    • HCRYPTHASH hHash:散列对象句柄。

    • HASHPARAM dwParam:请求的参数类型,对于散列值是HP_HASHVAL

    • BYTE *pbData:返回参数数据的缓冲区。

    • DWORD *pdwDataLen:缓冲区的长度。

    • DWORD dwFlags:标志,通常为0

(5)CryptDestroyHash()

  • 功能:销毁散列对象。

  • 参数

    • HCRYPTHASH hHash:要销毁的散列对象句柄。

(6)CryptReleaseContext()

  • 功能:释放CSP句柄。

  • 参数

    • HCRYPTPROV hCryptProv:要释放的CSP句柄。

    • DWORD dwFlags:保留供将来使用,通常为0

一个典型的使用流程如下:

  1. 调用CryptAcquireContext()获取CSP句柄。

  2. 使用CryptCreateHash()创建散列对象。

  3. 多次调用CryptHashData()将文件数据块传递给散列对象。

  4. 调用CryptGetHashParam()获取散列值。

  5. 使用CryptDestroyHash()销毁散列对象。

  6. 最后,调用CryptReleaseContext()释放CSP句柄。

这些函数通常会返回一个布尔值或错误代码,用于指示操作是否成功,因此在调用这些函数后,应检查返回值并适当处理错误。在处理文件数据时,通常需要将文件分成多个块进行处理,以防止内存不足或提高性能。


2.2 计算指定文件的MD5值

开发环境:在Windows下安装一个VS即可。我当前采用的版本是VS2020。

下面是一个使用C语言和Windows CryptoAPI计算文件MD5值的完整示例代码。

此代码将打开一个文件,读取其内容,并使用CryptoAPI计算MD5散列值,最后将结果以十六进制形式输出到控制台。

#include <windows.h>
#include <stdio.h>
#include <wincrypt.h>
​
void PrintBufferAsHex(const BYTE* buffer, DWORD length) {
    for (DWORD i = 0; i < length; i++) {
        printf("%02X", buffer[i]);
    }
}
​
int main(int argc, char* argv[]) {
    if (argc < 2) {
        printf("Usage: %s <filename>\n", argv[0]);
        return 1;
    }
​
    HANDLE hFile;
    HCRYPTPROV hCryptProv;
    HCRYPTHASH hHash;
    DWORD dwDataLen;
    BYTE* pbData;
    DWORD dwHashSize;
    BYTE* pbHash;
​
    // Open the file
    hFile = CreateFile(argv[1], GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
        printf("Failed to open file: %d\n", GetLastError());
        return 1;
    }
​
    // Get file size
    dwDataLen = GetFileSize(hFile, NULL);
    pbData = (BYTE*)malloc(dwDataLen);
    if (pbData == NULL) {
        printf("Memory allocation failed.\n");
        CloseHandle(hFile);
        return 1;
    }
​
    // Read file into buffer
    DWORD bytesRead;
    if (!ReadFile(hFile, pbData, dwDataLen, &bytesRead, NULL) || bytesRead != dwDataLen) {
        printf("ReadFile failed: %d\n", GetLastError());
        free(pbData);
        CloseHandle(hFile);
        return 1;
    }
​
    // Initialize CryptoAPI
    if (!CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
        printf("CryptAcquireContext failed: %d\n", GetLastError());
        free(pbData);
        CloseHandle(hFile);
        return 1;
    }
​
    // Create hash object
    if (!CryptCreateHash(hCryptProv, CALG_MD5, 0, 0, &hHash)) {
        printf("CryptCreateHash failed: %d\n", GetLastError());
        CryptReleaseContext(hCryptProv, 0);
        free(pbData);
        CloseHandle(hFile);
        return 1;
    }
​
    // Hash data
    if (!CryptHashData(hHash, pbData, dwDataLen, 0)) {
        printf("CryptHashData failed: %d\n", GetLastError());
        CryptDestroyHash(hHash);
        CryptReleaseContext(hCryptProv, 0);
        free(pbData);
        CloseHandle(hFile);
        return 1;
    }
​
    // Get hash size
    dwHashSize = sizeof(DWORD);
    if (!CryptGetHashParam(hHash, HP_HASHSIZE, (BYTE*)&dwHashSize, &dwHashSize, 0)) {
        printf("CryptGetHashParam failed: %d\n", GetLastError());
        CryptDestroyHash(hHash);
        CryptReleaseContext(hCryptProv, 0);
        free(pbData);
        CloseHandle(hFile);
        return 1;
    }
​
    // Allocate memory for hash result
    pbHash = (BYTE*)malloc(dwHashSize);
    if (pbHash == NULL) {
        printf("Memory allocation failed.\n");
        CryptDestroyHash(hHash);
        CryptReleaseContext(hCryptProv, 0);
        free(pbData);
        CloseHandle(hFile);
        return 1;
    }
​
    // Get hash value
    if (!CryptGetHashParam(hHash, HP_HASHVAL, pbHash, &dwHashSize, 0)) {
        printf("CryptGetHashParam failed: %d\n", GetLastError());
        free(pbHash);
        CryptDestroyHash(hHash);
        CryptReleaseContext(hCryptProv, 0);
        free(pbData);
        CloseHandle(hFile);
        return 1;
    }
​
    // Print hash value in hex format
    printf("MD5 of '%s': ", argv[1]);
    PrintBufferAsHex(pbHash, dwHashSize);
    printf("\n");
​
    // Clean up
    free(pbHash);
    free(pbData);
    CryptDestroyHash(hHash);
    CryptReleaseContext(hCryptProv, 0);
    CloseHandle(hFile);
​
    return 0;
}

在运行此代码之前,这个程序假设文件可以一次性读入内存;对于大文件,需要分块读取并多次调用CryptHashData()

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。