本文目录导读:

这是一个非常经典的密码学问题。加盐可以确保即使两个用户设置了完全相同的密码,他们在数据库中存储的密码哈希值也是完全不同的。
这之所以能有效抵御彩虹表攻击,核心在于彩虹表的工作原理和盐是如何打乱这一过程的。
第一步:理解彩虹表攻击是什么
彩虹表攻击的核心思想是拿空间换时间。
- 预计算:攻击者先对常见密码(123456”、“password”、“qwerty”等,通常有数亿或数十亿个)进行哈希计算。
- 存储:攻击者不会直接存所有“密码-哈希对”(数据量太大),而是用一种巧妙的压缩算法(彩虹表)将计算结果以“链”的形式存储起来,这个表就像一个巨大的密码-哈希字典,但体积小得多。
- 快速查找:当攻击者窃取到数据库中的哈希值(
5d41402abc4b2a76b9719d911017c592)后,他只需在彩虹表中进行几次计算和查找,就能极快地逆向找到原始密码(hello”)。
一旦你数据库中所有用户的密码都只是单纯地哈希(如 hash(密码)),那么一个预先建好的彩虹表就能瞬间破解所有弱密码。
第二步:理解“盐”是如何破坏彩虹表的
盐 是一个随机生成的唯一字符串(F3gH7s),在存储密码时,系统会执行以下操作:
- 为用户生成一个随机盐值。
- 计算
哈希(盐 + 密码)或者哈希(哈希(盐) + 密码)。 - 将 盐值 和 哈希结果 一起存储在数据库中。
攻击者面对的情况完全不同了。
使预计算白费功夫
- 无盐的情况:攻击者只需计算一次
hash("123456"),就能预建立一个包含123456 -> 5d41402abc...的通用彩虹表,这个表可以用于攻击任何网站的数据库。 - 有盐的情况:假设所有用户都用“123456”做密码:
- 用户A的盐是
F3gH7s,存储的哈希是hash("F3gH7s" + "123456")。 - 用户B的盐是
K9lP2m,存储的哈希是hash("K9lP2m" + "123456")。 - 这两个哈希值完全不同。
- 用户A的盐是
关键点:攻击者需要为每一个可能的盐值都生成一张独立的彩虹表,盐值越随机、越长(例如16字节),可能的盐值数量就呈指数级增长,这是完全不可行的,因为光是存储一个针对“123456”的彩虹表就已经很贵了,更不用说针对 2^128 个可能盐值的彩虹表了。
使反向查找失效
- 无盐的情况:攻击者在数据库中看到两个相同的哈希值
5d41...,可以立刻断定这两个用户使用了相同的密码。 - 有盐的情况:即使两个用户密码相同,哈希值也完全不同,这切断了攻击者通过哈希值模式来推断密码的途径。
打个比方
- 无盐哈希:就像所有人把他们的保险箱密码直接写在一张公共的黑板上,只要有人编了一本《常见保险箱密码对照表》(相当于彩虹表),就能一眼认出黑板上写的是什么。
- 加盐哈希:就像每个人把自己的保险箱密码放在一个加了自己专属“配料”的魔法酱料(盐) 后面,A的密码是“1234”,但他把它写成“1234-F3gH7s”;B的密码也是“1234”,但他写成“1234-K9lP2m”,攻击者手里那本《常见保险箱密码对照表》只记录了“1234”,根本不认识“1234-F3gH7s”或“1234-K9lP2m”,他必须为每一个可能的配料都重新编写一本全新的《保险箱密码对照表》,这工作量大到几乎不可能完成。
| 攻击方式 | 无盐哈希(hash(密码)) |
有盐哈希(hash(盐+密码)) |
|---|---|---|
| 彩虹表预计算 | 有效,攻击者可以提前算好所有常见密码的哈希值。 | 无效,攻击者需要为每个不同的盐值都计算一张表,成本无限大。 |
| 批量破解 | 高效,找到一条哈希记录,就能同时破解所有相同哈希的用户。 | 低效,每个用户的哈希都需要单独破解,因为盐值不同。 |
| 弱密码保护 | 差,只要密码在彩虹表里(通常是弱密码),瞬间破解。 | 强,相同的弱密码也会产生完全不同的哈希值,无法重用彩虹表。 |
加盐之所以能抵御彩虹表攻击,核心就在于它引入了随机性,使得攻击者无法利用“在攻击之前”就预先生成的通用查找表。 攻击者现在不得不针对每个用户的每个盐值进行“实时计算”,这极大地降低了攻击效率,在现实场景中几乎不可行。