Go 语言标准库 —— crypto/hmac 包(密钥哈希消息认证码)
🔹 概述
crypto/hmac 包实现了密钥哈希消息认证码(HMAC),定义在 FIPS 198 标准中。HMAC 是一种使用密钥对消息进行签名的加密哈希函数。
主要功能:
- HMAC 消息认证码生成
- 消息完整性验证
- 消息来源认证
- 防篡改保护
重要说明:
- ✅ HMAC 使用密钥进行哈希
- 🔑 发送方和接收方共享同一密钥
- 🔒 同时保证完整性和真实性
- ⚡ 比数字签名更快
- 🛡️ 防止长度扩展攻击
核心概念:
- 密钥(Key) - 共享的 secret key
- 消息(Message) - 要认证的数据
- MAC 标签(MAC Tag) - HMAC 计算结果
- 哈希函数(Hash) - SHA-256、SHA-512 等
工作流程:
- 发送方使用密钥计算消息的 HMAC
- 发送方发送消息和 HMAC 标签
- 接收方使用相同密钥重新计算 HMAC
- 接收方比较两个 HMAC 标签是否相同
🔹 核心函数
New - 创建 HMAC
hmac.New(h func() hash.Hash, key []byte) hash.Hash
-
说明:
- 创建新的 HMAC 哈希对象
- 实现
hash.Hash接口 - 可重复使用
-
参数:
h func() hash.Hash- 哈希函数构造器(如 sha256.New)key []byte- 密钥(共享密钥)
-
返回值:
hash.Hash- HMAC 哈希对象
-
示例(基本使用):
package main import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "fmt" ) func main() { // 密钥 key := []byte("secret-key") // 消息 message := []byte("Hello, HMAC!") // 创建 HMAC h := hmac.New(sha256.New, key) h.Write(message) // 获取 HMAC 标签 mac := h.Sum(nil) fmt.Printf("HMAC: %s\n", hex.EncodeToString(mac)) fmt.Printf("长度:%d 字节\n", len(mac)) } -
注意事项:
- ✅ 密钥长度应足够(至少 256 位)
- ✅ 使用安全的哈希函数(SHA-256、SHA-512)
- ⚠️ 不要使用 MD5、SHA-1
Equal - 常量时间比较
hmac.Equal(mac1, mac2 []byte) bool
-
说明:
- 常量时间比较两个 MAC 标签
- 防止时序攻击
- ✅ 必须使用此函数比较 MAC
-
参数:
mac1 []byte- 第一个 MAC 标签mac2 []byte- 第二个 MAC 标签
-
返回值:
bool- 是否相等
-
示例:
package main import ( "crypto/hmac" "crypto/sha256" "fmt" ) func main() { key := []byte("secret-key") message := []byte("Hello!") // 计算 HMAC h1 := hmac.New(sha256.New, key) h1.Write(message) mac1 := h1.Sum(nil) h2 := hmac.New(sha256.New, key) h2.Write(message) mac2 := h2.Sum(nil) // 常量时间比较 if hmac.Equal(mac1, mac2) { fmt.Println("HMAC 验证通过") } else { fmt.Println("HMAC 验证失败") } // 错误示例 - 不要使用 == 或 bytes.Equal // if mac1 == mac2 { } // 错误! // if bytes.Equal(mac1, mac2) { } // 错误! } -
注意事项:
- ✅ 必须使用 Equal 比较 MAC
- ❌ 不要使用
==或bytes.Equal() - 🔒 防止时序攻击
🔹 完整示例
1. 基本 HMAC 生成和验证
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
)
// GenerateHMAC 生成消息的 HMAC
func GenerateHMAC(message, key []byte) []byte {
h := hmac.New(sha256.New, key)
h.Write(message)
return h.Sum(nil)
}
// VerifyHMAC 验证 HMAC 是否有效
func VerifyHMAC(message, expectedMAC, key []byte) bool {
actualMAC := GenerateHMAC(message, key)
return hmac.Equal(actualMAC, expectedMAC)
}
func main() {
// 密钥
key := []byte("my-secret-key-12345678901234567890")
// 消息
message := []byte("This is a test message")
// 生成 HMAC
mac := GenerateHMAC(message, key)
fmt.Printf("消息:%s\n", string(message))
fmt.Printf("HMAC: %s\n", hex.EncodeToString(mac))
// 验证 HMAC
valid := VerifyHMAC(message, mac, key)
fmt.Printf("验证结果:%v\n", valid)
// 篡改消息
tamperedMessage := []byte("Tampered message")
valid = VerifyHMAC(tamperedMessage, mac, key)
fmt.Printf("篡改后验证:%v\n", valid)
}
2. 使用不同哈希函数
package main
import (
"crypto/hmac"
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"encoding/hex"
"fmt"
)
func generateHMAC(message, key []byte, hashFunc string) []byte {
var h hmac.Hash
switch hashFunc {
case "MD5":
h = hmac.New(md5.New, key)
case "SHA1":
h = hmac.New(sha1.New, key)
case "SHA256":
h = hmac.New(sha256.New, key)
case "SHA512":
h = hmac.New(sha512.New, key)
default:
h = hmac.New(sha256.New, key)
}
h.Write(message)
return h.Sum(nil)
}
func main() {
key := []byte("secret-key")
message := []byte("Hello, HMAC!")
fmt.Println("不同哈希函数的 HMAC 对比")
fmt.Println("========================")
// MD5(不推荐)
mac := generateHMAC(message, key, "MD5")
fmt.Printf("HMAC-MD5: %s (%d 字节)\n", hex.EncodeToString(mac), len(mac))
// SHA-1(不推荐)
mac = generateHMAC(message, key, "SHA1")
fmt.Printf("HMAC-SHA1: %s (%d 字节)\n", hex.EncodeToString(mac), len(mac))
// SHA-256(推荐)
mac = generateHMAC(message, key, "SHA256")
fmt.Printf("HMAC-SHA256: %s (%d 字节)\n", hex.EncodeToString(mac), len(mac))
// SHA-512(推荐)
mac = generateHMAC(message, key, "SHA512")
fmt.Printf("HMAC-SHA512: %s (%d 字节)\n", hex.EncodeToString(mac), len(mac))
}
3. API 请求签名验证
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"fmt"
"net/http"
"strings"
"time"
)
type APIServer struct {
secretKey []byte
}
func NewAPIServer(secretKey string) *APIServer {
return &APIServer{
secretKey: []byte(secretKey),
}
}
// GenerateSignature 生成请求签名
func (s *APIServer) GenerateSignature(method, path, body string, timestamp int64) string {
// 构造签名消息
message := fmt.Sprintf("%s\n%s\n%d\n%s", method, path, timestamp, body)
// 计算 HMAC
h := hmac.New(sha256.New, s.secretKey)
h.Write([]byte(message))
signature := h.Sum(nil)
return base64.StdEncoding.EncodeToString(signature)
}
// VerifyRequest 验证请求签名
func (s *APIServer) VerifyRequest(r *http.Request) bool {
// 获取签名头
signature := r.Header.Get("X-Signature")
if signature == "" {
return false
}
// 获取时间戳
timestampStr := r.Header.Get("X-Timestamp")
if timestampStr == "" {
return false
}
// 验证时间戳(防止重放攻击)
timestamp, _ := strconv.ParseInt(timestampStr, 10, 64)
if time.Now().Unix()-timestamp > 300 { // 5 分钟有效期
fmt.Println("请求已过期")
return false
}
// 读取请求体
body := make([]byte, r.ContentLength)
r.Body.Read(body)
// 重新计算签名
expectedSignature := s.GenerateSignature(r.Method, r.URL.Path, string(body), timestamp)
// 常量时间比较
decodedSignature, _ := base64.StdEncoding.DecodeString(signature)
expectedBytes, _ := base64.StdEncoding.DecodeString(expectedSignature)
return hmac.Equal(decodedSignature, expectedBytes)
}
func main() {
server := NewAPIServer("my-super-secret-key")
// 客户端生成签名
method := "POST"
path := "/api/data"
body := `{"name": "test"}`
timestamp := time.Now().Unix()
signature := server.GenerateSignature(method, path, body, timestamp)
fmt.Printf("签名:%s\n", signature)
// 模拟请求
req, _ := http.NewRequest(method, path, strings.NewReader(body))
req.Header.Set("X-Signature", signature)
req.Header.Set("X-Timestamp", fmt.Sprintf("%d", timestamp))
// 服务器验证
valid := server.VerifyRequest(req)
fmt.Printf("验证结果:%v\n", valid)
}
4. 文件完整性验证
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"os"
)
// ComputeFileHMAC 计算文件的 HMAC
func ComputeFileHMAC(filename string, key []byte) ([]byte, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
h := hmac.New(sha256.New, key)
if _, err := io.Copy(h, file); err != nil {
return nil, err
}
return h.Sum(nil), nil
}
// VerifyFileIntegrity 验证文件完整性
func VerifyFileIntegrity(filename string, expectedMAC []byte, key []byte) bool {
actualMAC, err := ComputeFileHMAC(filename, key)
if err != nil {
fmt.Println("计算 HMAC 失败:", err)
return false
}
return hmac.Equal(actualMAC, expectedMAC)
}
// SaveFileHMAC 保存文件 HMAC 到文件
func SaveFileHMAC(filename, macFilename string, key []byte) error {
mac, err := ComputeFileHMAC(filename, key)
if err != nil {
return err
}
return os.WriteFile(macFilename, []byte(hex.EncodeToString(mac)), 0644)
}
// LoadFileHMAC 从文件加载 HMAC
func LoadFileHMAC(macFilename string) ([]byte, error) {
data, err := os.ReadFile(macFilename)
if err != nil {
return nil, err
}
mac, err := hex.DecodeString(string(data))
if err != nil {
return nil, err
}
return mac, nil
}
func main() {
// 密钥
key := []byte("file-integrity-key-1234567890123456")
// 创建测试文件
testFile := "test.txt"
macFile := "test.txt.hmac"
os.WriteFile(testFile, []byte("This is test file content"), 0644)
// 计算并保存 HMAC
err := SaveFileHMAC(testFile, macFile, key)
if err != nil {
fmt.Println("保存 HMAC 失败:", err)
return
}
fmt.Println("HMAC 已保存")
// 加载 HMAC
expectedMAC, _ := LoadFileHMAC(macFile)
fmt.Printf("期望 HMAC: %x\n", expectedMAC)
// 验证文件完整性
valid := VerifyFileIntegrity(testFile, expectedMAC, key)
fmt.Printf("完整性验证:%v\n", valid)
// 篡改文件
os.WriteFile(testFile, []byte("Tampered content"), 0644)
valid = VerifyFileIntegrity(testFile, expectedMAC, key)
fmt.Printf("篡改后验证:%v\n", valid)
}
5. JWT 风格的 HMAC 签名
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"strings"
"time"
)
type Claims struct {
UserID string `json:"user_id"`
Username string `json:"username"`
Exp int64 `json:"exp"`
Iat int64 `json:"iat"`
}
// Base64URL 编码
func base64URLEncode(data []byte) string {
return strings.TrimRight(base64.URLEncoding.EncodeToString(data), "=")
}
// GenerateToken 生成 JWT 风格的令牌
func GenerateToken(secretKey []byte, claims Claims) (string, error) {
// 设置时间戳
claims.Iat = time.Now().Unix()
if claims.Exp == 0 {
claims.Exp = claims.Iat + 3600 // 默认 1 小时有效期
}
// 编码 header
header := map[string]string{
"alg": "HS256",
"typ": "JWT",
}
headerJSON, _ := json.Marshal(header)
headerEncoded := base64URLEncode(headerJSON)
// 编码 payload
claimsJSON, _ := json.Marshal(claims)
payloadEncoded := base64URLEncode(claimsJSON)
// 构造签名消息
message := headerEncoded + "." + payloadEncoded
// 计算 HMAC
h := hmac.New(sha256.New, secretKey)
h.Write([]byte(message))
signature := h.Sum(nil)
signatureEncoded := base64URLEncode(signature)
// 返回令牌
return message + "." + signatureEncoded, nil
}
// VerifyToken 验证令牌
func VerifyToken(secretKey []byte, token string) (*Claims, error) {
parts := strings.Split(token, ".")
if len(parts) != 3 {
return nil, fmt.Errorf("无效的令牌格式")
}
headerEncoded := parts[0]
payloadEncoded := parts[1]
signatureEncoded := parts[2]
// 验证签名
message := headerEncoded + "." + payloadEncoded
h := hmac.New(sha256.New, secretKey)
h.Write([]byte(message))
expectedSignature := h.Sum(nil)
actualSignature, _ := base64.RawURLEncoding.DecodeString(signatureEncoded)
expectedSignatureEncoded := base64URLEncode(expectedSignature)
if !hmac.Equal([]byte(signatureEncoded), []byte(expectedSignatureEncoded)) {
return nil, fmt.Errorf("签名验证失败")
}
// 解码 payload
payloadJSON, _ := base64.RawURLEncoding.DecodeString(payloadEncoded)
var claims Claims
json.Unmarshal(payloadJSON, &claims)
// 检查过期
if time.Now().Unix() > claims.Exp {
return nil, fmt.Errorf("令牌已过期")
}
return &claims, nil
}
func main() {
secretKey := []byte("super-secret-key-for-jwt-signing")
// 生成令牌
claims := Claims{
UserID: "12345",
Username: "john_doe",
}
token, _ := GenerateToken(secretKey, claims)
fmt.Printf("令牌:%s\n", token)
// 验证令牌
verifiedClaims, err := VerifyToken(secretKey, token)
if err != nil {
fmt.Println("验证失败:", err)
return
}
fmt.Printf("验证成功\n")
fmt.Printf("用户 ID: %s\n", verifiedClaims.UserID)
fmt.Printf("用户名:%s\n", verifiedClaims.Username)
fmt.Printf("过期时间:%d\n", verifiedClaims.Exp)
}
6. 流式 HMAC 计算
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"strings"
)
func main() {
key := []byte("streaming-key")
// 创建 HMAC
h := hmac.New(sha256.New, key)
// 流式写入数据
data1 := "First chunk of data\n"
data2 := "Second chunk of data\n"
data3 := "Third chunk of data\n"
h.Write([]byte(data1))
h.Write([]byte(data2))
h.Write([]byte(data3))
// 或使用 io.WriteString
io.WriteString(h, "Additional data\n")
// 获取 HMAC
mac := h.Sum(nil)
fmt.Printf("流式 HMAC: %s\n", hex.EncodeToString(mac))
// 验证:重新计算
h2 := hmac.New(sha256.New, key)
message := data1 + data2 + data3 + "Additional data\n"
h2.Write([]byte(message))
mac2 := h2.Sum(nil)
fmt.Printf("验证结果:%v\n", hmac.Equal(mac, mac2))
// 使用 io.MultiReader
h3 := hmac.New(sha256.New, key)
reader := io.MultiReader(
strings.NewReader(data1),
strings.NewReader(data2),
strings.NewReader(data3),
)
io.Copy(h3, reader)
mac3 := h3.Sum(nil)
fmt.Printf("MultiReader HMAC: %s\n", hex.EncodeToString(mac3))
fmt.Printf("验证结果:%v\n", hmac.Equal(mac, mac3))
}
🔹 注意事项和最佳实践
1. 密钥管理
- ✅ 使用足够长的密钥(至少 256 位)
- ✅ 密钥应随机生成
- ✅ 安全存储密钥
- ❌ 不要硬编码密钥
// 正确 - 足够长的密钥
key := make([]byte, 32) // 256 位
rand.Read(key)
// 错误 - 密钥太短
key := []byte("short") // 不安全
// 错误 - 硬编码密钥
key := []byte("my-secret-key") // 不安全
2. 哈希函数选择
- ✅ SHA-256 - 推荐(256 位安全)
- ✅ SHA-512 - 高安全性(512 位安全)
- ❌ MD5 - 已破解,不安全
- ❌ SHA-1 - 已弃用
// 推荐
h := hmac.New(sha256.New, key)
// 高安全性
h := hmac.New(sha512.New, key)
// 不推荐
h := hmac.New(md5.New, key) // 不安全
3. MAC 比较
- ✅ 必须使用 hmac.Equal()
- ❌ 不要使用
==或bytes.Equal() - 🔒 防止时序攻击
// 正确
if hmac.Equal(mac1, mac2) {
// 验证通过
}
// 错误 - 时序攻击风险
if mac1 == mac2 { }
if bytes.Equal(mac1, mac2) { }
4. 密钥派生
- ✅ 从密码派生密钥(PBKDF2、bcrypt)
- ✅ 使用足够的迭代次数
- ✅ 使用随机盐
// 从密码派生密钥
func deriveKey(password string, salt []byte) []byte {
return pbkdf2.Key([]byte(password), salt, 100000, 32, sha256.New)
}
// 使用派生的密钥
key := deriveKey("user-password", salt)
h := hmac.New(sha256.New, key)
5. 防止重放攻击
- ✅ 添加时间戳
- ✅ 验证时间戳有效期
- ✅ 使用 nonce
// 添加时间戳
timestamp := time.Now().Unix()
message := fmt.Sprintf("%d:%s", timestamp, data)
// 验证时间戳
if time.Now().Unix()-timestamp > 300 {
// 请求过期(5 分钟)
return false
}
6. 错误处理
- ✅ 检查所有错误
- ✅ 不泄露敏感信息
- ✅ 统一的错误响应
mac, err := ComputeHMAC(message, key)
if err != nil {
// 不泄露具体错误
return nil, fmt.Errorf("HMAC 计算失败")
}
if !hmac.Equal(mac, expectedMAC) {
// 统一的错误响应
return fmt.Errorf("验证失败")
}
🔥 总结
核心函数
| 函数 | 说明 | 返回值 | 推荐度 |
|---|---|---|---|
| New() | 创建 HMAC | hash.Hash | ✅ 必需 |
| Equal() | 常量时间比较 | bool | ✅ 必须使用 |
哈希函数对比
| 哈希函数 | 输出大小 | 安全性 | 性能 | 推荐度 |
|---|---|---|---|---|
| HMAC-MD5 | 16 字节 | ❌ 已破解 | 快 | ❌ 不推荐 |
| HMAC-SHA1 | 20 字节 | ⚠️ 已弃用 | 快 | ❌ 不推荐 |
| HMAC-SHA256 | 32 字节 | ✅ 高 | 很快 | ✅ 强烈推荐 |
| HMAC-SHA512 | 64 字节 | ✅ 极高 | 快 | ✅ 推荐 |
主要特点
- 密钥认证 👉 使用共享密钥进行认证
- 完整性保护 👉 检测消息篡改
- 来源认证 👉 验证消息来源
- 防长度扩展 👉 免疫长度扩展攻击
- 高性能 👉 比数字签名快
使用场景
- API 认证 👉 请求签名验证
- 消息认证 👉 消息完整性验证
- 会话令牌 👉 JWT 签名
- 文件完整性 👉 文件防篡改
- Webhook 验证 👉 GitHub、Stripe 等
最佳实践
- ✅ 使用 SHA-256 或 SHA-512
- ✅ 使用足够长的密钥(至少 256 位)
- ✅ 使用 hmac.Equal() 比较 MAC
- ✅ 从密码派生密钥(PBKDF2)
- ✅ 添加时间戳防止重放攻击
- ✅ 安全存储和管理密钥
- ⚠️ 定期轮换密钥
- ⚠️ 记录 HMAC 验证日志
安全建议
- 🔒 使用至少 256 位密钥
- 🔒 实施密钥轮换策略
- 🔒 记录验证失败日志
- 🔒 进行安全审计
- 🔒 防止时序攻击
🔹 与相关技术对比
| 技术 | 用途 | 密钥 | 性能 | 使用场景 |
|---|---|---|---|---|
| HMAC | 消息认证 | 对称密钥 | 很快 | API 认证、完整性 |
| 数字签名 | 消息认证 + 不可否认 | 非对称密钥 | 慢 | 证书、合同 |
| 简单哈希 | 完整性 | 无密钥 | 很快 | 校验和 |
| 加密 | 机密性 | 对称/非对称 | 中等 | 数据加密 |
crypto/hmac 包提供了标准的 HMAC 实现,请使用 SHA-256 并始终使用 Equal() 比较 MAC!