哈希与消息认证:数字世界的指纹与签名

哈希函数,说白了就是把任意长度的数据,变成一串固定长度的“指纹”。你想想看,这世上没有两片完全相同的树叶,好的哈希算法也没有两个不同的输入能产生相同的输出。我刚开始接触加密时,总觉得这东西挺玄乎的,一个字符串怎么就能变成一串看不懂的字符呢?后来才明白,它就是单向的压缩算法。

MD5 vs. SHA-256:老将与新秀

先说 MD5。MD5 生成 128 位的摘要。速度快,生成短。我以前刚入行时还用过它做文件校验,嗯,那时候还挺流行。但现在,我强烈建议你别在生产环境用 MD5 做安全相关的事。

避坑指南: MD5 的碰撞已经被证明可以在普通家用电脑上实现。我曾经在项目中看到过旧系统用 MD5 存密码,后来被安全审计报告直接打回,重写权限模块,那叫一个折腾。

SHA-256 是 SHA-2 家族的一员,输出 256 位。抗碰撞能力远比 MD5 强。现在各大加密协议、数字证书、区块链里都有它的身影。

特性MD5SHA-256
摘要长度128 位 (16 字节)256 位 (32 字节)
安全状态已不再安全,有碰撞攻击当前安全,广泛使用
性能极快较慢但可接受
典型用途非安全场景的校验(老系统)数字签名、证书、密码哈希

个人习惯: 在日常开发中,但凡涉及 hash,我默认直接用 SHA-256。没必要为了这点性能差异去冒安全风险。

哈希碰撞:小概率,大问题

什么是碰撞?就是两个不同的输入,产生了完全相同的哈希值。理论上,任何哈希函数都会发生碰撞,因为输入空间是无限的,输出空间是有限的。但好的算法会把碰撞概率降到极低。

可是 MD5 就不一样了。早在 2004 年,中国学者王小云教授就提出了高效的 MD5 碰撞方法。我在一次安全审计中就看到过一个案例:攻击者构造了两个内容不同但 MD5 值完全一样的 PDF 文件,成功绕过了电子签章校验。这问题相当严重。

所以,SHA-256 至今仍被认为安全,还没有找到实用的碰撞攻击方法。这就是为什么它成为行业标准。

HMAC:给哈希加上钥匙

纯粹的哈希函数没有密钥,谁都能算。但 HMAC 不一样,它混入了一把密钥。说白了,HMAC = Hash + Key。它用于消息认证,确认数据没被篡改且来源可信。

HMAC 具体流程很简单:

  • 双方事先共享一个密钥
  • 发送方用 HMAC(密钥, 消息) 生成一个认证码
  • 接收方用相同密钥和消息,计算 HMAC,并对比

如果认证码一致,说明消息在传输过程中没被改过。为什么?因为攻击者不知道密钥,改完消息后没法伪造出合法的 HMAC。

核心区别: 哈希保证完整性,HMAC 保证完整性和消息来源的真实性。

我们平时用的 JWT 的签名部分,底层就是 HMAC。很多 API 网关的签名校验也是它。我建议你记住这个套路。

import hmac
import hashlib

key = b"my_secret_key"
message = b"transfer $100 to Alice"

# 计算 HMAC-SHA256
digest = hmac.new(key, message, hashlib.sha256).hexdigest()
print(digest)  # 输出认证码

加盐哈希:存密码的正确姿势

很多人直接对用户密码做一次 SHA-256 就存数据库。这个做法让我很着急。为什么呢?因为如果两个用户的密码一样,哈希值也会一样。而且攻击者可以事先算好上亿个常用密码的哈希值,做成彩虹表来碰撞。

加盐就是给每个用户生成一个唯一的随机字符串(就叫盐)。然后把密码和盐拼在一起,再做哈希。就算两个用户密码相同,因为盐不同,哈希结果也完全不同。彩虹表也就失效了。

我个人习惯的做法是:每个用户注册时,生成一个 32 字节长的随机盐。然后把盐和哈希结果一起存到数据库。验证密码时,从数据库取出盐,拼上用户输入的密码,再算一遍哈希,跟库里的哈希对比。

这是单向哈希与加盐哈希的对比:

存储方式相同密码的存储值抗彩虹表抗暴力破解
明文完全相同❌ 完全无效❌
无盐哈希完全相同❌ 直接暴露⚠️ 可加速
加盐哈希各不相同✅ 攻破成本极高✅ 需要逐个破解

我曾经踩过的坑: 早期做项目时,我忘了给每个用户生成独立盐,而是用了一个全局固定的盐。结果安全审计的说,这样跟没加盐也差不多。每个用户还得用随机盐,单独存。

知识体系总结

下面这张图总结了本章的核心知识点。你看看从原始数据怎么通过不同的哈希方式,分别走向数据完整性、消息认证和密码存储安全。

哈希与消息认证知识体系 原始数据 哈希函数(H) MD5 / SHA-256 HMAC MD5 碰撞 不推荐安全场景 SHA-256 安全、广泛使用 消息认证 完整性 + 来源真实性 加盐哈希存储密码 每个用户独立随机盐 📌 单向函数 不可逆,算不出原始数据 完整哈希函数群 密码存储最佳实践 消息认证机制