文章目录
- 前言
- [一、什么是 HMAC?](#一、什么是 HMAC?)
- [二、HMAC 的数学构造与工作流程](#二、HMAC 的数学构造与工作流程)
- [三、为什么 HMAC 安全?](#三、为什么 HMAC 安全?)
- 四、常见用法场景
- 五、示例
-
- [示例 1 --- Python(标准库)](#示例 1 — Python(标准库))
- [示例 2 --- Java(使用 javax.crypto)](#示例 2 — Java(使用 javax.crypto))
- [示例 3 --- Node.js(crypto)](#示例 3 — Node.js(crypto))
- 总结
前言
随着网络服务与分布式系统的广泛应用,服务之间交换的数据需要保证完整性 和认证。HMAC(Hash-based Message Authentication Code,基于哈希的消息认证码)在实际工程中被大量使用来验证消息未被篡改并确认消息来自持有密钥的一方。
一、什么是 HMAC?
HMAC 是一种基于公认哈希函数(如 SHA-256、SHA-1、SHA-512)的消息认证码(MAC)。其目标是通过一个对称密钥对任意长度的消息生成固定长度的标签(MAC),接收方使用相同密钥重新计算 MAC 并比较,从而确认消息完整性与来源。
关键点:
- 提供认证 与完整性 ,但不提供保密。
- 安全性依赖于底层哈希函数的碰撞/伪随机性质和密钥的秘密性与长度。
- 广泛用于 API 签名、TLS 中的某些模式、JWT 的签名(HMAC-SHA256)等。
二、HMAC 的数学构造与工作流程
HMAC 的标准构造(伪代码):
HMAC(K, m) = H( (K' ⊕ opad) || H( (K' ⊕ ipad) || m ) )
其中:
H是底层哈希函数(如SHA-256)。K是原始密钥。K'是被处理后的密钥:如果len(K) > block_size,则K' = H(K);否则K' = K填充到block_size(用 0x00 填充)。block_size是哈希函数的块大小(对于 SHA-256 为 64 字节)。ipad= 0x36 重复block_size次,opad= 0x5c 重复block_size次。||表示连接(concatenate)。⊕表示按字节异或(XOR)。
流程分两步哈希(inner,outer):
- inner = H( (K' XOR ipad) || message )
- outer = H( (K' XOR opad) || inner )
返回 outer 作为最终 MAC。
这样做的设计目的包括:利用哈希函数的压缩性质,同时避免对密钥直接暴露给哈希内部结构,增加对密钥长度变化与扩展攻击的抵抗。
三、为什么 HMAC 安全?
- 安全性来源:假设底层哈希函数是伪随机函数/抗碰撞的,HMAC 的安全性可以证明为难以伪造 MAC 的。
- 密钥长度:密钥应至少与哈希输出长度相当(例如对 SHA-256 至少 256 位),并应由高熵随机源生成。过短或易猜测的密钥会削弱安全性。
- 不要把 HMAC 当成加密:HMAC 不隐藏消息内容,仅验证。若需要保密,请同时使用加密(如 AES-GCM)。
- 比较时防止时序攻击 :比较 MAC 时应使用常数时间比较(例如 Python 的
hmac.compare_digest),不要使用普通==。 - 选择合适哈希 :避免使用已知弱点的哈希(例如 SHA-1 的碰撞问题),优先使用
SHA-256、SHA-384、SHA-512等。 - 密钥管理:密钥应定期轮换并安全存储(例如 KMS、硬件 HSM)。不要把密钥硬编码在源码或日志中。
- 消息边界与格式:签名数据格式必须严格定义(例如包含时间戳、请求方法、URL、Body 等),否则存在签名绕过风险。
四、常见用法场景
- API 请求签名(客户端用密钥签名请求,服务端验证)
- Cookie/Session 防篡改(签名 Cookie 内容)
- 简单消息完整性检验(不保密)
- 与 TLS/SSL 内部构造结合用于 MAC(历史上用到 HMAC)
五、示例
下面给出 Python、Java、Node.js 的 HMAC 使用示例,随后提供一个完整实战:使用 HMAC 对 Flask API 请求进行签名与验证。
示例 1 --- Python(标准库)
要点说明:使用
hmac与hashlib,并用hmac.compare_digest做常数时间比较。
python
# hmac_example.py
import hmac
import hashlib
import base64
def hmac_sha256_base64(key: bytes, message: bytes) -> str:
mac = hmac.new(key, message, hashlib.sha256).digest()
return base64.b64encode(mac).decode('ascii')
if __name__ == "__main__":
key = b'supersecretkey123456' # 实际使用中应更长、更随机
message = b'important message'
signature_b64 = hmac_sha256_base64(key, message)
print("Base64 HMAC-SHA256:", signature_b64)
解释:
hmac.new(key, message, hashlib.sha256).digest()计算原始 MAC(二进制)。- 结果用 Base64 编码便于放入 HTTP 头或 URL 中。
比较示例(验证):
python
def verify_hmac(key: bytes, message: bytes, signature_b64: str) -> bool:
expected = base64.b64encode(hmac.new(key, message, hashlib.sha256).digest()).decode('ascii')
# 使用常数时间比较避免时序攻击
return hmac.compare_digest(expected, signature_b64)
示例 2 --- Java(使用 javax.crypto)
java
// HmacExample.java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
public class HmacExample {
public static String hmacSha256Base64(String key, String message) throws Exception {
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
sha256_HMAC.init(secret_key);
byte[] mac_data = sha256_HMAC.doFinal(message.getBytes("UTF-8"));
return Base64.getEncoder().encodeToString(mac_data);
}
public static void main(String[] args) throws Exception {
String key = "supersecretkey123456";
String msg = "important message";
System.out.println(hmacSha256Base64(key, msg));
}
}
注意 :Java 的 Mac 已在底层实现了 HMAC 的标准构造。
示例 3 --- Node.js(crypto)
javascript
// hmac_example.js
const crypto = require('crypto');
function hmacSha256Base64(key, message) {
return crypto.createHmac('sha256', key).update(message).digest('base64');
}
const key = 'supersecretkey123456';
const message = 'important message';
console.log(hmacSha256Base64(key, message));
总结
HMAC 是一种简单、实用且经证明安全的消息认证机制,适用于绝大多数需要消息完整性与认证的场景。正确使用 HMAC 的要点包括选择合适的哈希函数与密钥长度、使用常数时间比较防止时序攻击、在签名中包含时间戳或 nonce 防止重放,并严格一致地定义被签名的数据格式。