文章目录
1 算法原理概述
MD5,即Message-Digest Algorithm 5 (信息-摘要算法5),是广泛使用的Hash 算法,用于确保信息传输的完整性和一致性。 MD5 使用little-endian(小端模式),输入任意不定长度信息,以 512-bit 进行分组,生成四个32-bit 数据,最后联合输出固定 128-bit 的信息摘要。 其基本过程为:填充、分块、缓冲区初始化、循环压缩、得出结果。
基本流程图如下:
2 总体结构
- MD5.hpp:定义MD5类,宏定义4轮循环中使用的生成函数,以及循环移位
- MD5.cpp:实现MD5.hpp中定义的类方法
- main.cpp:测试文件,数据来自https://www.ietf.org/rfc/rfc1321.txt
3 模块分解
3.1 填充及分块模块
3.2 初始化模块
3.3 循环压缩模块
3.4 整数转字符串模块
3.5 获取MD5模块(总)
3.6 输入模块
3.7 输出模块
4 数据结构
4.1 MD5类
面向对象编程,在类内定义各个变量和函数,可以使架构更加清晰。
4.2 unsigned int
unsigned int为32-bit无符号整数,正好可以对应一个32-bit分组。
5 编译运行结果
使用了RCF 1321中给出的标准测试样例进行测试:
6 源代码
MD5.hpp
// MD5.hpp
#include <iostream>
#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n))))
#define F(x, y, z) (((x) & (y)) | ((~x) & (z)))
#define G(x, y, z) (((x) & (z)) | ((y) & (~z)))
#define H(x, y, z) ((x) ^ (y) ^ (z))
#define I(x, y, z) ((y) ^ ((x) | (~z)))
typedef unsigned int byte4;
typedef unsigned long long int byte8;
using namespace std;
class MD5{
public:
MD5(const string &input);
void padding();
void init();
void transform(byte8 blockNum);
string toString(byte4 a);
string getMD5();
private:
string input; // 输入字符串消息
byte4* M; // 填充后的用32-bit整数数组表示的32-bit分组
byte8 L; // L个512-bit(即64-byte)分组
byte4 A, B, C, D; // MD 缓冲区,4个32-bit 寄存器(A, B, C, D)
};
MD5.cpp
// MD5.cpp
# include "MD5.hpp"
// 各次迭代运算采用的T值, T[i] = int((2^32)*|sin(i)|)
const byte4 T[]={
0xd76aa478,0xe8c7b756,0x242070db,0xc1bdceee,
0xf57c0faf,0x4787c62a,0xa8304613,0xfd469501,
0x698098d8,0x8b44f7af,0xffff5bb1,0x895cd7be,
0x6b901122,0xfd987193,0xa679438e,0x49b40821,
0xf61e2562,0xc040b340,0x265e5a51,0xe9b6c7aa,
0xd62f105d,0x02441453,0xd8a1e681,0xe7d3fbc8,
0x21e1cde6,0xc33707d6,0xf4d50d87,0x455a14ed,
0xa9e3e905,0xfcefa3f8,0x676f02d9,0x8d2a4c8a,
0xfffa3942,0x8771f681,0x6d9d6122,0xfde5380c,
0xa4beea44,0x4bdecfa9,0xf6bb4b60,0xbebfbc70,
0x289b7ec6,0xeaa127fa,0xd4ef3085,0x04881d05,
0xd9d4d039,0xe6db99e5,0x1fa27cf8,0xc4ac5665,
0xf4292244,0x432aff97,0xab9423a7,0xfc93a039,
0x655b59c3,0x8f0ccc92,0xffeff47d,0x85845dd1,
0x6fa87e4f,0xfe2ce6e0,0xa3014314,0x4e0811a1,
0xf7537e82,0xbd3af235,0x2ad7d2bb,0xeb86d391};
// 各次迭代运算采用的左循环移位的s值
const byte4 s[]={7,12,17,22,7,12,17,22,7,12,17,22,7,12,17,22,
5,9,14,20,5,9,14,20,5,9,14,20,5,9,14,20,
4,11,16,23,4,11,16,23,4,11,16,23,4,11,16,23,
6,10,15,21,6,10,15,21,6,10,15,21,6,10,15,21};
// 各轮循环中迭代使用的X[k]的下标
const byte4 k[]={0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,
1, 6,11, 0, 5,10,15, 4, 9,14, 3, 8,13, 2, 7,12,
5, 8,11,14, 1, 4, 7,10,13, 0, 3, 6, 9,12,15, 2,
0, 7,14, 5,12, 3,10, 1, 8,15, 6,13, 4,11, 2, 9};
MD5::MD5(const string &input){
this->input = input;
}
/*
* 填充及分块
*/
void MD5::padding(){
byte8 len = input.length(); // 输入字节数
L = ((len + 8) / 64) + 1; // L个512-bit(即64-byte)分组
byte8 N = L * 16; // N个32-bit字
M = new byte4[N]; // 填充后的结果用byte4表示,每个byte4为一个32-bit字
for(byte4 i = 0; i < N; i++)
M[i] = 0; // 所有位先初始化为0
for(byte4 i = 0; i < len; i++)
M[i / 4] |= (input[i])<<((i % 4) * 8); // 一个32位整数可保存4个字符信息
M[len / 4] |= 0x80<<((len%4)*8); // 尾部添加1
// 尾部附加原始长度
M[N - 1] |= (len * 8) >> 32;
M[N - 2] |= len * 8;
}
/*
* 初始化MD缓存区
*/
void MD5::init(){
A= 0x67452301;
B= 0xEFCDAB89;
C= 0x98BADCFE;
D= 0x10325476;
}
/*
* 循环压缩
*/
void MD5::transform(byte8 blockNum){
byte4 a = A;
byte4 b = B;
byte4 c = C;
byte4 d = D;
for (int i = 0; i < 64; i++) {
if(i < 16){
A = B + ROTATE_LEFT(A + F(B, C, D) + M[blockNum * 16 + k[i]] + T[i], s[i]);
}else if(i < 32){
A = B + ROTATE_LEFT(A + G(B, C, D) + M[blockNum * 16 + k[i]] + T[i], s[i]);
}else if(i < 48){
A = B + ROTATE_LEFT(A + H(B, C, D) + M[blockNum * 16 + k[i]] + T[i], s[i]);
}else{
A = B + ROTATE_LEFT(A + I(B, C, D) + M[blockNum * 16 + k[i]] + T[i], s[i]);
}
byte4 temp = D;
D = C;
C = B;
B = A;
A = temp;
}
A += a;
B += b;
C += c;
D += d;
}
/*
* 把整数转为字符串
*/
string MD5::toString(byte4 a){
string result;
char buffer[4];
for(int i = 0; i < 4; i++){
snprintf(buffer, sizeof(buffer), "%02x", (a >> i * 8) & 0xff);
result += buffer;
}
return result;
}
/*
* 获取MD5
*/
string MD5::getMD5(){
// 第一步:填充及分块
padding();
// 第二步:初始化MD缓存区
init();
// 第三步:以512-bit 消息分组为单位,每一分组Yq (q= 0, 1, …, L-1) 经过4个循环的压缩
for(byte8 i = 0; i < L; i++)
transform(i);
// 第四步:返回字符串结果
return toString(A).append(toString(B)).append(toString(C)).append(toString(D));
}
main.cpp
// main.cpp
#include "MD5.hpp"
#define LEN 7
int main(){
string input[LEN] = {
"",
"a",
"abc",
"message digest",
"abcdefghijklmnopqrstuvwxyz",
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
"12345678901234567890123456789012345678901234567890123456789012345678901234567890"};
string expected[LEN] = {
"d41d8cd98f00b204e9800998ecf8427e", "0cc175b9c0f1b6a831c399e269772661",
"900150983cd24fb0d6963f7d28e17f72", "f96b697d7cb7938d525a2f31aaf161d0",
"c3fcd3d76192e4007dfb496cca67e13b", "d174ab98d277d9f5a5611c2c9f419d9f",
"57edf4a22be3c955ac49da2e2107b67a"};
for (int i = 0; i < LEN; i++) {
MD5 md5(input[i]);
string digest = md5.getMD5();
cout << "--------------------------------" << endl;
cout << "Test " << i << ":" << endl;
cout << "Original Message: " << input[i] << endl;
cout << "Expected Result: " << expected[i] << endl;
cout << "Calculated Result: " << digest << endl;
cout << (digest == expected[i] ? "PASS TEST" : "FAILED") << endl;
}
return 0;
}