GJM : 使用浏览器的计算力,对抗密码破解 [转载]
本文前半部分科普 KDF 函数的意义,后半部分探讨 KDF 在前端计算的可行性。
前言
几乎每隔一段时间,就会听到“XX 网站被拖库”的新闻。之后又会出现一些报道,分析该网站使用最多的密码是什么、有多少等等。
众所周知,密码在数据库中通常是以 Hash 值存储的,并且还加了盐。攻击者即使知道具体的 Hash 算法,也只能暴力破解。照理说这是极其费劲的,然而现实中却总有大量密码被破解,是什么导致安全性如此脆弱?
究其原因,莫过于这两点:口令密码、算法成本。
口令密码
密码可以记在很多地方。最常见的,就是记在自己脑袋里。当然还可以记在属于你的物品上,例如小本子、卡片等等,反正不用脑子记,不如设置的很长很乱,例如:
QQ: n5Py 2r8W qGyg 4tU6
GMail: 3TkS mVwQ hUrs wtmA
...
这种无意义的长串作密码,是很安全的。即使它们的 Hash 值以及算法泄露,攻击者想得到明文,只能暴力穷举所有组合:
泄露的值是 BF656DEC5DD8BA0B,算法是 f(x)。开始穷举...
尝试组合 f(x) 结果
aaaa aaaa aaaa aaaa 02F49B3EA5592B14 ×
aaaa aaaa aaaa aaab BD4E960D990DA3F3 ×
...
n5Py 2r8W qGyg 4tU5 4CEA28A904326A26 ×
n5Py 2r8W qGyg 4tU6 BF656DEC5DD8BA0B √
就算只有字母和数字,也要近 10^28 次才猜到。这是个天文数字,几乎不可行。所以,这种类型的密码还是很安全的。
然而现实中这么做的并不多。物品需要随身携带,非常不便,要是弄丢或者被偷,就更麻烦了。除非把它们都背下来,但这不又回到“记在脑袋里”这种方式了!
脑袋确实很安全,但容量也很有限。像上面那种毫无规律的字串,背一句都难,更别说多个了。所以,大家多少都会选些有意义、有规律的字串作为密码,例如 iloveyou2016、qwert12345,或是手机号、生日等组合。这种不用死记硬背的字串,就是口令(pass word)。
口令虽然方便,但缺陷也很明显:因为它是有规律的,所以猜起来就容易多了。攻击者只需测试常用单词组合,没准就能猜到了:
泄露的值是 2B649D47C4546A3E,算法是 f(x)。开始跑字典...
尝试组合 f(x) 结果
...
qwert yuiop 52708233CFFD6BFD ×
qwert asdfg CD07933880702B97 ×
qwert zxcvb 343F78782D73AB3A ×
qwert 12345 2B649D47C4546A3E √
这个过程,就是所谓的“跑字典”。一本好的字典,可以极大的提升猜中几率。
算法成本
在字典相同的情况下,速度就显得尤为重要了。每秒可以猜多少次?这得看具体的算法。
例如 MD5 函数,每次调用大约需要 1 微秒,这意味着每秒可以猜 100 万次!(而且这还只是单线程的速度,用上多并发更是恐怖)
由此可见,算法越快,对破解者就越有利。假如每次调用需要 10 毫秒,那么每秒只能猜 100 次,这样就足足慢了一万倍!
然而不幸的是,常用的 Hash 函数都是很快的。因为它们生来就有多种用途,并非为口令处理而设计。例如计算一个大文件的校验值,速度显然很重要。所以,用 MD5、SHA256 之类的“快函数”处理口令,是不合理的。(包括一些简单的变种,例如 MD5(SHA256(x)),仍然很快)。一旦 Hash 值和算法泄露,很容易被“跑字典”破解。
现实中,由于不少网站使用了“快函数”来处理口令,因此数据库泄露后,大量口令被还原也就在所难免了。
增加成本
虽然 Hash 函数单次执行很快,但我们可以反复执行大量次数,这样总体耗时就变长了。例如:
function slow_sha256(x)
for i = 0 to 100000
x = sha256(x)
end
return x
end
在密码学中,这种方式叫做 拉伸。现实中有不少方案,例如 PBKDF2 —— 它没有重头设计一种新算法,而是对现有的函数进行封装,从而更适合用于口令处理:
function pbkdf2(fn, ..., iter)
...
for i = 0 to iter
...
x = fn(x, ...)
...
end
...
return x
end
它有一个迭代参数,用于指定反复 Hash 的次数 —— 迭代次数越多,执行时间越长,破解也就越困难。
PBKDF(Password-Based Key Derivation Function,基于口令的秘钥导出函数),顾名思义,就是输入“口令”(有规律的字串)输出“秘钥”(无规律的长串)的函数,并且计算过程会消耗一定资源。本质上也是 Hash 函数,输出结果称之 DK(derived key)。
前端拉伸
拉伸次数越多虽然越安全,但这是以消耗服务端大量计算资源为代价的!为了能在安全和性能之间折衷,通常只选择几十到几百毫秒的计算时间。
服务端的计算量如此沉重,以至于不堪重负;而如今的客户端,系统资源却普遍过剩。能否让用户来分担一些计算量?
听起来似乎不可行。毕竟前端意味着公开,将密码相关的算法公开,不会产生安全问题吗。
先来回顾下,传统网站是如何处理口令的 —— 前端通常什么都不做,仅仅用于提交,口令都是由后端处理的:

现在,我们尝试对前端进行改造 —— 当用户在注册、登录等页面中提交时,不再发送原始口令,而是口令的 DK:

后端,则不做任何改动。(当然这会影响已有账号的使用,这里暂时先不考虑,假设这是个新网站)
这样,即使用户的口令很简单,但相应的 DK 却仍是个毫无意义的长串。通过 DK 的 Hash 值,是极难还原出 DK 的。(在本文开头就提到过了)
当然,攻击者更感兴趣的不是 DK,而是口令。这倒是可以破解的 —— 只需将前后端算法结合,形成一个新函数:
F(x) = server_hash(client_hash(x))
用这个最终函数 F 跑字典,还是可以猜口令的。只不过其中有 client_hash 这道障碍,破解速度就大幅降低了!
尝试组合 耗时 F(x) 结果
...
qwert yuiop 1s 1C525DC73898A8EF ×
qwert asdfg 1s F9C0A131F43F1969 ×
qwert zxcvb 1s 08F026D689D26746 ×
...
所以,我们需要:
一个缓慢的 client_hash,增加跑字典的成本
一个快速的 server_hash,防止 DK 泄露
这样,就能将绝大多数的计算转移到前端,后端只需极少的处理,即可实现一个高强度的密码保护系统。
对抗预算
由于前端的一切都是公开的,所以 client_hash 的算法大家都知道。攻击者可以把常用口令的 DK 提前算出来,编成一个新字典。将来拖库后,直接跑这个“新字典”,就能节省大量时间了。

对于这种方式,就需要使用“加盐”处理(事实上 PBKDF 本身就需要提供盐参数)。例如,选择用户 ID 作为盐:
function client_hash(password, salt) {
return pbkdf2(sha256, password, salt, 1000000);
}
client_hash('888888', 'tom@163.com'); // b80c97beaa7ca316...
client_hash('888888', 'jack@qq.com'); // 465e26b9d899b05f...
这样即使口令相同,但用户不同,生成的 DK 也是不同的。攻击者只能针对特定账号生成字典,适用范围就小多了。
更进一步,我们甚至可将“网站 ID”也掺入盐中:
function client_hash(password, salt) {
return pbkdf2(sha256, password, salt, 1000000);
}
client_hash('888888', 'jack@qq.com/www.site-a.com'); // 77a1b139aa93ac8b...
client_hash('888888', 'jack@qq.com/www.site-b.com'); // fab6b82e6a1d17d7...
这样即使同样的“账号密码”,在不同网站上生成的 DK 也是不一样了!
思考题:ID 是公开的,能不能选个隐蔽的字段作为 client_hash 的盐?
DK 泄露
DK 诞生于前端,后端对其 Hash 之后就不复存在了,所以它是个临时值。理想情况下,它是不会泄露的。

但在某些场合,DK 还是有可能泄露的。例如服务器中毒、网络传输被窃听等,都能导致 DK 泄露。
DK 泄露后,攻击者就能控制该账号了,这是无法避免的。但幸运的是,DK 只是个无意义的长串而已,攻击者并不知道其背后的那个有意义的“口令”是什么。因此其他使用类似口令的账号,就幸免于难了!
攻击者若要通过 DK 还原口令,就得用 client_hash 算法跑字典 —— 这个成本依然很大。相比之前的“最终函数 F”,只是少算一次 server_hash 而已。(server_hash 本来就很快,可以忽略不计)。所以即使 DK 泄露,破解口令难度基本没降低。
“账号被盗,口令拿不到”,这就是“前端 Hash”的意义。
撞库成本
前端拉伸计算,使得用户每次登陆,都会耗费一定的系统资源。这对普通用户来说,或许影响不大;但对于频繁登录的人来说,将是一个极大的开销。有谁会极其频繁地登录?很可能就是“撞库攻击者” —— 他们从其他地方弄到一堆账号口令,然后来这里撞运气,看看能成功登上多少。
由于我们是用 DK 登录的,因此攻击者也必须将待测的口令,先算出 DK 再提交,于是会增加不少计算成本。这和上一篇的 Proof-of-Work 有点类似。如同拉伸弹簧需要付出能量,拉伸 Hash 同样需要投入实实在在的算力。
小结
本篇提到的 3 类密码:
| 类型 | 安全性 | 易用性 | 说明 |
|---|---|---|---|
| 无意义的长串 | 高 | 差 | 很难记住,只能储存在外部,增加了保管成本 |
| 有规律的口令 | 低 | 好 | 容易记住,但也容易被“跑字典”攻击 |
| 口令转成秘钥 | 中 | 好 | 同上。但转换过程很耗时,提高跑字典的成本 |
两种 Hash 函数:
快函数(MD5、SHA256 等,输入数据“很长很没规律”时使用)
慢函数(PBKDF2、bcrypt 等,输入数据“容易猜到”时使用)
关于“前端 Hash”,其实算是 “零知识证明”(zero-knowledge proof)的一种。什么是零知识证明,这里套用一个经典的例子:
你拥有一个宝库,可以通过念咒语来开门。有天你想在朋友面前证明你能打开宝库,但又不想让他听到咒语。这该如何解决?
正好,他知道你的宝库里有个独一无二的宝物,如果能取出来给他看,自然就能证明你能打开。这样就无需带他到场,自己单独打开取出宝物,然后拿给他看就行了。于是,就可以在不暴露咒语的同时,证明你能打开。
这就是零知识证明 —— 证明者在不透露“任何有用信息”的情况下,使验证者相信某个论断是正确的。
“前端 Hash”的做法在“密码管理插件”中很常见:用户的口令不再发往后端,而是仅仅用于生成 DK(并且不同账号的 DK 不一样,即使口令相同)。这样就算遇到最坏的情况(例如服务器中毒、传输窃听、后端明文存储等)导致 DK 泄露,也不会暴露你的口令。最多损失某个账号,而不影响其他账号!
(另外,口令也不再填写于原先的文本框中,而是填在插件的界面上,插件算出 DK 后自动填到原先的文本框。这样可以降低口令泄露的风险,例如网页中可能潜伏着恶意脚本)
实际应用
不过现实中,天生内置了前端 KDF 计算的网站并不多。不像插件可以调用本地程序,性能高且稳定,浏览器则是良莠不齐,不同的版本性能差异很大,因此计算时间很不稳定。
另外,在曾经很长一段时间里(IE 时代),浏览器的计算力都十分低下,以至于大家都保留了前端计算意义不大、“一切都由后端计算”的观念。不过如今主流浏览器的性能已得到大幅提升,甚至 HTML5 还引入了 WebCrypto 规范,JS 可直接调用浏览器内置的密码学算法库,其中就包括了 PBKDF2。由于是原生实现的,因此性能非常的高。这里有个简单的演示:
https://etherdream.github.io/FunnyScript/pbkdf2/test.html

(分别使用 asm.js、Flash、WebCrypto 几种方案,计算 100 万次迭代的测试)
不过 PBKDF2 并不是最好的,因为它只是简单地套用了现有的 Hash 函数而已,而非针对性的进行设计。
2015 年 Password Hashing Competition 的胜出者 —— argon2,就非常先进了。它不仅可设置时间成本(迭代次数),还能设置空间成本(内存占用)。并且还支持多线程计算,让攻击者也必须投入同样多的算力进行破解!
当然 argon2 还太新,目前尚未收录到 WebCrypto 里。不过已有人将其移植成 JavaScrpt 版本,不妨值得一试。
GJM : 使用浏览器的计算力,对抗密码破解 [转载]的更多相关文章
- 对抗密码破解 —— Web 前端慢 Hash
(更新:https://www.cnblogs.com/index-html/p/frontend_kdf.html ) 0x00 前言 天下武功,唯快不破.但在密码学中则不同.算法越快,越容易破. ...
- 密码破解技术——P201421410029
学 号 201421410029 中国人民公安大学 Chinese people’ public security university 网络对抗技术 实验报告 实验三 密码破解技术 ...
- 中兴F412光猫超级密码破解、破解用户限制、关闭远程控制、恢复路由器拨号
不少家庭都改了光纤入户,那肯定少不了光猫的吧.今天以中兴F412光猫为例介绍下此型号光猫超级密码的破解方法.一.F412超级密码破解方法1.运行CMD,输入telnet 192.168.1.1: 2. ...
- WiFi密码破解CDlinux
好了,先说下提前要准备的东东吧:1.U盘一枚,最小1G空间.需进行格式化操作,提前保存内部文件.2.CDlinux镜像.帖子最后会提供一枚8月最新修改版,共135M. 1.CDlinux U盘启动 ...
- 【WiFi密码破解详细图文教程】ZOL仅此一份 详细介绍从CDlinux U盘启动到设置扫描破解-破解软件论坛-ZOL中关村在线
body { font-family: Microsoft YaHei UI,"Microsoft YaHei", Georgia,Helvetica,Arial,sans-ser ...
- 怎样的 Hash 算法能对抗硬件破解
前言 用过暴力破解工具 hashcat 的都知道,这款软件的强大之处在于它能充分利用 GPU 计算,比起 CPU 要快很多.所以在破解诸如 WiFi 握手包.数据库中的口令 Hash 值时,能大幅提高 ...
- WiFi密码破解详细图文教程
每天都能看到有不少网友在回复论坛之前发布的一篇破解WiFi密码的帖子,并伴随各种疑问.今天流云就为大家准备一篇实战型的文章吧,详细图文从思维CDlinux U盘启动到中文设置,如何进行路由SSID扫描 ...
- rar密码破解工具汇总
rar密码破解工具汇总 前言 假如酷爱在网络上找各种资源的你,经历千辛万苦终于找到了一个rar打包的文件,兴奋地慌忙点击,可打开才发现是加密的,相信这样的场景很多人都遇到过,今天就针对压缩文件密码的破 ...
- [基础技能] 安全技术——哈希算法密码破解之彩虹表(Rainbow Table)学习
1.基础知识 刚刚学习过数字签名的相关知识,以及数字签名的伪造技术,而伪造数字签名归根结底就是密码破解的一个过程,然而直接破解的速度是非常缓慢的,所以有人想出一种办法,直接建立出一个数据文件,里面事先 ...
随机推荐
- 微软BI 之SSIS 系列 - 使用 SQL Profilling Task (数据探测) 检测数据源数据
开篇介绍 SQL Profilling Task 可能我们很多人都没有在 SSIS 中真正使用过,所以对于这个控件的用法可能也不太了解.那我们换一个讲法,假设我们有这样的一个需求 - 需要对数据库表中 ...
- webpack学习笔记
1.安装webpack npm install webpack -g 2.进入项目目录,初始化 npm init 3.将webpack安装到项目依赖中 npm install webpack --sa ...
- IO流-文件管理
File f = new File(“test.txt”); File的构造器不会在文件不存在的情况下新建一个文件,从File对象中创建文件是由文件流的构造器或File类的createNewFile方 ...
- android 手机开启debug日志
来自 http://blog.csdn.net/aikongmeng/article/details/9764297 真机默认是不开启Log 开关的,这么来说我们如果使用真机来搞程序测试的话,需要做以 ...
- 希望有兴趣的加入,共同为项目智能化管理jar包而努力 第二篇
想听听大家对于我这个想法的一些看法,喷也好,赞也罢,希望留下您宝贵的建议! 有共同想法并且想实现的请入群 2500261120 在使用autort插件时,首先要到autort服务器下载所有 ...
- LeetCode:Maximum Depth of Binary Tree_104
LeetCode:Maximum Depth of Binary Tree [问题再现] Given a binary tree, find its maximum depth. The maximu ...
- 设置Android程序图标
先在/res/drawable/目录下放一个叫icon.png的图标图片(48×48),并且在/res/values/strings.xml中定义app_name 修改<string name= ...
- Apache Kylin 部署之不完全指南
1. 引言 Apache Kylin(麒麟)是由eBay开源的分布式分析引擎,提供Hadoop之上的SQL查询接口及多维分析(OLAP)能力以支持超大规模数据.底层存储用的是HBase,数据输入与cu ...
- 阅读《LEARNING HARD C#学习笔记》知识点总结与摘要四
又是一个周末,刚好有时间,又继续复习与总结了,希望能让大家受益,不足之处欢迎指正,谢谢! 十八. Lambda 1.Lambda表达式:匿名方法的另一种表现形式,它可以包含表达式和语句,且用于创建委托 ...
- 【Swift学习】Swift编程之旅---枚举(十二)
枚举为一组相关的值定义一个共同的类型,并允许您在代码中的以类型安全的方式中使用这些值,在 Swift 中,枚举类型是一等(first-class)类型.它们采用了很多传统上只被类所支持的特征,例如计算 ...