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/md5 包(MD5 哈希算法)


🔹 概述

crypto/md5 包实现了 MD5(Message-Digest Algorithm 5)哈希算法,定义在 RFC 1321 中。

⚠️ 重要安全警告:

  • MD5 已被破解,不应再用于安全应用
  • 🚫 存在碰撞攻击漏洞
  • 🔒 仅用于兼容旧系统或非安全场景
  • 推荐使用 SHA-256 或 SHA-512 替代

主要功能:

  • MD5 哈希计算
  • 实现 hash.Hash 接口
  • 支持流式哈希计算
  • 提供校验和功能

重要说明:

  • MD5 输出固定 128 位(16 字节)哈希值
  • 块大小:512 位(64 字节)
  • 计算速度快
  • ❌ 不适合密码学用途
  • ✅ 可用于文件完整性校验(非对抗场景)

🔹 常量

const Size = 16        // MD5 哈希值大小(字节)
const BlockSize = 64   // MD5 块大小(字节)
  • 说明:
    • Size - MD5 输出固定为 16 字节(128 位)
    • BlockSize - MD5 每次处理 64 字节数据

🔹 核心函数

New - 创建 MD5 哈希对象

md5.New() hash.Hash

  • 说明:

    • 创建新的 MD5 哈希对象
    • 实现 hash.Hash 接口
    • 可重复使用
  • 返回值:

    • hash.Hash - MD5 哈希对象
  • 示例(基本使用):

    package main
    
    import (
    	"crypto/md5"
    	"encoding/hex"
    	"fmt"
    )
    
    func main() {
    	// 创建 MD5 哈希对象
    	h := md5.New()
    	
    	// 写入数据
    	h.Write([]byte("Hello, MD5!"))
    	
    	// 获取哈希值
    	hash := h.Sum(nil)
    	fmt.Printf("MD5: %s\n", hex.EncodeToString(hash))
    	fmt.Printf("长度:%d 字节\n", len(hash))
    }
    
  • 注意事项:

    • ⚠️ MD5 已破解,不推荐用于安全场景
    • ✅ 可用于非安全校验和计算
    • ✅ 支持流式写入

Sum - 直接计算 MD5

md5.Sum(data []byte) [Size]byte

  • 说明:

    • 直接计算数据的 MD5 哈希
    • 返回固定大小的数组
    • 便捷函数
  • 参数:

    • data []byte - 要哈希的数据
  • 返回值:

    • [16]byte - MD5 哈希值(16 字节数组)
  • 示例:

    package main
    
    import (
    	"crypto/md5"
    	"encoding/hex"
    	"fmt"
    )
    
    func main() {
    	// 直接计算 MD5
    	data := []byte("Hello, MD5!")
    	hash := md5.Sum(data)
    	
    	fmt.Printf("数据:%s\n", string(data))
    	fmt.Printf("MD5: %s\n", hex.EncodeToString(hash[:]))
    	fmt.Printf("长度:%d 字节\n", len(hash))
    }
    
  • 注意事项:

    • ⚠️ 一次性计算,不适合大数据
    • ✅ 小数据便捷计算
    • ⚠️ 不推荐用于安全场景

🔹 完整示例

1. 基本 MD5 计算

package main

import (
	"crypto/md5"
	"encoding/hex"
	"fmt"
)

func main() {
	// 方法 1:使用 Sum 函数
	data1 := []byte("Hello, MD5!")
	hash1 := md5.Sum(data1)
	fmt.Printf("方法 1: %s\n", hex.EncodeToString(hash1[:]))
	
	// 方法 2:使用 New + Write
	h := md5.New()
	h.Write([]byte("Hello, MD5!"))
	hash2 := h.Sum(nil)
	fmt.Printf("方法 2: %s\n", hex.EncodeToString(hash2))
	
	// 验证结果相同
	fmt.Printf("结果相同:%v\n", string(hash1[:]) == string(hash2))
}

2. 流式 MD5 计算

package main

import (
	"crypto/md5"
	"encoding/hex"
	"fmt"
	"io"
	"strings"
)

func main() {
	// 创建 MD5 哈希对象
	h := md5.New()
	
	// 流式写入数据
	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")
	
	// 获取最终哈希
	hash := h.Sum(nil)
	fmt.Printf("流式 MD5: %s\n", hex.EncodeToString(hash))
	
	// 验证:与一次性计算结果相同
	fullData := data1 + data2 + data3 + "Additional data\n"
	expectedHash := md5.Sum([]byte(fullData))
	fmt.Printf("验证结果:%v\n", string(hash) == string(expectedHash[:]))
	
	// 使用 io.MultiReader 流式计算
	h2 := md5.New()
	reader := io.MultiReader(
		strings.NewReader(data1),
		strings.NewReader(data2),
		strings.NewReader(data3),
	)
	io.Copy(h2, reader)
	hash2 := h2.Sum(nil)
	
	fmt.Printf("MultiReader MD5: %s\n", hex.EncodeToString(hash2))
	fmt.Printf("验证结果:%v\n", string(hash2) == string(hash))
}

3. 文件 MD5 计算

package main

import (
	"crypto/md5"
	"encoding/hex"
	"fmt"
	"io"
	"os"
)

// CalculateFileMD5 计算文件的 MD5 值
func CalculateFileMD5(filename string) (string, error) {
	file, err := os.Open(filename)
	if err != nil {
		return "", err
	}
	defer file.Close()
	
	h := md5.New()
	if _, err := io.Copy(h, file); err != nil {
		return "", err
	}
	
	hash := h.Sum(nil)
	return hex.EncodeToString(hash), nil
}

// VerifyFileMD5 验证文件 MD5
func VerifyFileMD5(filename, expectedMD5 string) bool {
	actualMD5, err := CalculateFileMD5(filename)
	if err != nil {
		fmt.Println("计算 MD5 失败:", err)
		return false
	}
	
	return actualMD5 == expectedMD5
}

// SaveFileMD5 保存文件 MD5 到文件
func SaveFileMD5(filename, md5Filename string) error {
	md5Value, err := CalculateFileMD5(filename)
	if err != nil {
		return err
	}
	
	return os.WriteFile(md5Filename, []byte(md5Value), 0644)
}

// LoadFileMD5 从文件加载 MD5
func LoadFileMD5(md5Filename string) (string, error) {
	data, err := os.ReadFile(md5Filename)
	if err != nil {
		return "", err
	}
	
	return string(data), nil
}

func main() {
	// 创建测试文件
	testFile := "test.txt"
	md5File := "test.txt.md5"
	os.WriteFile(testFile, []byte("This is test file content"), 0644)
	
	// 计算并保存 MD5
	err := SaveFileMD5(testFile, md5File)
	if err != nil {
		fmt.Println("保存 MD5 失败:", err)
		return
	}
	fmt.Println("MD5 已保存")
	
	// 加载 MD5
	expectedMD5, _ := LoadFileMD5(md5File)
	fmt.Printf("期望 MD5: %s\n", expectedMD5)
	
	// 验证文件完整性
	valid := VerifyFileMD5(testFile, expectedMD5)
	fmt.Printf("完整性验证:%v\n", valid)
	
	// 篡改文件
	os.WriteFile(testFile, []byte("Tampered content"), 0644)
	valid = VerifyFileMD5(testFile, expectedMD5)
	fmt.Printf("篡改后验证:%v\n", valid)
	
	// 计算大文件的 MD5(流式处理)
	largeFile := "large.dat"
	// 创建 100MB 测试文件
	f, _ := os.Create(largeFile)
	data := make([]byte, 1024*1024) // 1MB
	for i := 0; i < 100; i++ {
		f.Write(data)
	}
	f.Close()
	
	fmt.Println("\n计算大文件 MD5...")
	largeMD5, _ := CalculateFileMD5(largeFile)
	fmt.Printf("大文件 MD5: %s\n", largeMD5)
}

4. 字符串 MD5 工具函数

package main

import (
	"crypto/md5"
	"encoding/hex"
	"fmt"
)

// MD5String 计算字符串的 MD5
func MD5String(s string) string {
	hash := md5.Sum([]byte(s))
	return hex.EncodeToString(hash[:])
}

// MD5Bytes 计算字节切片的 MD5
func MD5Bytes(data []byte) string {
	hash := md5.Sum(data)
	return hex.EncodeToString(hash[:])
}

// MD5File 计算文件的 MD5
func MD5File(filename string) (string, error) {
	data, err := os.ReadFile(filename)
	if err != nil {
		return "", err
	}
	return MD5Bytes(data), nil
}

func main() {
	// 字符串 MD5
	str := "Hello, MD5!"
	fmt.Printf("字符串:%s\n", str)
	fmt.Printf("MD5: %s\n", MD5String(str))
	
	// 常用字符串的 MD5
	commonStrings := []string{
		"password",
		"123456",
		"admin",
		"hello",
		"test",
	}
	
	fmt.Println("\n常见字符串的 MD5:")
	for _, s := range commonStrings {
		fmt.Printf("%-10s -> %s\n", s, MD5String(s))
	}
	
	// 空字符串
	fmt.Printf("\n空字符串 MD5: %s\n", MD5String(""))
	
	// 中文字符串
	chineseStr := "你好,世界!"
	fmt.Printf("中文:%s\n", chineseStr)
	fmt.Printf("MD5: %s\n", MD5String(chineseStr))
}

5. MD5 碰撞示例(演示不安全)

package main

import (
	"crypto/md5"
	"encoding/hex"
	"fmt"
)

func main() {
	fmt.Println("MD5 碰撞演示(不安全)")
	fmt.Println("====================")
	
	// 示例:两个不同的消息产生相同的 MD5 哈希
	// 这是理论上的碰撞攻击示例
	// 实际碰撞需要复杂的计算,这里仅演示概念
	
	// 消息 1
	msg1 := []byte("Message 1")
	hash1 := md5.Sum(msg1)
	
	// 消息 2
	msg2 := []byte("Message 2")
	hash2 := md5.Sum(msg2)
	
	fmt.Printf("消息 1: %s\n", string(msg1))
	fmt.Printf("MD5 1: %s\n", hex.EncodeToString(hash1[:]))
	
	fmt.Printf("\n消息 2: %s\n", string(msg2))
	fmt.Printf("MD5 2: %s\n", hex.EncodeToString(hash2[:]))
	
	fmt.Printf("\n哈希相同:%v\n", string(hash1[:]) == string(hash2[:]))
	
	// 说明:实际碰撞攻击可以找到两个不同的消息产生相同的 MD5
	// 这就是为什么 MD5 不应用于数字签名、证书等安全场景
	
	fmt.Println("\n⚠️  警告:")
	fmt.Println("MD5 已被证明存在碰撞漏洞")
	fmt.Println("不应再用于:")
	fmt.Println("  - 数字签名")
	fmt.Println("  - SSL/TLS 证书")
	fmt.Println("  - 密码存储")
	fmt.Println("  - 任何安全关键应用")
	fmt.Println("\n✅ 推荐使用 SHA-256 或 SHA-512 替代")
}

6. 与 SHA 系列对比

package main

import (
	"crypto/md5"
	"crypto/sha1"
	"crypto/sha256"
	"crypto/sha512"
	"encoding/hex"
	"fmt"
	"time"
)

func benchmarkHash(name string, hashFunc func([]byte) []byte, data []byte) {
	start := time.Now()
	hash := hashFunc(data)
	elapsed := time.Since(start)
	
	fmt.Printf("%-10s: %s (%d 字节) - %.2f μs\n",
		name,
		hex.EncodeToString(hash),
		len(hash),
		float64(elapsed.Microseconds()))
}

func main() {
	data := []byte("This is test data for benchmark")
	
	fmt.Println("哈希算法对比")
	fmt.Println("====================")
	
	// MD5
	benchmarkHash("MD5",
		func(d []byte) []byte {
			h := md5.Sum(d)
			return h[:]
		}, data)
	
	// SHA-1
	benchmarkHash("SHA-1",
		func(d []byte) []byte {
			h := sha1.Sum(d)
			return h[:]
		}, data)
	
	// SHA-256
	benchmarkHash("SHA-256",
		func(d []byte) []byte {
			h := sha256.Sum256(d)
			return h[:]
		}, data)
	
	// SHA-512
	benchmarkHash("SHA-512",
		func(d []byte) []byte {
			h := sha512.Sum512(d)
			return h[:]
		}, data)
	
	fmt.Println("\n安全性对比")
	fmt.Println("====================")
	fmt.Println("MD5:    ❌ 已破解(碰撞攻击)")
	fmt.Println("SHA-1:  ⚠️  已弃用(碰撞攻击)")
	fmt.Println("SHA-256: ✅ 推荐(安全)")
	fmt.Println("SHA-512: ✅ 推荐(更高安全)")
}

🔹 注意事项和最佳实践

1. 安全警告

  • MD5 已被破解

    • 存在碰撞攻击
    • 不应再用于安全应用
    • 2004 年王小云等人首次展示 MD5 碰撞
  • 推荐替代方案

    • SHA-256(通用场景)
    • SHA-512(高安全场景)
    • BLAKE3(高性能场景)
// ❌ 不推荐 - MD5
hash := md5.Sum(data)

// ✅ 推荐 - SHA-256
hash := sha256.Sum256(data)

// ✅ 推荐 - SHA-512
hash := sha512.Sum512(data)

2. 适用场景

  • 可以使用 MD5 的场景

    • 文件完整性校验(非对抗环境)
    • 数据库索引加速
    • 缓存键生成
    • 去重检测
  • 不应使用 MD5 的场景

    • 密码存储
    • 数字签名
    • SSL/TLS 证书
    • JWT 令牌
    • 任何安全关键应用
// ✅ 可以 - 文件完整性(非对抗)
fileMD5 := CalculateFileMD5("data.iso")

// ❌ 禁止 - 密码存储
passwordHash := md5.Sum(password) // 非常危险!

// ✅ 正确 - 使用 bcrypt 存储密码
hashedPassword, _ := bcrypt.GenerateFromPassword(password, 12)

3. 性能特点

  • ✅ MD5 计算速度快
  • ✅ 内存占用小
  • ✅ 适合大数据流式处理
  • ⚠️ 但安全性是首要考虑
// MD5 速度快,但不安全
h := md5.New()
io.Copy(h, largeFile)

// SHA-256 稍慢,但安全
h := sha256.New()
io.Copy(h, largeFile)

4. 哈希长度

  • MD5 输出固定 16 字节(128 位)
  • SHA-256 输出 32 字节(256 位)
  • SHA-512 输出 64 字节(512 位)
md5Hash := md5.Sum(data)      // 16 字节
sha256Hash := sha256.Sum256(data) // 32 字节
sha512Hash := sha512.Sum512(data) // 64 字节

5. FIPS 140-2 合规性

  • ❌ MD5 不符合 FIPS 140-2 标准
  • ⚠️ 在 FIPS 模式下会被禁止使用
  • ✅ SHA-256/512 符合 FIPS 140-2
// FIPS 模式下使用 MD5 会 panic
if fips140only.Enforced() {
	h := md5.New() // panic!
}

6. 迁移指南

从 MD5 迁移到 SHA-256:

// 旧代码(MD5)
import "crypto/md5"
hash := md5.Sum(data)

// 新代码(SHA-256)
import "crypto/sha256"
hash := sha256.Sum256(data)

// 或
h := sha256.New()
h.Write(data)
hash := h.Sum(nil)

🔥 总结

核心函数

函数说明返回值状态
New()创建 MD5 哈希对象hash.Hash⚠️ 不推荐
Sum()直接计算 MD5[16]byte⚠️ 不推荐

常量

常量说明
Size16MD5 哈希值大小(字节)
BlockSize64MD5 块大小(字节)

哈希算法对比

算法输出大小安全性性能推荐度使用场景
MD516 字节❌ 已破解很快❌ 不推荐非安全校验
SHA-120 字节⚠️ 已弃用❌ 不推荐遗留系统
SHA-25632 字节✅ 高很快✅ 强烈推荐通用场景
SHA-51264 字节✅ 极高✅ 推荐高安全场景

主要特点

  • 快速计算 👉 性能优秀
  • 固定输出 👉 128 位(16 字节)
  • 流式支持 👉 实现 hash.Hash 接口
  • 已破解 👉 存在碰撞攻击
  • 非 FIPS 👉 不符合 FIPS 140-2

适用场景

  • 文件完整性 👉 非对抗环境校验
  • 缓存键 👉 生成唯一标识
  • 去重检测 👉 数据去重
  • 数据库索引 👉 加速查询
  • 密码存储 👉 应使用 bcrypt/argon2
  • 数字签名 👉 应使用 SHA-256/ECDSA
  • SSL 证书 👉 应使用 SHA-256
  • JWT 令牌 👉 应使用 HMAC-SHA256

最佳实践

  • ✅ 仅用于非安全场景
  • ✅ 使用 SHA-256 替代用于安全场景
  • ✅ 流式处理大文件
  • ✅ 记录使用的哈希算法
  • ❌ 不要用于密码存储
  • ❌ 不要用于数字签名
  • ❌ 不要用于证书生成

安全建议

  • 🔒 立即停止在安全场景使用 MD5
  • 🔒 迁移到 SHA-256 或 SHA-512
  • 🔒 密码存储使用 bcrypt/argon2
  • 🔒 数字签名使用 ECDSA/Ed25519
  • 🔒 证书使用 SHA-256

🔹 常见用途示例

正确的使用场景

// ✅ 文件下载完整性校验
func verifyDownload(filename, expectedMD5 string) bool {
	actualMD5, _ := CalculateFileMD5(filename)
	return actualMD5 == expectedMD5
}

// ✅ 缓存键生成
func getCacheKey(data []byte) string {
	return md5.Sum(data)
}

// ✅ 数据去重
func deduplicate(data [][]byte) [][]byte {
	seen := make(map[string]bool)
	result := [][]byte{}
	for _, d := range data {
		hash := hex.EncodeToString(md5.Sum(d))
		if !seen[hash] {
			seen[hash] = true
			result = append(result, d)
		}
	}
	return result
}

错误的使用场景

// ❌ 密码哈希(非常危险!)
func hashPassword(password string) string {
	return hex.EncodeToString(md5.Sum([]byte(password)))
}

// ❌ 数字签名(不安全!)
func signMessage(message, key []byte) []byte {
	hash := md5.Sum(append(message, key...))
	return hash[:]
}

// ❌ JWT 令牌(不安全!)
func generateJWT(payload map[string]interface{}, secret string) string {
	h := hmac.New(md5.New, []byte(secret))
	// ...
}

⚠️ crypto/md5 包已不推荐用于安全应用,请使用 crypto/sha256 或 crypto/sha512 替代!