如何在C语言中计算大文件的MD5哈希值?

21

我正在使用OpenSSL库编写C代码。

如何使用md5计算大文件的哈希值?

据我所知,我需要将整个文件作为char数组加载到RAM中,然后调用哈希函数。但是如果文件大小约为4GB,这听起来像一个不好的主意。

已解决:感谢askovpen,我找到了我的错误。我已经使用了

while ((bytes = fread (data, 1, 1024, inFile)) != 0)
    MD5_Update (&mdContext, data, 1024);

while ((bytes = fread (data, 1, 1024, inFile)) != 0)
    MD5_Update (&mdContext, data, bytes);

我很好奇它的必要性是什么?如果它不是线程安全的话,那肯定是个坏主意,因为它可能会阻塞程序很长一段时间并且让用户感到恼怒。 - crockpotveggies
5
加密文件并不等同于使用像MD5这样的哈希函数进行哈希处理。您真正想要的是哈希吗,还是想加密文件? - Oleksi
7
MD5是基于流的。您不需要一次性将整个4GB文件加载到内存中,而是需要分块读取。 - Charles Salvia
抱歉,我当然是指哈希文件,而不是加密。 - user1256821
4个回答

46

例子

gcc -g -Wall -o file file.c -lssl -lcrypto

这是一个使用gcc编译器的命令,用于编译名为file.c的源代码文件,并将其链接到libssl和libcrypto库。该命令中的选项-g将生成调试信息,-Wall启用所有警告,-o指定输出文件的名称为file。
#include <stdio.h>
#include <openssl/md5.h>

int main()
{
    unsigned char c[MD5_DIGEST_LENGTH];
    char *filename="file.c";
    int i;
    FILE *inFile = fopen (filename, "rb");
    MD5_CTX mdContext;
    int bytes;
    unsigned char data[1024];

    if (inFile == NULL) {
        printf ("%s can't be opened.\n", filename);
        return 0;
    }

    MD5_Init (&mdContext);
    while ((bytes = fread (data, 1, 1024, inFile)) != 0)
        MD5_Update (&mdContext, data, bytes);
    MD5_Final (c,&mdContext);
    for(i = 0; i < MD5_DIGEST_LENGTH; i++) printf("%02x", c[i]);
    printf (" %s\n", filename);
    fclose (inFile);
    return 0;
}

结果:

$ md5sum file.c
25a904b0e512ee546b3f47574703d9fc  file.c
$ ./file
25a904b0e512ee546b3f47574703d9fc file.c

可能是这样的: unsigned char data[4096]; while ((bytes = fread (data, 1, 4096, inFile)) != 0) - user1031143

6
首先,MD5是一种哈希算法。它不会加密任何内容。
无论如何,您可以按任意大小的块读取文件。调用MD5_Init一次,然后使用从文件中读取的每个数据块调用MD5_Update。完成后,调用MD5_Final以获取结果。

我已经尝试过了。我将文件分割成16字节块,并将它们传递给MD5_Update,但哈希值是错误的。 - user1256821
1
因此,请修复代码中导致哈希值错误的错误(另外,16字节块会很慢。最好使用至少64KB块)。 - David Schwartz

4
您不必一次性将整个文件加载到内存中。您可以使用函数MD5_Init()、MD5_Update()和MD5_Final()以块的形式进行处理,以产生哈希值。如果您担心它变成“原子”操作,可能需要锁定该文件以防止在操作期间其他人更改它。

1
顶部答案是正确的,但没有提到一些事情:哈希值的价值将因使用的每个缓冲区大小而异。该值将在哈希之间保持一致,因此每次使用相同的缓冲区大小将产生相同的哈希值,但是如果将此哈希与稍后对相同数据的哈希进行比较,则必须为每个调用使用相同的缓冲区大小。
此外,如果要确保您的摘要代码正常运行,并在线上使用哈希网站比较您的哈希,似乎它们使用长度为1的缓冲区。这也带来了一个有趣的想法:使用长度为1的缓冲区对大文件进行哈希处理是完全可以接受的,只是需要更长时间(当然)。
因此,我的经验法则是,如果仅供内部使用,则可以相应地设置缓冲区长度以适应大文件,但是如果必须与其他系统兼容,则将缓冲区长度设置为1并处理时间后果。
int hashTargetFile(FILE* fp, unsigned char** md_value, int *md_len) {

    #define FILE_BUFFER_LENGTH 1

    EVP_MD_CTX *mdctx;
    const EVP_MD *md;
    int diglen; //digest length
    int arrlen = sizeof(char)*EVP_MAX_MD_SIZE + 1;
    int arrlen2 = sizeof(char)*FILE_BUFFER_LENGTH + 1;
    unsigned char *digest_value = (char*)malloc(arrlen);
    char *data = (char*)malloc(arrlen2);
    size_t bytes; //# of bytes read from file

    mdctx = EVP_MD_CTX_new();
    md = EVP_sha512();

    if (!mdctx) {
        fprintf(stderr, "Error while creating digest context.\n");
        return 0;
    }

    if (!EVP_DigestInit_ex(mdctx, md, NULL)) {
        fprintf(stderr, "Error while initializing digest context.\n");
        return 0;
    }

    while (bytes = fread(data, 1, FILE_BUFFER_LENGTH, fp) != 0) {
        if (!EVP_DigestUpdate(mdctx, data, bytes)) {
            fprintf(stderr, "Error while digesting file.\n");
            return 0;
        }
    }

    if (!EVP_DigestFinal_ex(mdctx, digest_value, &diglen)) {
        fprintf(stderr, "Error while finalizing digest.\n");
        return 0;
    }

    *md_value = digest_value;
    *md_len = diglen;

    EVP_MD_CTX_free(mdctx);

    return 1;
}

我使用不同的文件缓冲区大小(40k、4k、23和1)运行了askovpen的代码,并且每次都得到了相同的哈希值。有人对此了解更多吗? - labyrinth
1
对于后来者来说,这是因为fread只会读取到文件的末尾,而bytes的值是读取的字节数。所以,如果您有一个FILE_BUFFER_LENGTH为512且文件在第一次迭代时长度为513字节,则bytes = 512,在第二次迭代时bytes = 1。只要保持一致使用缓冲区大小就可以了,它的大小并不重要。 - Chris Rapier
1
如果您正在读取任意长度的文件(比如说,您想获取前N个字节的哈希值(如果您要检查文件1是否是文件2数据的子集,则非常有用),则需要使用缓冲区大小为1 使用2个while循环。第一个while循环以缓冲区长度块读取数据(当hit bytes_read+buffer_length < size时),然后第二个while循环以1字节增量读取数据,同时bytes_read < size。 - Chris Rapier

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接