如何生成 HMAC 消息认证码
什么是 HMAC
HMAC(Hash-based Message Authentication Code,基于哈希的消息认证码)是一种将密码学哈希函数与密钥结合使用的认证方式。与普通哈希不同,HMAC 需要一个共享密钥,只有知道密钥的人才能生成或验证 HMAC。HMAC 同时提供两个保证:消息完整性(数据未被修改)和消息真实性(数据确实来自持有密钥的人)。
/* HMAC formula */
HMAC(key, message) = Hash(
(key XOR opad) ||
Hash((key XOR ipad) || message)
)
/* Where:
opad = 0x5c repeated
ipad = 0x36 repeated
|| = concatenation */
HMAC 与普通哈希的核心区别
普通哈希(如 SHA256)是公开可计算的——任何人都能计算任意数据的哈希。这意味着攻击者可以修改数据后重新计算哈希来伪造完整性证明。HMAC 解决了这个问题:没有密钥就无法计算正确的 HMAC,即使攻击者知道数据和 HMAC 算法,也无法在不知道密钥的情况下伪造 HMAC。
在各编程语言中实现 HMAC-SHA256
// JavaScript (Node.js)
const crypto = require('crypto');
function generateHmac(secret, message) {
return crypto
.createHmac('sha256', secret)
.update(message)
.digest('hex');
}
const signature = generateHmac('my-secret-key', 'Hello World');
// Output: 'f59c9c0b1234567...' (64-char hex)
// Verify
function verifyHmac(secret, message, receivedHmac) {
const expected = generateHmac(secret, message);
// Use timingSafeEqual to prevent timing attacks!
return crypto.timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(receivedHmac, 'hex')
);
}
# Python
import hmac, hashlib
def generate_hmac(secret: str, message: str) -> str:
key = secret.encode('utf-8')
msg = message.encode('utf-8')
return hmac.new(key, msg, hashlib.sha256).hexdigest()
def verify_hmac(secret: str, message: str, received: str) -> bool:
expected = generate_hmac(secret, message)
# Use compare_digest to prevent timing attacks!
return hmac.compare_digest(expected, received)
signature = generate_hmac('my-secret-key', 'Hello World')
HMAC 在 API 签名中的应用
API 签名是 HMAC 最常见的应用场景之一。以下是一个典型的 HMAC-based API 签名流程(参考 AWS Signature Version 4 的简化版):
// API signing example
function signRequest(secretKey, method, path, body, timestamp) {
// 1. Create string to sign
const bodyHash = crypto.createHash('sha256')
.update(body).digest('hex');
const stringToSign = [
method.toUpperCase(),
path,
timestamp,
bodyHash
].join('\n');
// 2. Generate HMAC signature
const signature = crypto
.createHmac('sha256', secretKey)
.update(stringToSign)
.digest('hex');
return signature;
}
// Server verification
const expectedSig = signRequest(serverSecret, method, path, body, timestamp);
if (crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSig))) {
// Request is authentic
}
JWT 中的 HMAC-SHA256 (HS256)
JSON Web Token(JWT)的 HS256 算法就是 HMAC-SHA256。JWT 的结构是 header.payload.signature,其中 signature 是用密钥对 header.payload 进行 HMAC-SHA256 的结果(Base64URL 编码)。持有密钥的服务器可以验证 JWT 是否被篡改,客户端无法伪造有效的 JWT(除非知道密钥)。
// JWT HS256 signature (simplified)
const header = Base64URL(JSON.stringify({ alg: "HS256", typ: "JWT" }));
const payload = Base64URL(JSON.stringify({ sub: "user123", exp: 1720000000 }));
const sigInput = `${header}.${payload}`;
const signature = Base64URL(
crypto.createHmac('sha256', jwtSecret).update(sigInput).digest()
);
const jwt = `${header}.${payload}.${signature}`;
HMAC vs 数字签名(RSA/ECDSA)
HMAC 和数字签名(如 RSA-SHA256、ECDSA)都能保证消息真实性,但有一个关键区别:HMAC 使用对称密钥(相同密钥用于生成和验证,通信双方共享同一密钥),因此只适合双方互信的场景。数字签名使用非对称密钥(私钥签名,公钥验证),任何人都可以用公钥验证签名,但只有私钥持有者能生成签名,适合公开验证的场景(如软件包签名、证书)。
HMAC 安全最佳实践
- 使用 HMAC-SHA256 或 HMAC-SHA512,避免使用 HMAC-MD5 和 HMAC-SHA1
- 密钥长度应与哈希输出长度相同或更长(HMAC-SHA256 使用 32 字节或更长的密钥)
- 验证时始终使用时序安全比较(如
crypto.timingSafeEqual),防止时序攻击 - 在 API 签名中加入时间戳和随机数,防止重放攻击
- 密钥应安全存储(环境变量、密钥管理服务),绝不硬编码在代码中
立即免费使用相关工具
免费使用 →