MD5

MD5(Message Digest Algorithm 5)是一种单向哈希函数,输出固定 128 位(32 位十六进制字符串)。不可逆,常用于数据签名和完整性校验。 搜索特征常量而非函数名:混淆代码中函数名被替换,但算法的魔法数字(0x67452301、0xefcdab89、0x98badcfe、0x10325476)不会被混淆,全局搜索这 4 个初始化向量可快速定位 MD5 函数。 Hook CryptoJS.MD5 拦截明文:若页面使用 CryptoJS 库,在调用前注入 Hook 打印参数: 区分完整 MD5 和截取变体:目标站点可能只取 MD5

分享

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

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

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


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 通用)

// 安装: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

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 个,取中间段)
  • 搜索关键字:md5MD5CryptoJS.MD5createHash('md5')
  • 特征常量:0x674523010xefcdab89(MD5 初始化向量)

最佳实践

搜索特征常量而非函数名:混淆代码中函数名被替换,但算法的魔法数字(0x674523010xefcdab890x98badcfe0x10325476)不会被混淆,全局搜索这 4 个初始化向量可快速定位 MD5 函数。

Hook CryptoJS.MD5 拦截明文:若页面使用 CryptoJS 库,在调用前注入 Hook 打印参数:

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 调用前的字符串处理逻辑(如 encodeURIComponentunescapebtoa),在 Python 中用相同编码处理后再 MD5。

陷阱:32 位大小端(字节序)被修改

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

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

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


参见

SHA
HMAC
魔法数字速查
代码片段大全

阅读更多

Web 安全基础

1. HTML 转义(服务端渲染必须): 2. CSP(Content Security Policy): 3. HttpOnly Cookie:防止 JS 读取会话 Cookie: 4. 前端框架防护: 攻击者在第三方网站构造一个表单,诱导已登录用户提交,浏览器会自动携带目标站的 Cookie。 触发条件: 1. 用户已登录目标网站(Cookie 有效) 2. 目标 API 仅凭 Cookie 识别用户身份 3. 请求来源未验证 1. CSRF Token(推荐): 2. SameSite Cookie: 3. 验证 Origin/Referer 头:

By yellowdog

HTTP 协议深度指南

HTTP(HyperText Transfer Protocol)是 Web 的基础传输协议,基于 TCP/IP,采用请求/响应模型。 相关文档:Web安全基础(/web-an-quan-ji-chu/) FastAPI完全指南(/fastapi-wan-quan-zhi-nan/) Nginx完全指南(/nginx-wan-quan-zhi-nan/) 幂等性:多次执行相同请求,服务器状态结果相同。PUT /users/1 多次执行结果一致;POST /users 每次创建新资源,非幂等。 浏览器直接从本地缓存读取,不向服务器发送请求。 缓存命中时,状

By yellowdog

系统设计基础

SLA 对照表: 选择建议:无状态服务(Web 层、API 层)优先水平扩展;数据库初期垂直扩展,达到瓶颈后考虑分库分表或读写分离。 缓存穿透(查询不存在的 key,每次都打到 DB): 缓存击穿(热点 key 过期,瞬间大量请求打到 DB): 缓存雪崩(大量 key 同时过期,或缓存服务宕机): 令牌桶 Python 实现: Redis 实现分布式限流(滑动窗口): URL 命名规则: Cursor 分页响应格式: 雪花算法结构(64 bit): 定义:分布式系统不能同时满足以下三个特性: 在分布式环境中 P 是必须保证的,所以实际是 CP vs AP

By yellowdog

算法思路与模板

二分查找要求序列有序,每次将搜索范围缩减一半,时间复杂度 O(log n)。 两个指针从两端向中间收缩,常用于有序数组。 滑动窗口维护一个满足条件的区间 left, right,right 不断向右扩张,条件不满足时收缩 left。 滑动窗口通用框架: 1. 确定"子问题":原问题可以分解为哪些规模更小的同类问题 2. 定义 dpi 或 dpij 的含义,要足够清晰 3. 推导状态转移方程 4. 确定初始状态(边界条件) 5. 确定计算顺序(确保依赖的子问题先计算) 每件物品最多选一次。dpj = 容量为 j 时的最大价值,逆序遍历容量防止重复选取。 每

By yellowdog