【Reverse-Crypto】深入逆向-密码篇-MD5算法详解以及代码编写
字数 906 2025-08-22 12:22:54
MD5算法详解与实现指南
1. MD5算法概述
MD5(Message Digest Algorithm 5)是一种广泛使用的密码散列函数,产生128位(16字节)的哈希值,通常表示为32个十六进制字符。
核心特性
- 固定输出:无论输入多长,输出总是128位
- 不可逆性:无法从哈希值反推原始数据
- 抗碰撞性:难以找到两个不同输入产生相同哈希值
- 雪崩效应:输入微小变化导致输出巨大差异
应用场景
- 文件完整性校验
- 数字签名
- 密码存储(需加盐)
- 数据指纹生成
2. MD5算法处理步骤
完整处理流程
- 消息填充(Padding)
- 分组(Grouping)
- 缓冲区初始化
- 迭代处理
- 输出结果
3. 详细实现解析
3.1 消息填充
目的:将输入长度填充至模512等于448的位数(即N×512+448)
填充规则:
- 首先附加一个"1"位
- 然后附加足够多的"0"位
- 最后附加64位的原始消息长度(小端序)
void MD5::padding(vector<unsigned char>& data) {
uint64_t nBits = data.size() * 8;
size_t paddingBits = 0;
if (nBits % 512 >= 448) {
paddingBits = 512 - nBits % 512 % 448;
} else {
paddingBits = 448 - nBits % 512;
}
// 先填充1位"1"
data = Tools::left_shift(data, 1);
data[data.size() - 1] ^= 0x1;
paddingBits -= 1;
// 再填充剩余"0"位
data = Tools::left_shift(data, paddingBits);
// 最后附加原始长度(64位小端序)
vector<uint8_t> MessageSize = Tools::uint64ToVector(Tools::swapEndian64(nBits));
data.insert(data.end(), MessageSize.begin(), MessageSize.end());
}
3.2 分组处理
将填充后的数据按512位(64字节)分组,每组再划分为16个32位字(小端序存储)。
vector<vector<uint32_t>> MD5::blockText(vector<uint8_t>& data) {
size_t groupCount = data.size() * 8 / 512;
vector<vector<uint32_t>> groups(groupCount);
for (size_t i = 0; i < groupCount; i++) {
groups[i] = vector<uint32_t>(16, 0);
for (size_t j = 0; j < 16; j++) {
for (size_t k = 0; k < 4; k++) {
groups[i][j] |= (uint32_t)data[i*64 + j*4 + k] << ((3-k)*8);
}
}
}
return groups;
}
3.3 缓冲区初始化
使用4个32位幻数初始化哈希缓冲区:
const uint32_t A = 0x67452301;
const uint32_t B = 0xEFCDAB89;
const uint32_t C = 0x98BADCFE;
const uint32_t D = 0x10325476;
3.4 迭代处理
MD5使用四轮循环处理,每轮16次操作,共64次操作。每轮使用不同的非线性函数:
非线性函数定义
#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))
四轮处理函数
uint32_t MD5::FF(uint32_t a, uint32_t b, uint32_t c, uint32_t d, uint32_t M, uint32_t t, uint32_t shift) {
return b + Tools::rotate_left(a + F(b, c, d) + M + t, shift);
}
uint32_t MD5::GG(uint32_t a, uint32_t b, uint32_t c, uint32_t d, uint32_t M, uint32_t t, uint32_t shift) {
return b + Tools::rotate_left(a + G(b, c, d) + M + t, shift);
}
uint32_t MD5::HH(uint32_t a, uint32_t b, uint32_t c, uint32_t d, uint32_t M, uint32_t t, uint32_t shift) {
return b + Tools::rotate_left(a + H(b, c, d) + M + t, shift);
}
uint32_t MD5::II(uint32_t a, uint32_t b, uint32_t c, uint32_t d, uint32_t M, uint32_t t, uint32_t shift) {
return b + Tools::rotate_left(a + I(b, c, d) + M + t, shift);
}
循环左移函数
uint32_t Tools::rotate_left(uint32_t value, uint8_t shift) {
const int bits = 32;
shift %= bits;
return (value << shift) | (value >> (bits - shift));
}
完整迭代处理
void MD5::transByIterator(vector<uint32_t>& v, uint32_t& A, uint32_t& B, uint32_t& C, uint32_t& D) {
// Round 1 (FF)
A = FF(A, B, C, D, v[0], 0xd76aa478, 7);
D = FF(D, A, B, C, v[1], 0xe8c7b756, 12);
// ... 共16次FF操作
// Round 2 (GG)
A = GG(A, B, C, D, v[1], 0xf61e2562, 5);
D = GG(D, A, B, C, v[6], 0xc040b340, 9);
// ... 共16次GG操作
// Round 3 (HH)
A = HH(A, B, C, D, v[5], 0xfffa3942, 4);
D = HH(D, A, B, C, v[8], 0x8771f681, 11);
// ... 共16次HH操作
// Round 4 (II)
A = II(A, B, C, D, v[0], 0xf4292244, 6);
D = II(D, A, B, C, v[7], 0x432aff97, 10);
// ... 共16次II操作
}
3.5 输出处理
将最终的四轮处理结果转换为大端序并拼接:
vector<uint32_t> MD5::update1(vector<vector<uint32_t>>& groups) {
vector<uint32_t> res = {A, B, C, D};
// 小端序转换
for (size_t i = 0; i < groups.size(); i++) {
encode(groups[i]);
}
// 分组处理
size_t i = 0;
while (i < groups.size()) {
uint32_t tmp_A = res[0];
uint32_t tmp_B = res[1];
uint32_t tmp_C = res[2];
uint32_t tmp_D = res[3];
transByIterator(groups[i], tmp_A, tmp_B, tmp_C, tmp_D);
res[0] += tmp_A;
res[1] += tmp_B;
res[2] += tmp_C;
res[3] += tmp_D;
i++;
}
// 转回大端序
decode(res);
return res;
}
4. 优化实现
使用预定义的常量数组简化代码:
常量定义
// 位移矩阵
const uint8_t shiftBits[4][4] = {
{7, 12, 17, 22},
{5, 9, 14, 20},
{4, 11, 16, 23},
{6, 10, 15, 21}
};
// 伪随机数矩阵
const uint32_t pseudoRandom[4][16] = {
{0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821},
// ... 其他三轮的伪随机数
};
// 消息分组索引矩阵
const uint8_t groupsIndex[4][16] = {
{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}
};
优化后的迭代处理
void MD5::transByRange(vector<uint32_t>& v, vector<uint32_t>& res) {
using RoundFunction = uint32_t (MD5::*)(uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t);
RoundFunction roundFunctions[] = {&MD5::FF, &MD5::GG, &MD5::HH, &MD5::II};
for (size_t i = 0; i < 4; i++) {
const uint32_t* pseudo = pseudoRandom[i];
const uint8_t* shift = shiftBits[i];
const uint8_t* groupIndex = groupsIndex[i];
for (size_t k = 16; k > 0; k--) {
res[k%4] = (this->*roundFunctions[i])(
res[k%4], res[(k+1)%4], res[(k+2)%4], res[(k+3)%4],
v[groupIndex[16-k]], pseudo[16-k], shift[(16-k)%4]
);
}
}
}
5. 辅助工具函数
字节序转换
// 64位字节序转换
uint64_t Tools::swapEndian64(uint64_t x) {
return ((x >> 56) & 0xff) | ((x >> 40) & 0xff00) |
((x >> 24) & 0xff0000) | ((x >> 8) & 0xff000000) |
((x << 8) & 0xff00000000) | ((x << 24) & 0xff0000000000) |
((x << 40) & 0xff000000000000) | ((x << 56) & 0xff00000000000000);
}
// uint64转vector
vector<unsigned char> Tools::uint64ToVector(uint64_t value) {
vector<uint8_t> vec(8);
for (size_t i = 0; i < 8; ++i) {
vec[i] = static_cast<uint8_t>((value >> ((7-i)*8)) & 0xFF);
}
return vec;
}
左移操作
vector<unsigned char> Tools::left_shift(vector<unsigned char> vec, size_t shift) {
if (shift == 0) return vec;
size_t byte_shift = shift / 8;
size_t bit_shift = shift % 8;
vector<unsigned char> result(vec.size() + byte_shift, 0);
for (size_t i = 0; i < vec.size(); ++i) {
result[i] |= (vec[i] << bit_shift);
if (i + 1 < vec.size()) {
result[i] |= (vec[i+1] >> (8 - bit_shift));
}
}
if (vec[0] >> (8 - bit_shift)) {
result.insert(result.begin(), 1);
result[0] = (vec[0] >> (8 - bit_shift));
}
return result;
}
6. 安全注意事项
虽然MD5曾广泛使用,但现已发现以下安全问题:
- 碰撞攻击:可以构造两个不同输入产生相同哈希值
- 彩虹表攻击:使用预先计算的哈希表反向查找
- 速度过快:不利于密码存储(应使用bcrypt/PBKDF2等)
建议:
- 文件校验等非安全场景仍可使用
- 密码存储必须加盐并使用更安全的算法
- 安全敏感场景应使用SHA-2或SHA-3系列算法
7. 完整实现示例
string MD5::hash(const string& input) {
// 1. 数据准备
vector<uint8_t> plaintext(input.begin(), input.end());
// 2. 填充
padding(plaintext);
// 3. 分组
vector<vector<uint32_t>> groups = blockText(plaintext);
// 4. 哈希计算
vector<uint32_t> res = updateRange(groups);
// 5. 格式化输出
stringstream ss;
for (size_t i = 0; i < res.size(); i++) {
ss << hex << setw(8) << setfill('0') << res[i];
}
return ss.str();
}
通过本指南,您应该能够全面理解MD5算法的原理和实现细节,并能够根据需要实现自己的MD5哈希函数。记住在实际应用中要考虑算法的安全性限制。