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)}'

快速识别

搜索关键字:HmacSHA256HmacMD5createHmacHMACsign(配合密钥)

特征:HMAC 结果与普通哈希输出格式相同(长度一致),区别在于代码中会出现一个额外的密钥参数。


最佳实践

密钥通常来自接口响应或本地常量:逆向 HMAC 的核心是找密钥。密钥可能硬编码在 JS 文件中(搜索 secretkeyapiKey),也可能由登录接口动态下发(抓包找到后用 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 调用),分析时间戳混入方式,在模拟时同步生成密钥。


参见

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

阅读更多

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