> ## 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.

# HMAC
- URL: https://blog.vercanti.com/hmac/
- Published: 2026-08-28T14:35:03.000Z
- Updated: 2026-08-28T14:57:57.000Z
- Description: 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
- Author: yellowdog
- Tags: js逆向, 加密算法

> 官方文档：<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 实现

```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

```javascript
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

```javascript
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（浏览器原生）

```javascript
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 签名生成

```python
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)`。

```python
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` 同时打印消息和密钥**：

```javascript
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 交叉验证：

```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](https://blog.vercanti.com/sha-xi-lie/)  
[MD5](https://blog.vercanti.com/md5/)  
[魔法数字速查](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/)