在网络通信、接口鉴权、数据校验的场景中,我们经常会听到“HMAC”这个词,它既是保障数据安全的关键技术,也是逆向工程师常遇到的“拦路虎”。很多人会把它和普通哈希(MD5、SHA256)、对称加密弄混,今天就用一篇文章,从零拆解HMAC的核心逻辑、计算过程、代码实现,还有逆向视角下的突破思路,新手也能轻松看懂。

一、先搞懂:HMAC到底是什么?

HMAC 的全称是 基于哈希的消息认证码(Hash-based Message Authentication Code),本质上是一种加密机制,核心作用只有两个,也是它解决的核心痛点:

  • 消息完整性:确保消息在传输或存储过程中,没有被篡改(哪怕只改一个字符,结果都会完全不同);

  • 身份认证:确认消息的发送方是合法的,不是伪造的——因为只有持有密钥的人,才能生成正确的HMAC值。

简单来说,HMAC的核心思想就是“哈希函数+密钥”的结合:通过将普通哈希函数与一个共享密钥绑定,生成专属的消息认证码(MAC),接收方通过相同的密钥和哈希逻辑,就能验证消息的真实性和完整性。

二、HMAC的工作原理:两步哈希+双重填充

很多人以为HMAC就是“哈希(密钥+消息)”,其实不然——它的计算过程更严谨,核心是“密钥准备+两次哈希+双重填充”,确保即使哈希函数本身有漏洞,也能通过密钥和填充逻辑提升安全性。

1. 核心前提

发送方和接收方必须提前共享一个密钥(这个密钥不对外公开,是验证的核心),双方使用相同的哈希函数(如MD5、SHA256),才能保证计算结果一致。

2. 标准计算过程

我们用通俗的步骤,拆解HMAC的计算逻辑(结合具体示例,更容易理解):

第一步:密钥准备

假设我们的密钥是“key”,使用的哈希函数块大小是4字节(实际不同哈希函数块大小不同,比如MD5是64字节)。 如果密钥长度不足块大小,就用0x00(零字节)填充到块大小——这里“key”是3字节,填充后变成“key\0”(3个字符+1个零字节,共4字节);如果密钥长度超过块大小,先对密钥做一次哈希,再用哈希结果作为新的密钥。

第二步:与内填充(ipad)混合

ipad(inner pad)是固定填充值,每个字节都是0x36,长度和哈希块大小一致(这里是4字节,即0x36363636)。 将准备好的密钥与ipad进行按位异或运算,得到混合后的“内部密钥”。

第三步:第一次哈希计算

将混合后的内部密钥,与原始消息拼接在一起,再用选定的哈希函数(如MD5)计算,得到一个中间哈希值。

第四步:与外填充(opad)混合

opad(outer pad)也是固定填充值,每个字节都是0x5C,长度同样和哈希块大小一致(这里是4字节,即0x5C5C5C5C)。 再次将准备好的密钥与opad进行按位异或运算,得到混合后的“外部密钥”。

第五步:第二次哈希计算

将混合后的外部密钥,与第三步得到的中间哈希值拼接,再用相同的哈希函数计算,最终得到的结果,就是HMAC认证码。

3. HMAC数学公式

用公式可以更简洁地表示整个计算过程(便于理解底层逻辑):

三、实战代码:JavaScript + Python 实现HMAC

理论懂了,实操才是关键。下面分别用JavaScript(Node.js环境)和Python实现HMAC,选用最常用的MD5和SHA256算法,代码可直接复制运行,结果完全一致。

1. JavaScript 实现(需借助crypto-js库)

JavaScript本身没有原生的HMAC实现,需要借助第三方库 crypto-js,适合前端、Node.js项目使用。

安装并引入crypto-js库

JavaScript本身并没有原生的HMAC实现,因此需要借助第三方库,如 crypto-js。

 npm install crypto-js --save
let CryptoJS = require('crypto-js');

准备明文和密钥

let text = 'admin'; // 原始消息(明文)
let key = '123';    // 共享密钥

注意:实际项目中密钥需保密,不要硬编码在前端

计算HMAC-MD5

可以使用 CryptoJS.HmacMD5() 方法来计算 HMAC-MD5 的结果:

let hmac_md5 = CryptoJS.HmacMD5(text, key);
console.log(hmac_md5);

此时输出的将是一个 WordArray 对象,而不是普通的字符串。这个对象是 crypto-js 用来表示二进制数据的结构。你可以通过 .toString() 方法将其转换为十六进制字符串,便于查看:

console.log('16进制格式:', hmac_md5.toString());
console.log('哈希字符串长度:', hmac_md5.toString().length);

完整代码如下所示:

// 1. 引入crypto-js库(Node.js环境先执行 npm install crypto-js)
let CryptoJS = require('crypto-js');

// 2. 准备明文和密钥(实际项目中密钥需保密,不要硬编码在前端)
let text = 'admin'; // 原始消息(明文)
let key = '123';    // 共享密钥

// 3. 计算HMAC-MD5(常用算法,输出32位十六进制字符串)
let hmac_md5 = CryptoJS.HmacMD5(text, key);
// 转换为十六进制字符串(默认是WordArray对象,需转义才能查看)
console.log('HMAC-MD5(十六进制):', hmac_md5.toString());
console.log('HMAC-MD5长度:', hmac_md5.toString().length); // 输出32

// 4. 计算HMAC-SHA256(更安全,输出64位十六进制字符串)
let hmac_sha256 = CryptoJS.HmacSHA256(text, key);
console.log('HMAC-SHA256(十六进制):', hmac_sha256.toString());
console.log('HMAC-SHA256长度:', hmac_sha256.toString().length); // 输出64

输出示例:

HMAC-MD5(十六进制): 59847108f5c2280c56d30b0991fd2dab
HMAC-MD5长度: 32
HMAC-SHA256(十六进制): f0662a977ea4e2df9622eda5fb3ef92c276e958dd8ac83edf1a076035a8eb957
HMAC-SHA256长度: 64

补充:实际应用中,验证HMAC的方式不是“解密”,而是接收方用相同的text和key,重新计算HMAC,对比两次结果是否一致——一致则说明消息未被篡改、来源合法。

2. Python 实现(原生hmac模块,无需第三方依赖)

引入内置模块

Python自带 hmac 标准库,无需额外安装,支持所有常用哈希算法,适合后端、爬虫、逆向场景使用。

首先,我们需要导入 hmac 模块,并确保输入数据是字节序列。

import hmac

# 准备明文和密钥
text = 'admin'.encode('utf-8')
key = '123'.encode('utf-8')

创建 HMAC 对象

使用 hmac.new() 方法创建一个 HMAC 对象,传入密钥、明文和哈希算法类型。这里我们使用 md5sha256 作为哈希算法。

hmac_result = hmac.new(key, text, digestmod='md5')

# 或者
import hashlib
hmac_result = hmac.new(key, text, digestmod=hashlib.md5)

获取哈希值

执行 HMAC 计算后,我们可以调用 .hexdigest() 方法将计算结果转换为十六进制字符串。

print(hmac_result.hexdigest())
#59847108f5c2280c56d30b0991fd2dab

输出结果与 JavaScript 中计算的 HMAC 一致

完整代码如下所示:

import hmac
import hashlib

# 1. 准备明文和密钥(注意:Python中需将字符串转为字节序列,用encode('utf-8'))
text = 'admin'.encode('utf-8')
key = '123'.encode('utf-8')

# 2. 计算HMAC-MD5
# digestmod指定哈希算法,可传入字符串(如'md5')或hashlib对象(如hashlib.md5)
hmac_md5 = hmac.new(key, text, digestmod='md5').hexdigest()
print('HMAC-MD5(十六进制):', hmac_md5)

# 3. 计算HMAC-SHA256
hmac_sha256 = hmac.new(key, text, digestmod=hashlib.sha256).hexdigest()
print('HMAC-SHA256(十六进制):', hmac_sha256)

四、常见HMAC算法类型及区别

HMAC的算法类型,本质是“哈希函数+密钥”的组合,常见的有3种,核心区别在于输出长度和安全性,具体如下:

  • HMAC-MD5:基于MD5哈希函数,输出128位哈希值,转换为十六进制后是32个字符;安全性一般,适合对安全性要求不高的场景。

  • HMAC-SHA1:基于SHA1哈希函数,输出160位哈希值,十六进制长度40个字符;安全性略高于MD5,目前仍有部分场景使用。

  • HMAC-SHA256:基于SHA256哈希函数,输出256位哈希值,十六进制长度64个字符;安全性较高,是目前最常用的HMAC算法,适合接口鉴权、数据加密校验等场景。

注意:HMAC的结果外观,和普通哈希(如MD5、SHA256)完全一样——区别只在于,HMAC必须使用密钥计算,而普通哈希不需要密钥,任何人都能计算。

五、HMAC与其他加密/哈希算法的区别

很多人会把HMAC和普通哈希、对称加密搞混,这里用通俗的语言讲清核心区别,避免踩坑:

1. HMAC vs MD5/SHA256(普通哈希)

  • 相同点:都是单向哈希,不可逆(无法通过哈希值反推原始消息);

  • 不同点:普通哈希无密钥,任何人都能计算;HMAC有密钥,只有持有密钥的人才能生成和验证,能防止伪造。

2. HMAC vs 对称/非对称加密(AES/RSA)

  • 相同点:都需要密钥;

  • 不同点:AES、RSA是“可逆”的(能加密、能解密),核心作用是加密消息;HMAC是“不可逆”的,核心作用是验证(不加密消息,只验证完整性和来源)。

3.所有不同加密类型区别

类型

可逆性

是否有密钥(个数)

密钥长度

核心作用

MD5

不可逆

无密钥

生成消息摘要,验证数据完整性(防篡改),不提供加密或身份认证

HAMC-MD5

不可逆

有密钥(1个)

无固定要求(建议>=16个字节)

带密钥的消息认证,验证数据完整性+来源真实性(防篡改+防伪造)

对称加密AES

可逆(同以密钥解密)

有密钥(1个)

一般为16或32个字节

可逆加密消息,保证数据机密性(只有持有密钥者才能解密读取原始内容)

非对称加密RSA

可逆(常见公钥解密、私钥解密)

有密钥对(2个)

128字节(已不安全)

256字节(主流)

512字节(高安全)

可逆加密/签名:公钥加密保证机密性,私钥签名验证身份与完整性;适合安全分发对称密钥或短数据

六、逆向工程视角:如何突破HMAC?

对于逆向工程师、爬虫开发者来说,经常会遇到接口用HMAC签名(比如请求头中的sign字段),此时很多人会想“破解HMAC”,但其实方向错了——HMAC的核心是密钥,哈希函数本身几乎无法破解。

核心思路:不攻哈希,只找密钥

HMAC的安全性依赖于密钥的保密性,只要找到密钥,就能模拟计算出正确的HMAC值,常见的密钥来源的场景:

  • 密钥硬编码在前端JS、APP代码中(最常见,可通过调试代码、反编译找到);

  • 密钥通过特定算法动态生成(比如结合时间戳、设备ID,需回溯代码找到生成逻辑);

  • 密钥存储在本地存储(如localStorage、APP配置文件)、内存中(可通过调试工具抓取)。

实战步骤

  1. 抓包:找到带有HMAC签名的请求(通常是signhmac等字段);

  2. 定位:调试前端JS或反编译APP,找到HMAC的计算代码(比如搜索“HmacMD5”“HmacSHA256”);

  3. 回溯:从计算代码向上追溯,找到密钥(key)的定义或生成逻辑;

  4. 模拟:用找到的密钥,结合相同的哈希算法,模拟计算HMAC,即可通过接口验证。

七、总结

HMAC本质上是“哈希+密钥”的组合,核心价值是验证消息完整性和来源合法性,没有加密功能,不可逆,是接口鉴权、数据校验的常用技术。

记住3个核心点,就能吃透HMAC:

  • 原理:密钥准备+两次哈希+双重填充(ipad/opad);

  • 实操:JS用crypto-js,Python用hmac模块,参数一致则结果一致;

  • 逆向:不破解哈希,重点找密钥的存储和生成逻辑。

无论是后端开发接口鉴权,还是前端做数据校验,亦或是逆向突破接口签名,掌握HMAC的核心逻辑,都能轻松应对。