> ## Content Index
> Fetch the complete content index at: https://blog.vercanti.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# MD5
- URL: https://blog.vercanti.com/md5/
- Published: 2026-08-28T14:35:04.000Z
- Updated: 2026-08-28T14:57:58.000Z
- Description: MD5（Message Digest Algorithm 5）是一种单向哈希函数，输出固定 128 位（32 位十六进制字符串）。不可逆，常用于数据签名和完整性校验。 搜索特征常量而非函数名：混淆代码中函数名被替换，但算法的魔法数字（0x67452301、0xefcdab89、0x98badcfe、0x10325476）不会被混淆，全局搜索这 4 个初始化向量可快速定位 MD5 函数。 Hook CryptoJS.MD5 拦截明文：若页面使用 CryptoJS 库，在调用前注入 Hook 打印参数： 区分完整 MD5 和截取变体：目标站点可能只取 MD5
- Author: yellowdog
- Tags: js逆向, 加密算法

> 官方文档：<https://www.rfc-editor.org/rfc/rfc1321>  
> 适用场景：JS 逆向中识别、提取、还原 MD5 加密逻辑

MD5（Message Digest Algorithm 5）是一种单向哈希函数，输出固定 128 位（32 位十六进制字符串）。不可逆，常用于数据签名和完整性校验。

> MD5 已不适合密码存储（可被彩虹表攻击），但在 JS 逆向场景中仍大量存在于签名参数生成中。

---

## Python 实现

```python
import hashlib

# 基础用法
def md5(data: str, encoding='utf-8') -> str:
    return hashlib.md5(data.encode(encoding)).hexdigest()

# 32位小写
print(md5('hello'))           # 5d41402abc4b2a76b9719d911017c592

# 32位大写
print(md5('hello').upper())   # 5D41402ABC4B2A76B9719D911017C592

# 16位（取中间16位）
def md5_16(data: str) -> str:
    return md5(data)[8:24]

print(md5_16('hello'))        # bc4b2a76b9719d91

# 对字节数据直接哈希
def md5_bytes(data: bytes) -> str:
    return hashlib.md5(data).hexdigest()

# MD5 加盐
def md5_salt(data: str, salt: str) -> str:
    return md5(salt + data)          # 盐前置
    # 或 return md5(data + salt)     # 盐后置

# 多次 MD5（常见于某些网站）
def md5_double(data: str) -> str:
    return md5(md5(data))

# 大文件分块哈希
def md5_file(filepath: str) -> str:
    h = hashlib.md5()
    with open(filepath, 'rb') as f:
        while chunk := f.read(8192):
            h.update(chunk)
    return h.hexdigest()

```

---

## JavaScript 实现

### 使用 CryptoJS（浏览器/Node.js 通用）

```javascript
// 安装：npm install crypto-js
const CryptoJS = require('crypto-js');

// 32位小写
const hash = CryptoJS.MD5('hello').toString();
console.log(hash);  // 5d41402abc4b2a76b9719d911017c592

// 32位大写
const hashUpper = CryptoJS.MD5('hello').toString().toUpperCase();

// 16位（中间16位）
const hash16 = CryptoJS.MD5('hello').toString().substring(8, 24);

// 加盐
const saltedHash = CryptoJS.MD5('salt' + 'hello').toString();

// 对 WordArray（字节数组）哈希
const wordArray = CryptoJS.enc.Hex.parse('68656c6c6f');
const hashFromBytes = CryptoJS.MD5(wordArray).toString();

```

### 使用 Node.js 内置 crypto

```javascript
const crypto = require('crypto');

// 基础用法
function md5(data, encoding = 'hex') {
    return crypto.createHash('md5').update(data).digest(encoding);
}

console.log(md5('hello'));              // 5d41402abc4b2a76b9719d911017c592
console.log(md5('hello', 'base64'));   // XUFAKrxLKna5cZ2REBfFkg==

// 加盐
function md5Salt(data, salt) {
    return md5(salt + data);
}

// 流式（大文件）
const fs = require('fs');
function md5File(path) {
    return new Promise((resolve) => {
        const hash = crypto.createHash('md5');
        fs.createReadStream(path).pipe(hash);
        hash.on('finish', () => resolve(hash.digest('hex')));
    });
}

```

### 使用 Web Crypto API（浏览器原生，异步）

> Web Crypto API 不直接支持 MD5，因为 MD5 已被认为不安全。需要使用第三方库或用 SHA-256 替代。

---

## 逆向中常见变体

| 变体           | 说明                              | Python                                     |
| ------------ | ------------------------------- | ------------------------------------------ |
| 标准 MD5       | 32位小写 hex                       | hashlib.md5(data).hexdigest()              |
| 大写 MD5       | 32位大写 hex                       | .hexdigest().upper()                       |
| 16位 MD5      | 取中间16位                          | \[8:24\]                                   |
| 双重 MD5       | md5(md5(data))                  | 嵌套调用                                       |
| 加盐 MD5       | md5(salt+data) 或 md5(data+salt) | 拼接后哈希                                      |
| MD5 后 Base64 | 对 MD5 结果再 Base64                | base64.b64encode(bytes.fromhex(md5(data))) |

---

## 快速识别

- 结果固定为 **32 个十六进制字符**（或 16 个，取中间段）
- 搜索关键字：`md5`、`MD5`、`CryptoJS.MD5`、`createHash('md5')`
- 特征常量：`0x67452301`、`0xefcdab89`（MD5 初始化向量）

---

## 最佳实践

**搜索特征常量而非函数名**：混淆代码中函数名被替换，但算法的魔法数字（`0x67452301`、`0xefcdab89`、`0x98badcfe`、`0x10325476`）不会被混淆，全局搜索这 4 个初始化向量可快速定位 MD5 函数。

**Hook `CryptoJS.MD5` 拦截明文**：若页面使用 CryptoJS 库，在调用前注入 Hook 打印参数：

```javascript
const _md5 = CryptoJS.MD5
CryptoJS.MD5 = function(msg, ...args) {
  console.log('[MD5 input]', typeof msg === 'string' ? msg : JSON.stringify(msg))
  return _md5.call(this, msg, ...args)
}

```

**区分完整 MD5 和截取变体**：目标站点可能只取 MD5 结果的一部分（如取第 8\~24 位共 16 字符），对比 Python `hashlib.md5(b'xxx').hexdigest()` 验证时要按相同截取方式处理。

**自定义 MD5（魔改）先对比标准输出**：若标准 MD5 对不上，先输入 `"abc"` 测试：标准 MD5 结果为 `900150983cd24fb0d6963f7d28e17f72`，与之对比可判断初始向量、字节序、字符编码哪里被修改。

**用 Python 快速验证还原结果**：还原 JS 逻辑后，用 Python `hashlib.md5(input.encode()).hexdigest()` 交叉验证；若编码不同，尝试 `encode('latin-1')` 或 `encode('utf-16-le')`。

---

## 常见陷阱

### 陷阱：MD5 输入编码不一致导致结果不匹配

**现象：** Python `hashlib.md5(b'hello').hexdigest()` 结果与目标 JS MD5 结果不同。  
**原因：** JS 字符串默认 UTF-16 编码，而 CryptoJS 处理时会先将字符串转为 UTF-8 字节；若 JS 代码将字符串转为 Latin-1 或直接取 charCode，结果会不同。  
**解决：** 检查 JS 中 MD5 调用前的字符串处理逻辑（如 `encodeURIComponent`、`unescape`、`btoa`），在 Python 中用相同编码处理后再 MD5。

### 陷阱：32 位大小端（字节序）被修改

**现象：** 特征常量一致但输出完全不同，不像是只修改了初始向量。  
**原因：** 部分魔改版 MD5 修改了内部 32 位整数的字节序（little-endian ↔ big-endian），导致每轮输出不同。  
**解决：** 对比输入 `""` 空字符串的 MD5 输出（标准为 `d41d8cd98f00b204e9800998ecf8427e`），从这一步找到字节序差异所在。

### 陷阱：将 16 位截取版误认为是不同算法

**现象：** 签名长度为 16 个字符，查阅算法表找不到对应输出长度的算法。  
**原因：** 这很可能是取了 MD5 的 32 位结果的中间 16 字符（第 8\~24 位），而非独立算法。  
**解决：** 用标准 MD5 计算后截取相同范围验证；也可能是截取了前 16 或后 16 位，逐一尝试。

---

## 参见

[SHA](https://blog.vercanti.com/sha-xi-lie/)  
[HMAC](https://blog.vercanti.com/hmac/)  
[魔法数字速查](https://blog.vercanti.com/jia-mi-suan-fa-mo-fa-shu-zi-su-cha/)  
[代码片段大全](https://blog.vercanti.com/js-ni-xiang-hook-yu-dai-ma-pian-duan-da-quan/)