高级 #crypto#hash
哈希函数(SHA, MD)
密码学哈希函数把任意长度输入映射为固定长度输出——抗碰撞、单向。SHA-256 是当前标准,MD5 和 SHA-1 已被破解
🆔 给数据”算个指纹”
你去公安局办身份证——警察量你的身高、拍你的照片、录你的指纹。按了手印,你就和这份档案”绑定”了。以后只要按个指纹,就能确认”这是你”。
哈希函数就是给数据”按个指纹”——把任意长度的数据(比如一个文件、一条消息),压缩成固定长度的”摘要”(哈希值)。
📐 哈希函数(Hash Function):
hash(任意长度输入) → 固定长度输出一个简单的哈希例子——Mod 10 哈希:
hash("Hello") = 842 → 842 % 10 = 2 hash("World") = 568 → 568 % 10 = 8
🔐 密码学哈希的三个性质
普通哈希(如 Python 的 hash())只要求”分布均匀”——但密码学哈希有三个更强的性质:
| 性质 | 含义 | 为什么重要 |
|---|---|---|
| 抗碰撞(Collision Resistance) | 找不到两个不同的输入,使哈希值相同 | 没人能伪造和你一样的”指纹” |
| 单向性(Preimage Resistance) | 从哈希值反推输入不可行 | 密码哈希存储——数据库被拖了也得不到密码 |
| 雪崩效应(Avalanche Effect) | 输入改一比特,输出约一半比特翻转 | 改一个字符,哈希值完全不同 |
import hashlib
def sha256(text):
return hashlib.sha256(text.encode()).hexdigest()
print(sha256("Hello"))
# 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
print(sha256("Hello ")) # 只多了一个空格
# 5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03
# ↑ 完全不同!
print(sha256("hello")) # 首字母小写
# 2cf24dba... ← 和 "Hello" 不同!
🧪 常见的哈希算法
| 算法 | 输出长度 | 安全性 | 推荐使用? |
|---|---|---|---|
| MD5 | 128 位 | ❌ 已破解(可制造碰撞) | 不用于安全场景 |
| SHA-1 | 160 位 | ❌ 已破解(Google 演示过碰撞) | 不再推荐 |
| SHA-256 | 256 位 | ✅ 安全 | ✅ 当前推荐 |
| SHA-3 | 可变 | ✅ 安全 | ✅ 新一代标准 |
⚠️ MD5 的破解:2004 年,中国学者王小云团队发现了 MD5 的碰撞攻击——可以在几分钟内生成两个不同的文件,使 MD5 哈希完全相同。这直接宣告了 MD5 在安全场景中的死亡。
🏢 哈希函数的四个关键应用
① 密码存储
# ❌ 错误做法:数据库存明文密码
# 如果数据库被拖——所有用户密码暴露
# ✅ 正确做法:存哈希值
import hashlib
def store_password(password):
# 加盐——防止彩虹表攻击
salt = os.urandom(16)
hashed = hashlib.pbkdf2_hmac('sha256', password.encode(), salt, 100000)
return salt + hashed # 存 salt + 哈希
def verify_password(password, stored):
salt = stored[:16]
hashed = hashlib.pbkdf2_hmac('sha256', password.encode(), salt, 100000)
return hashed == stored[16:]
🔑 加盐(Salt):即使两个用户设置相同的密码,由于加盐不同,存储的哈希值也不同——攻击者无法用一个”常见密码哈希表”(彩虹表)批量破解。
② 文件完整性验证
# 下载大文件时——验证文件有没有被篡改
# 官方网站提供 SHA-256 校验码
sha256sum ubuntu-24.04-desktop.iso
# 输出:a1b2c3...(64 位十六进制数)
# 和官网对比——一致说明文件没有被修改
③ 数字签名
哈希函数是数字签名的关键——你不是对整个文件签名(太慢),而是先对文件的哈希值签名:
签名:hash(文件) → 签名算法(hash) → 签名
验证:签名算法验证(hash) → 对比 hash(下载的文件)
④ 区块链中的 Merkle Tree
比特币的区块不是直接存所有交易——而是用 Merkle Tree(哈希树)把所有交易的哈希值层层归约为一个根哈希。改任何一笔交易都会导致根哈希变化——保证了区块链数据的不可篡改性。
Root Hash
/ \
Hash(AB) Hash(CD)
/ \ / \
Hash(A) Hash(B) Hash(C) Hash(D)
↑ ↑ ↑ ↑
交易A 交易B 交易C 交易D
🆚 密码学哈希 vs 普通哈希
| 对比 | 密码学哈希(SHA-256) | 普通哈希(Python hash()) |
|---|---|---|
| 安全性 | 抗碰撞、单向 | 不保证安全 |
| 速度 | 相对慢(故意慢一点防暴力) | 快(用于哈希表) |
| 确定性 | 完全确定 | 每次运行可能不同(Python 加随机种子) |
| 用途 | 安全场景 | 数据结构 |
📝 小结
| 概念 | 一句话 |
|---|---|
| 哈希函数 | 任意输入 → 固定输出(数据”指纹”) |
| 抗碰撞 | 找不到两个不同输入产生相同哈希值 |
| 单向性 | 从哈希值反推输入不可行 |
| 雪崩效应 | 改一比特输入 → 一半输出比特翻转 |
| SHA-256 | 当前推荐的密码学哈希(256 位输出) |
| MD5/SHA-1 | 已破解——不用于安全场景 |
| 加盐(Salt) | 哈希前加随机串——防彩虹表攻击 |
🎯 思考题:为什么密码学哈希要”故意慢一点”?如果验证用户密码需要 0.1 秒而不是 0.0001 秒——对用户来说几乎无感,但对攻击者来说影响有多大?(提示:尝试 1 亿次暴力破解需要的时间)
为什么先学这个? 哈希函数是数字签名与证书的基础——先把消息”浓缩”成哈希值,再对哈希值签名。