MD5 的分片校验

有一个需求:对下载的文件执行 MD5 运算。在 UE 中可以使用 FMD5Hash 来进行 MD5 计算,但是只能指定文件加载:

1
2
FMD5Hash FileHash = FMD5Hash::HashFile(*InFile);
FString HashValue = LexToString(FileHash);

但是当文件比较大的时候如果等文件下载完再执行校验耗时会很长,所以我想有没有办法边下边进行 MD5 的计算,因为知道 MD5 是基于摘要的,所以觉得边下边校验的方法应该可行。我查了一下相关的实现,找到 OpenSSL 中有相关的操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// openssl/md5.h
#ifndef HEADER_MD5_H
#define HEADER_MD5_H

#include <openssl/e_os2.h>
#include <stddef.h>

#ifdef __cplusplus
extern "C" {
#endif

#ifdef OPENSSL_NO_MD5
#error MD5 is disabled.
#endif

/*
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* ! MD5_LONG has to be at least 32 bits wide. If it's wider, then !
* ! MD5_LONG_LOG2 has to be defined along. !
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
*/

#if defined(__LP32__)
#define MD5_LONG unsigned long
#elif defined(OPENSSL_SYS_CRAY) || defined(__ILP64__)
#define MD5_LONG unsigned long
#define MD5_LONG_LOG2 3
/*
* _CRAY note. I could declare short, but I have no idea what impact
* does it have on performance on none-T3E machines. I could declare
* int, but at least on C90 sizeof(int) can be chosen at compile time.
* So I've chosen long...
* <[email protected]>
*/
#else
#define MD5_LONG unsigned int
#endif

#define MD5_CBLOCK 64
#define MD5_LBLOCK (MD5_CBLOCK/4)
#define MD5_DIGEST_LENGTH 16

typedef struct MD5state_st
{
MD5_LONG A,B,C,D;
MD5_LONG Nl,Nh;
MD5_LONG data[MD5_LBLOCK];
unsigned int num;
} MD5_CTX;

#ifdef OPENSSL_FIPS
int private_MD5_Init(MD5_CTX *c);
#endif
int MD5_Init(MD5_CTX *c);
int MD5_Update(MD5_CTX *c, const void *data, size_t len);
int MD5_Final(unsigned char *md, MD5_CTX *c);
unsigned char *MD5(const unsigned char *d, size_t n, unsigned char *md);
void MD5_Transform(MD5_CTX *c, const unsigned char *b);
#ifdef __cplusplus
}
#endif

#endif

其中提供的 MD5 计算可以分开操作的有三个函数:

1
2
3
int MD5_Init(MD5_CTX *c);
int MD5_Update(MD5_CTX *c, const void *data, size_t len);
int MD5_Final(unsigned char *md, MD5_CTX *c);

其中的 MD5_Update 就是我们需要的函数。

所以使用的伪代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
MD5_CTX Md5CTX;

void Request()
{
MD5_Init(&Md5CTX);
}
void TickRequestProgress(char* InData,uint32 InLength)
{
MD5_Update(&Md5CTX,InData,InLength);
}
void RequestCompleted()
{
unsigned char digest[16] = { 0 };
MD5_Final(digest, &Md5CTX);
char md5string[33];
for (int i = 0; i < 16; ++i)
std::sprintf(&md5string[i * 2], "%02x", (unsigned int)digest[i]);

// result
pritf("MD5:%s",md5string);
}

这样当文件下载完,MD5 计算就完成了。

注:在 UE4 中 (~4.24) 提供的 OpenSSL 在 Win 下只支持到 VS2015,可以自己把这个限制给去掉(VS2015 的链接库在 VS2017 中使用也没有问题)。