哈希函数
哈希函数
复习定位
哈希函数能将任意长度的输入(文件、密码)映射成一个固定长度的短输出(如SHA-256输出256位)。哈希是单向的——从哈希值不能反推出原始输入。密码存储不是存明文也不是存"对称加密的密文"(密钥可以反向解出密码)——而是存哈希——验证时比较哈希值。
哈希函数的安全性质
一个安全的密码学哈希函数需要具有三个性质:
抗原像(Preimage Resistance)——给定哈希值h、不可能找到任何消息m使得hash(m)=h。这一性质保证了如果数据库中的密码哈希(admin的哈希: e3b0c44298fc...)被攻击者拖走——攻击者无法从哈希中反推出原始密码(除非密码太弱可以哈希字典暴力比对)。
抗第二原像(Second Preimage Resistance)——给定消息m1、不可能找到不同的m2使得hash(m1)=hash(m2)。如果可找到——攻击者可以制作一个包含恶意代码的文件B——使得它的哈希与正常文件A相同——系统用哈希校验文件完整性时B的哈希正确加载B——恶意代码执行。
抗碰撞(Collision Resistance)——不可能找到任意两个不同的消息m1,m2使得hash(m1)=hash(m2)。这比抗第二原像更强——攻击者可以自由选择两个消息都不限而不仅仅替换一个合法消息。SHA-1的碰撞攻击(SHAttered 2017)展示了两个不同的PDF文件生成了相同的SHA-1哈希值——因此SHA-1在数字签名证书等依赖碰撞抵抗的场景中已经不再安全。
常用哈希函数
| 算法 | 输出长度 | 碰撞抵抗 | 状态 |
|---|---|---|---|
| MD5 | 128位 | 已破(2004年) | 不可再用于安全场景 |
| SHA-1 | 160位 | 已破(2017年SHAttered) | 已弃用 |
| SHA-256 | 256位 | 当前安全 | 广泛使用 |
| SHA-3 | 任意 | 最新标准 | 可选 |
| BLAKE3 | 任意 | 安全 | 新强 |
哈希在密码存储中的应用
存密码的正确方式不是存用户的密码本身——而是存它的哈希加随机salt。salt是一个随机字符串——在每个用户注册时生成——附加到密码上一起哈希——数据库里存salt:hash(salt+password)。salt的作用——即使两个用户选择相同的密码——加了不同的salt后哈希值不同——防止攻击者从哈希观察出"这两个用户密码相同"——且salt使预计算的彩虹表失效。
但是仅仅SHA256(password+salt)一次仍然容易暴力破解——因为SHA256设计上非常快——现代GPU每秒可以计算数亿次SHA256——弱密码(比如pass123)无论如何加盐都能在可接受的GPU时间中被暴力穷举到。正确做法是使用慢哈希函数bcrypt/scrypt/argon2——设计为需要大量CPU时间或内存——使得每次验证密码故意变慢(约100ms)——将攻击者的穷举速度降低到不可接受。
文件完整性校验
Linux发行版提供ISO文件的SHA-256校验和——用户下载后计算哈希并与官方提供的哈希对比——确保下载的文件在传输过程中没有被篡改。sha256sum ubuntu-24.04.iso。
数字签名中——不是对整个文件签名——是对文件的哈希值签名——因为哈希值(256位的长)比文件本身小太多——签名/验签操作更快。
复习检查
哈希的抗碰撞性与抗原像性之间的区别——找到两个不同的消息哈希相同——和已知一条哈希找不到原始消息——被单向哈希函数的哪种困难度保证?
MD5的碰撞攻击——在2004年已经由王小云团队提出的技术可成功制造MD5碰撞——除了"检查文件MD5—发现与官方MD5一致”——还能相信这个文件对吗?
盐(salt)为什么不需要保密——为什么攻击者知道salt后破译单个密码的速度没有比不加盐时快——对整表密码批量破解效率与salt的随机性有多大关联?
为什么散列密码不用SHA-256加盐就够了——为什么需要慢哈希(如bcrypt)?慢哈希的"慢"是如何实现的?
在一台现代机器上暴力破解SHA-256密码哈希可以达到每秒多少亿次——如果破解一个随机8位密码(包含大小写字母数字+符号)需要多少时间?