HMAC
HMAC(Hash-based Message Authentication Code)是一种基于哈希函数和密钥的消息认证码。它结合了哈希算法和一个密钥,同时验证数据完整性和来源身份。 HMAC 本质上是:HMAC(key, data) = Hash((key XOR opad) + Hash((key XOR ipad) + data)) JWT 的签名部分通常是 HMAC-SHA256(base64(header) + '.' + base64(payload), secret)。 搜索关键字:HmacSHA256、HmacMD5、createHma
官方文档:https://www.rfc-editor.org/rfc/rfc2104
适用场景:JS 逆向中识别、提取 HMAC 签名逻辑及密钥
HMAC(Hash-based Message Authentication Code)是一种基于哈希函数和密钥的消息认证码。它结合了哈希算法和一个密钥,同时验证数据完整性和来源身份。
HMAC 本质上是:HMAC(key, data) = Hash((key XOR opad) + Hash((key XOR ipad) + data))
与普通哈希的区别
| 特性 | 普通哈希(SHA/MD5) | HMAC |
|---|---|---|
| 需要密钥 | 否 | 是 |
| 防篡改 | 否 | 是(没有密钥无法伪造) |
| 验证来源 | 否 | 是 |
| 典型场景 | 数据指纹 | API 签名、JWT |
Python 实现
import hmac
import hashlib
import base64
# HMAC-MD5
def hmac_md5(key: str, data: str) -> str:
return hmac.new(
key.encode('utf-8'),
data.encode('utf-8'),
hashlib.md5
).hexdigest()
# HMAC-SHA1
def hmac_sha1(key: str, data: str) -> str:
return hmac.new(
key.encode('utf-8'),
data.encode('utf-8'),
hashlib.sha1
).hexdigest()
# HMAC-SHA256
def hmac_sha256(key: str, data: str) -> str:
return hmac.new(
key.encode('utf-8'),
data.encode('utf-8'),
hashlib.sha256
).hexdigest()
# HMAC-SHA512
def hmac_sha512(key: str, data: str) -> str:
return hmac.new(
key.encode('utf-8'),
data.encode('utf-8'),
hashlib.sha512
).hexdigest()
# 输出为 Base64(常见于 OAuth、API 签名)
def hmac_sha256_b64(key: str, data: str) -> str:
raw = hmac.new(
key.encode('utf-8'),
data.encode('utf-8'),
hashlib.sha256
).digest()
return base64.b64encode(raw).decode()
# 使用字节作为 key(十六进制 key 场景)
def hmac_sha256_hex_key(hex_key: str, data: str) -> str:
key_bytes = bytes.fromhex(hex_key)
return hmac.new(key_bytes, data.encode('utf-8'), hashlib.sha256).hexdigest()
# 通用封装
def hmac_hash(key: str, data: str, algorithm: str = 'sha256', output: str = 'hex') -> str:
h = hmac.new(
key.encode('utf-8'),
data.encode('utf-8'),
getattr(hashlib, algorithm)
)
if output == 'hex':
return h.hexdigest()
elif output == 'base64':
return base64.b64encode(h.digest()).decode()
return h.digest()
# 示例
key = 'secret'
data = 'hello world'
print(hmac_sha256(key, data)) # hex 输出
print(hmac_sha256_b64(key, data)) # base64 输出
JavaScript 实现
使用 CryptoJS
const CryptoJS = require('crypto-js');
const key = 'secret';
const data = 'hello world';
// HMAC-MD5
const hmacMd5 = CryptoJS.HmacMD5(data, key).toString();
// HMAC-SHA1
const hmacSha1 = CryptoJS.HmacSHA1(data, key).toString();
// HMAC-SHA256
const hmacSha256 = CryptoJS.HmacSHA256(data, key).toString();
// HMAC-SHA512
const hmacSha512 = CryptoJS.HmacSHA512(data, key).toString();
// 输出为 Base64
const hmacSha256Base64 = CryptoJS.HmacSHA256(data, key).toString(CryptoJS.enc.Base64);
// 使用 WordArray 作为 key(十六进制 key)
const hexKey = CryptoJS.enc.Hex.parse('73656372657474');
const hmacWithHexKey = CryptoJS.HmacSHA256(data, hexKey).toString();
console.log('HMAC-MD5: ', hmacMd5);
console.log('HMAC-SHA1: ', hmacSha1);
console.log('HMAC-SHA256:', hmacSha256);
console.log('HMAC-SHA256 Base64:', hmacSha256Base64);
使用 Node.js 内置 crypto
const crypto = require('crypto');
function hmacHash(key, data, algorithm = 'sha256', encoding = 'hex') {
return crypto.createHmac(algorithm, key)
.update(data, 'utf8')
.digest(encoding);
}
const key = 'secret';
const data = 'hello world';
console.log(hmacHash(key, data, 'md5')); // HMAC-MD5 hex
console.log(hmacHash(key, data, 'sha1')); // HMAC-SHA1 hex
console.log(hmacHash(key, data, 'sha256')); // HMAC-SHA256 hex
console.log(hmacHash(key, data, 'sha512')); // HMAC-SHA512 hex
console.log(hmacHash(key, data, 'sha256', 'base64')); // HMAC-SHA256 Base64
// 使用 Buffer 作为 key(二进制 key)
const bufKey = Buffer.from('73656372657474', 'hex');
const result = crypto.createHmac('sha256', bufKey).update(data).digest('hex');
使用 Web Crypto API(浏览器原生)
async function hmacSha256(keyStr, data) {
const enc = new TextEncoder();
const keyMaterial = await crypto.subtle.importKey(
'raw',
enc.encode(keyStr),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
);
const signature = await crypto.subtle.sign(
'HMAC',
keyMaterial,
enc.encode(data)
);
return Array.from(new Uint8Array(signature))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}
// 输出为 Base64
async function hmacSha256Base64(keyStr, data) {
const enc = new TextEncoder();
const keyMaterial = await crypto.subtle.importKey(
'raw',
enc.encode(keyStr),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
);
const signature = await crypto.subtle.sign('HMAC', keyMaterial, enc.encode(data));
return btoa(String.fromCharCode(...new Uint8Array(signature)));
}
// 使用示例
hmacSha256('secret', 'hello world').then(console.log);
逆向中常见场景
API 签名生成
import hmac, hashlib, time, urllib.parse
def generate_signature(params: dict, secret: str) -> str:
# 1. 将参数按 key 排序后拼接
sorted_params = sorted(params.items())
param_str = '&'.join(f'{k}={v}' for k, v in sorted_params)
# 2. HMAC-SHA256 签名
return hmac.new(secret.encode(), param_str.encode(), hashlib.sha256).hexdigest()
params = {'uid': '123', 'ts': str(int(time.time())), 'action': 'login'}
sign = generate_signature(params, 'my_secret_key')
JWT 签名验证
JWT 的签名部分通常是 HMAC-SHA256(base64(header) + '.' + base64(payload), secret)。
import base64, json, hmac, hashlib
def b64url_encode(data: bytes) -> str:
return base64.urlsafe_b64encode(data).rstrip(b'=').decode()
def jwt_sign(payload: dict, secret: str) -> str:
header = b64url_encode(json.dumps({'alg':'HS256','typ':'JWT'}).encode())
payload = b64url_encode(json.dumps(payload).encode())
msg = f'{header}.{payload}'
sig = hmac.new(secret.encode(), msg.encode(), hashlib.sha256).digest()
return f'{msg}.{b64url_encode(sig)}'
快速识别
搜索关键字:HmacSHA256、HmacMD5、createHmac、HMAC、sign(配合密钥)
特征:HMAC 结果与普通哈希输出格式相同(长度一致),区别在于代码中会出现一个额外的密钥参数。
最佳实践
密钥通常来自接口响应或本地常量:逆向 HMAC 的核心是找密钥。密钥可能硬编码在 JS 文件中(搜索 secret、key、apiKey),也可能由登录接口动态下发(抓包找到后用 Python 模拟 HMAC 验证)。
Hook CryptoJS.HmacSHA256 同时打印消息和密钥:
const _hmac = CryptoJS.HmacSHA256
CryptoJS.HmacSHA256 = function(msg, key) {
console.log('[HMAC-SHA256] msg:', msg.toString ? msg.toString() : msg)
console.log('[HMAC-SHA256] key:', key.toString ? key.toString() : key)
return _hmac.call(this, msg, key)
}
用 Python 快速验证 HMAC:找到消息和密钥后,用 Python 交叉验证:
import hmac, hashlib
key = b'secret_key'
msg = b'message_to_sign'
result = hmac.new(key, msg, hashlib.sha256).hexdigest()
HMAC-SHA1 签名的 sign 方法是重点:AWS 签名、微信支付、很多 API 签名都用 HMAC-SHA1/256,搜索 .sign( 或 computeSignature( 通常能快速定位。
密钥经过多次处理时逐层剥离:密钥可能先做 Base64 解码、URL 解码或 Hex 解码后才传入 HMAC,需要在 Hook 中观察 key 的原始形态后逐层还原。
常见陷阱
陷阱:HMAC 密钥是 CryptoJS WordArray 对象
现象: Hook 打印的 key 是 {words: [...], sigBytes: 32} 而非字符串。
原因: CryptoJS 内部用 WordArray 表示二进制数据,密钥可能是 CryptoJS.enc.Hex.parse('abcd...') 形式传入,不是纯字符串密钥。
解决: 用 CryptoJS.enc.Hex.stringify(key) 还原为十六进制字符串;Python 侧 hmac.new(bytes.fromhex(hex_key), ...) 对应处理。
陷阱:混淆代码中 HMAC 和 SHA256 函数名相同难以区分
现象: 找到了 SHA-256 函数,输出长度相同但结果不对,以为是魔改。
原因: 实际上是 HMAC-SHA256,有额外密钥参数。混淆后参数名无意义,容易误判。
解决: 在混淆代码中确认该函数接受几个参数:纯哈希接受 1 个(数据),HMAC 接受 2 个(数据 + 密钥)。
陷阱:密钥每次请求都变化
现象: 抓包发现密钥不固定,模拟请求时无法复现签名。
原因: 密钥可能基于时间戳、设备 ID 或服务端下发的 nonce 动态生成,而非静态常量。
解决: 追踪密钥的生成逻辑(通常紧邻 HMAC 调用),分析时间戳混入方式,在模拟时同步生成密钥。