字典攻击不是「算出来」,是「试试看」。Demo 里 20 条常见密码、46 次尝试就撞开 2 个账号;
第三个 Zq7#vL2!xR 猜不中。加盐改不了弱密码,它改的是「同密码不同哈希」。
前面十几节都在「骗过程序」。这一节最直白:一个漏洞都不用,直接猜。
一、三种猜法,都不是破解算法
字典攻击:拿一份常见密码表逐个试。Demo 的 20 条字典里就有 pa55word 和 sunshine。
暴力破解:按字符集穷举,空间等于字符集大小的位数次方。它慢,但一定能跑完——只是可能要跑几百亿年。
撞库:拿别的站泄露的账号密码来试。
因为人习惯在多处用同一个密码,这一招命中率最高、算力最省,也是现实里最常见的一种。
三种都不是「破解算法」——哈希不可逆这件事没被推翻。它们都是「试」。
所以防御的重点也不在算法,在限制试的次数和让每个密码够长。
二、实测:46 次尝试,撞开 2 个
三个账号,密码分别是 pa55word、sunshine、Zq7#vL2!xR。前两个在字典里,第三个不在。
结果:bozai 猜中、guest 猜中、root 没猜中。
字典 20 条,总尝试 46 次(每个账号逐条试,命中即停),命中 2 个。
46 次是什么概念:一次登录请求的往返时间里能试几十个。
所以「连错三次锁定五分钟」这条规则,比换更长的哈希算法管用得多。
注意 pa55word 这种写法:把 o 换成 0、s 换成 5,人觉得自己很聪明,
但这类变形在字典里早就是标配——字典不只收原词,还收常见变形。
三、加盐改的是什么
加盐是在密码后面拼一段随机值再哈希。
Demo 里 bozai 的哈希从 4c1b9bb3405f53cf 变成 f6cc58f0eca9fd1f,盐是 0f9d5d39。
它挡不住字典攻击:攻击者拿到盐,对每条候选密码把盐拼上去再算一遍就行,
多花的成本只是一次字符串拼接。Demo 里加盐之后,那 20 条照样能撞中。
它真正挡住的是两件事。一是彩虹表:预先算好的海量哈希表全部作废。
二是「同密码同哈希」这个信息泄露——三个账号密码都是 123456 的话,加盐后三串哈希互不相同,
攻击者就看不出它们同密码。
所以盐要每用户独立、随机、和哈希一起存。全站共用一个固定盐,等于没加。
顺带说一句:Demo 里那个盐只有 4 字节,是为了好打印,真实系统至少 16 字节。
四、穷举要多久
本机 sha256 速度 55 万次/秒(Python 单线程)。
8 位小写字母的空间是 26 的 8 次方,等于 208827064576 种;照这个速度跑完要 105 小时。
这个数只代表这台机器用 Python 单线程。真实攻击用 GPU,一秒能算几十亿次,8 位纯小写字母是分钟级的。
所以它不能当「够安全」的依据。
由此得出一条实用的结论:长度比复杂度值钱。多一位,空间乘 26;
把 a 换成 @,空间只多一点点,而且这种替换早在字典的变形表里。
实际能落地的三条:长度 12 以上、不用词典词、不重复使用。
剩下的交给限速、锁定、二次验证——这些是服务端能控制的。
五、动手 Demo:20 条字典,46 次尝试,撞开 2 个
造三个账号,密码一个用字典词加常见变形、一个是字典词、一个随机。 然后拿一份 20 条的小字典逐条算 sha256 去比对,数一数总共试了几次。 最后对本机的哈希速度做一次实测换算。
def h(pw, salt=u''):
return hashlib.sha256((salt + pw).encode()).hexdigest()
for user, real in USERS.items():
for cand in DICT:
tries += 1
if h(cand) == h(real):
hit.append((user, cand))
break
实跑输出:
--------------------------------------------------------------------
密码破解:字典、加盐、穷举要多久
--------------------------------------------------------------------
三个账号,密码分别是:一个在字典里、一个也在字典里、一个不在。
账号 密码(只有我们知道) sha256 前 16 位
bozai pa55word 4c1b9bb3405f53cf
guest sunshine a941a4c4fd0c01cd
root Zq7#vL2!xR 37f02984c2f8a3ff
字典攻击(20 条常见密码,逐个算 sha256 去比对):
bozai 猜中:pa55word
guest 猜中:sunshine
root 没猜中
字典大小 20 条
尝试次数 46 次
命中 2 个账号
`root` 那个没猜中——因为它的密码不在字典里。这就是字典攻击的全部原理:
不是「算出来」,是「试试看」;管用的唯一原因是人爱用常见密码。
加盐之后 bozai 的哈希 f6cc58f0eca9fd1f5acbc9a2e8f9e817
它的盐 0f9d5d39
和加盐前一样吗 不一样
加盐之后,字典攻击还是能猜中弱密码——因为攻击者对每条候选密码把盐拼上去再算一遍就行。
它真正防住的是另一件事:**同密码不同哈希**,所以彩虹表和「一次算完全网通用」失效。
三个账号的密码如果都是 `123456`,加盐后三串哈希互不相同,也就不暴露「这三个号同密码」。
本机 sha256 速度 55 万次/秒
8 位小写字母的空间 26 的 8 次方 = 208827064576 种
照这个速度跑完 105 小时
这个数只代表**这台机器用 Python 单线程**的速度。
真实攻击用 GPU,一秒钟能算几十亿次,所以纯字母数字的 8 位密码早就守不住了。
输出里三个账号的哈希前 16 位都打了出来,字典 20 条、尝试 46 次、命中 2 个。
46 是怎么来的:逐个账号扫字典,命中就停,没命中的那个要扫满 20 条。
root 没猜中不是因为它多强——Zq7#vL2!xR 只有 10 位——而是因为它不在那 20 条里。
字典攻击的全部原理就在这一句:它不知道答案,它在试。
加盐那一段:哈希变了,盐是 8 个十六进制字符。
这里要说明白——盐短只是为了好打印,短盐不会让加盐失效,但真实系统不该这么短。
55 万次/秒和 105 小时都是这台机器 + Python 单线程的实测换算。
换 GPU 会小几个数量级,所以这两个数的用途是「说明长度空间怎么算」,不是「说明这个密码很安全」。
30 秒实验
- 用
hashlib加一份 10 条的字典,试着「破解」你自己设过的一个弱密码。 - 算一算:12 位纯小写字母的空间,是 8 位的多少倍?
- 想一想:为什么「连错三次锁定」比「哈希换得更强」更管用?
常见误区
- 「加了大写和符号就很难猜」——
P@ssw0rd在字典里排得很靠前。 - 「加了盐就不怕字典攻击」——盐挡的是彩虹表,弱密码照样一猜就中。
- 「哈希算法够强就安全」——攻击者不破解哈希,他直接试密码。
自测三问(点开对答案)
1. 字典攻击为什么管用? —— 人爱用常见密码,把常见密码表逐个试一遍就能撞上。 2. 加盐防住什么、防不住什么? —— 防彩虹表和「同密码同哈希」,防不住弱密码被猜中。 3. 为什么长度比复杂度重要? —— 多一位空间乘字符集大小;换个符号只增几个候选,还容易被变形表覆盖。猜密码还在「替用户试」。下一种连服务端都不碰,只骗用户的眼睛。
- ☐ (本节完,下一节 11.5.2 点击劫持)