Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Go 语言标准库 —— crypto/hmac 包(密钥哈希消息认证码)


🔹 概述

crypto/hmac 包实现了密钥哈希消息认证码(HMAC),定义在 FIPS 198 标准中。HMAC 是一种使用密钥对消息进行签名的加密哈希函数。

主要功能:

  • HMAC 消息认证码生成
  • 消息完整性验证
  • 消息来源认证
  • 防篡改保护

重要说明:

  • HMAC 使用密钥进行哈希
  • 🔑 发送方和接收方共享同一密钥
  • 🔒 同时保证完整性和真实性
  • ⚡ 比数字签名更快
  • 🛡️ 防止长度扩展攻击

核心概念:

  • 密钥(Key) - 共享的 secret key
  • 消息(Message) - 要认证的数据
  • MAC 标签(MAC Tag) - HMAC 计算结果
  • 哈希函数(Hash) - SHA-256、SHA-512 等

工作流程:

  1. 发送方使用密钥计算消息的 HMAC
  2. 发送方发送消息和 HMAC 标签
  3. 接收方使用相同密钥重新计算 HMAC
  4. 接收方比较两个 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()创建 HMAChash.Hash✅ 必需
Equal()常量时间比较bool✅ 必须使用

哈希函数对比

哈希函数输出大小安全性性能推荐度
HMAC-MD516 字节❌ 已破解❌ 不推荐
HMAC-SHA120 字节⚠️ 已弃用❌ 不推荐
HMAC-SHA25632 字节✅ 高很快✅ 强烈推荐
HMAC-SHA51264 字节✅ 极高✅ 推荐

主要特点

  • 密钥认证 👉 使用共享密钥进行认证
  • 完整性保护 👉 检测消息篡改
  • 来源认证 👉 验证消息来源
  • 防长度扩展 👉 免疫长度扩展攻击
  • 高性能 👉 比数字签名快

使用场景

  • 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!