这个十年前就有结论的问题每隔一段时间都会有人讨论。结论是不管什么环境,前端提交 hash 后的口令总是好的,防的不是中途的嗅探者,而是脱库后的破解者。前端 hash 越耗时,脱库后跑字典越慢。
至于前端 hash 也不用自己捣鼓 js/wasm 这些,主流浏览器早已内置 PBKDF2 算法,较新的 CPU 都有相应的硬件加速,比自己实现可以快很多倍。
演示:
const username = new TextEncoder().encode('alice')
const password = new TextEncoder().encode('hello1234')
// 重复 1000 万次 SHA256
const pbkdfOpts = {
name: 'PBKDF2',
hash: 'SHA-256',
salt: username,
iterations: 1e7,
}
async function pbkdf2(pwd, opts, bits) {
const baseKey = await crypto.subtle.importKey('raw', pwd, 'PBKDF2', false, ['deriveBits'])
const buf = await crypto.subtle.deriveBits(opts, baseKey, bits)
return new Uint8Array(buf)
}
const dk = await pbkdf2(password, pbkdfOpts, 256)
// 注册/登录提交 dk 即可,无需提交 password
console.log(dk)
1000 万次 SHA256 在近几年的 CPU 上大约 1s,可以参考之前帖子 发一个拼 CPU 性能的红包。如果要兼顾低端设备可以调低点,也可以注册时让用户自己选(或者自动选)常用设备的性能。
如果觉得 1s 太久但又不想降低强度,可以同时异步算多个 PBKDF2,每个参数不同(比如 salt 加个后缀),然后把结果合并即可。破解难度相当于乘上了线程数。
修改下:
如果觉得强度不够但又不想增加时间,可以同时异步算多个 PBKDF2,每个参数不同(比如 salt 加个后缀),然后把各个 dk 合并后再 hash 出一个结果即可。破解难度相当于乘上了线程数。
1
lisxour 165 天前
你强任你强,md5 一把梭
|
3
LandCruiser 165 天前 15
前端明文提,后端就要铭文存?什么逻辑啊。。。
|
4
majula 165 天前
可以了解一下 PAKE ,比传统的 KDF 泄露密码的风险更低。
我们公司网站目前用的是 OPAQUE: https://www.ietf.org/archive/id/draft-irtf-cfrg-opaque-02.html |
5
iqoo OP @LandCruiser KDF 计算量太大,后端算成本太高。
|
6
beginor 165 天前
然而 Firefox Safari 不支持 https://caniuse.com/mdn-api_subtlecrypto_derivekey_derivedkeyalgorithm_option_pbkdf2
|
8
tool2dx 165 天前
iterations: 1e7, 那么大的递归数值?绝了,确实普通 PC 没办法轻易破解。
就是怕老 arm 手机浏览器,会算不过来。 |
10
StevenRCE0 165 天前
谢谢科普,马了
|
11
Chad0000 165 天前 8
不管你怎么算的,都可以将算的结果认为是“密码”,服务端认这个“密码”就得。
当天强度不够了,要升级:服务端没办法认新的算法了吧?必须得强迫用户重置密码? |
12
kdwnil 165 天前
道理我都懂,但是
> Crypto.subtle > 安全上下文: 此项功能仅在一些支持的浏览器的安全上下文( HTTPS )中可用。 而更有必要倒腾前端加密的 http 页面被完全排除在外,另外一般脱库得到的是加密后的 hash ,也没法拿去撞库吧 |
13
Puteulanus 165 天前 4
后端用慢 hash 来增加密文泄漏时候的暴力破解成本,但是用户量大了的话后端自己很难扛住这么大量的慢 hash 计算量,所以把计算过程分到前端去做,本质上不是为了安全(虽然后端上慢 hash 确实是为了安全),而是一种分布式计算的方案
|
17
lovelylain 165 天前 via Android
我觉得还是不 hash 更好,用非对称加密传输。
10 年前前端 hash 用的是 md5 ,现在认为 md5 不安全,你只能把 md5 当原始密码再做一次新 hash ,要是未来新 hash 算法也被认为不安全,你是不是要再套一层? 前端传可解密的密文,更能适应技术升级,未来你 db 用的 hash 算法被认为不安全了,也可以在用户重新登录时更换新的 hash 算法。而前端传 hash ,未来前端技术升级,你要把 hash 当密码,要么同时传可解密密码和 hash ,反正原 hash 是甩不掉了。 |
19
Chad0000 165 天前
@chingyat #18
就算没有多个版本,你现在需要升级算法。但:你有网页版和 APP 版,前者容易,后者你都不一定能限定用户必须使用某个版本的 APP 。你直接停掉之前所有版本的 APP 强迫用户升级?还是你做兼容(意味着保存多个密码版本)? |
20
iminto 165 天前 via Android
看得我一头雾水,我是不是穿越了?
16 年前,人人网最红火的年代,就看过有人研究人人网的爬虫,它的登录就是用的 RSA 算法做加密的。现在都 2024 了,还有人讨论这类的问题吗? 至于有一层楼 @majula 提到的 PAKE 算法,怎么看着像是币圈造的轮子呢? |
21
zictos 165 天前
先 sha256 ,如果还不行就加时间戳再 sha256 一次,时间戳也作为参数传输
|
22
iqoo OP @Chad0000 记录每个用户的难度值就可以了(这个值公开)。登录时输完用户名,前端可以得到当前难度值。然后算这个难度值的 dk 。如果后端还给了新的难度值,顺便再算新的 dk 。
|
25
Chad0000 165 天前
|
29
shiny 165 天前
前端能 hash ,后端也能 hash
大多数情况下脱库都是查彩虹表,这种场景下一个靠谱的 hash 才是关键,而不是在哪一端 hash 的。 |
30
Chad0000 165 天前
@iqoo #27
你没明白我说的,我说的就是升级的时候发生的事情:只要你没办法统一升级客户端(让他们一致使用新的难度),你就得保存难度的 Hash 过后的密码,否则其他难度的登录请求你没办法验证。 |
32
tool2dx 165 天前 1
这算法自古以来就有,早在 linux 还叫 unix 年代,password 就为了防止本地账号密码破解,启用了递归 hash 算法,那时候是 md5 递归 1000 次,代码里还专门注明了一句:just to make sure things don't run too fast 。
|
33
mark2025 165 天前
PBKDF2 挺不错的,基本是通用口令散列算法了
|
36
iqoo OP @shiny 目前浏览器里只有 sha 相关的 pbkdf ,只能在 salt 上做些变通,比如选取 网站 uuid+用户名,专为这个网站做彩虹表,成本太大划不来。
|
37
Chad0000 165 天前
@iqoo #35
用户名可以被扫描了:安全值 - 1 。 另,哪天 Sha256 不安全了,你需要换个算法,你一样会遇到上述问题。你为了避免被脱库,将算法固化在了前端,导致后期修改比较复杂,这个算是得不偿失的方案。 |
38
drymonfidelia 165 天前 via iPhone
@iqoo 穿戴设备也不方便输入:老板说别人都可以,我们不行就是你不专业
|
39
tool2dx 165 天前
@Chad0000 没有固化,现在的 hash 和以前的 hash 不一样了,都是加密码的。
比如 wireguard 协议,里面用的是 blake2s 算法,和 OP 这个类似,你密码一变,hash 结果天差地别。 |
40
forvvvv123 165 天前
|
41
Chad0000 165 天前
@tool2dx #39
“现在的 hash 和以前的 hash 不一样了" 你看吧,Hash 也有新算法,也就是说你永远没办法保证现在的 Hash 算法就是最佳的:这就意味着哪天你会需要升级前端算法以保证安全(不会被迅速脱库)。又回到我上面说的:如何确保不同终端一并更新,而且你的新算法还得考虑到不同算力的终端(从 PC 到手机再到穿戴设备)。而且你的前端算法还是公开的。 所以,为什么不定期更新后端密码落库的算法呢?这个百分百可控,不影响前端,任你随便玩,你还可以将应用和 DB 分开,这样只拿到 DB 不知道应用里的参数(比如这个 1000 万次 Hash ),是没办法彩虹的。不更好么? |
42
iqoo OP @Chad0000 这个是可以缓解的,不管用户存不存在,都可以返回一个值。
实际应用时为了灵活性,返回的并不是难度值,而是算法名和参数,这样适用于任何算法。 (更进一步,比如针对浏览器的场合,返回的甚至是一个 JS 版本号,前端动态加载这个 JS ,然后通过约定的接口去计算 hash 。顺便还可以在动态模块里做些风控检测) |
43
Chad0000 165 天前
@iqoo #42
那问题可就多了去了,不同终端还得适配不对算法:我桌面软件不是 Jscript 写的怎么办?手表也不可能是 JS 写的,要动态加载 JS ? 如何确保这个 JS 是安全的,万一被别人替换了或开发不小心搞错配置文件直接改明文了。你返回算法名,如果我旧的终端当时还没有这种新的算法呢?那你得提前更新新算法到终端,否则终端不支持。 还是回到那句话:你做了这么复杂的方案,安全提升了(或降低)几个百分点?是否值得? |
45
tool2dx 165 天前 2
我简单科普一下,可能有些新人不了解 PBKDF2
一个 hash 函数,比如 sha256 为了防止被破解,需要加入密码,起名为 HMAC 。 而 PBKDF2 ,就是针对于 HMAC 递归计算,次数就是 OP 里的 1000 万次,计算量的增加,大大加强了 HMAC 的安全性。 而网页登陆的用户名,就是 hash 函数的密码(也叫 salt) 这样做有什么好处呢,那就是针对用户名 A 建立的彩虹表,无法用于用户名 B/C/D 。不同用户名只能单独计算,这就增加了密码防撞的安全性。 |
46
iqoo OP @Chad0000 这个方案只讨论针对浏览器的场合,本身就是一个低成本方案,也没考虑要升级难度。多加几行代码,多花几百 ms 强化口令,用 dk 代替 password 而已,没有非常大的提升但聊胜于无。
|
47
rxmt 165 天前
前几天刷到好几个明文密码帖子,我没有仔细研究过密码传输,正在看各个帖子里的观点。
--- 我有一个想法,如果非对称加密传输可以么?不这样做的原因是开销太大吗? |
48
Chad0000 165 天前 2
@rxmt #47
有人问得好,安全要看你防的谁。 非对称你是要防传输层: - 人家大可以给你一个假的密钥:你可以做证书认证 - 恭喜你发明了 HTTPS - 传输层大可以直接换掉你的 JS 文件,跳过加密算法不更简单么 |
49
rxmt 165 天前
|
50
zhtyytg 165 天前 1
@rxmt 是没有必要,前端加密对于本地用户仅仅只是增加了破解难度,对于中间人攻击则 https 就可以防御,对于后端来说如果拿到即存数据库则密码是不安全的所以后端往往会在存储前再处理一次(不可逆 hash)。所以前端加密的意义在资深前端看来仅仅起到防脚本小子和呆比,类比前端代码混淆
|
51
gamexg 165 天前 1
@rxmt #47 因为 https 已经实现了非对称加密握手+对称加密传输.
自己再做一层没必要,而且还不如 https 实现可靠,至少 https 还有个根证书机构认证公钥. |
52
gamexg 165 天前 1
我没理解错误的话,
这个的作用是把用户长度不一,特殊字符不一的密码统一变成了 17EB4014C8C461C300E9B61518B9A18B 固定长度固定格式的密码? 攻击者也不需要知道用户原始密码,攻击者只要拿到这个加密后的密码就能当作真正的密码来实现登录. 另外,对于防止脱裤后的破解者,靠谱的解决办法不是每个用户用不同的随机盐吗? 随机盐可以使得本来一个彩虹表就能解决的问题变成每个用户都要有一个彩虹表,直接造成彩虹表无效,破解难度爆表. |
53
Y25tIGxpdmlk 165 天前 2
@iqoo #5 你的网站注册、登陆这类提交密码的操作并发很高吗??
|
54
gamexg 165 天前
@gamexg #48 不过考虑到现在基本都使用了 CDN,然后 CDN 处都有 https 证书,CDN 可以得到密码.
可能还真需要自己再套一层加密机制,或者登陆相关的域名不经过 CDN, |
56
dode 165 天前
正经的服务器都不是明文存储用户密码,比如谷歌。
你这每次认证算一个密码 hash 出来,提交到服务器,服务器没法知道你的密码对不对? |
57
dode 165 天前
演示程序电脑上跑了 5 秒
|
58
DeWjjj 165 天前
后端存密码加的盐越多,密码就越安全,不过事实上没什么用。
现在基本靠二次验证做保护,账户内消费靠二级密码。 |
59
dode 165 天前 1
正经的安全是浏览器&密码管理器在每个网站密码框自动生成填充一个唯一密码
|
60
Chad0000 165 天前
|
61
danhahaha 165 天前
前后端纠结于各种算法的安全性,用户依旧 123456 走天下
|
62
dode 165 天前
|
63
gamexg 165 天前
|
64
gamexg 165 天前
@dode #62 添加密码手机函数被发现的风险就大得多了.
我是觉得非针对单个网站,广谱的密码收集工具还是监听方式实现更不容易被发现. 当然真的被专门针对的话那就只能说认了, 即使将登陆相关的改为独立子域名不经过 CDN,但是 CDN 还是能够利用经过的域名获得 cookie . 甚至将登录域名改为经过 CDN 的域名. |
65
knva 165 天前
纠结这个不如让用户来个 16 位以上大小写特殊符号密码
|
66
forty 165 天前
1. 要防 CDN ,那就自己再来一层公钥加密。
2. 要兼顾多版本客户端: 2.1 在客户端提交请求时,附带客户端版本号,以便后端识别后区别采用新旧算法 2.2 或在新旧客户端都保持长期固定不变的算法。 至于说要后端保存每种不同 hash 值的,完全不需要。 |
68
DesnLee 165 天前
没看到有人讨论 bcrypt 呢
|
69
sampeng 165 天前
我一直奇怪这有啥好讨论的。。。。
|
71
liuzhaowei55 165 天前 via Android
pbkdf2 在动态 salt 的情况下才有意义吧
|
72
zhzbql 165 天前
花里胡哨的不如强制密码长度增加 2 个字符
|
73
supuwoerc 165 天前
我用的 bcrypt...
|
74
fionasit007 165 天前
说得好,实操还是 md5 一把梭
|
78
Chad0000 165 天前 via iPhone
@forty
完全换另外一个算法的话,你就需要提前部署那个算法。否则 app 来不及适配,尤其是紧急安全事件,你得有足够窗口期提前安排。还是那句,搞这么复杂到底提升了多少安全性。 |
79
forty 165 天前
@Chad0000 你说的“紧急安全事件”是指某个哈希算法不安全了吗?这种不会紧急。一般紧急都是数据库或某些算法泄露吧,这种就换后端算法啊。 客户端算法就保持不变,固定一套基本的哈希就够了,可变部分由服务端来提供。
你说的“搞这么复杂”不知道所指是哪个方案,是指 OP 的那个吗?我其实并不认同 OP 方案。 不复杂,也可以实现我说的: [ [ 1. 要防 CDN ,那就自己再来一层公钥加密。 2. 要兼顾多版本客户端: -- 2.1 在客户端提交请求时,附带客户端版本号,以便后端识别后区别采用新旧算法 -- 2.2 或在新旧客户端都保持长期固定不变的哈希算法。(算法不变,但是参数会变,结果会变) 至于说要后端保存每种不同 hash 值的,完全不需要。 ] ] |
80
expy 165 天前
前端 hash 后,数据库是直接存前端的 hash ,还是用 argon2 之类专用算法再 hash 一次?
|
81
yjhatfdu2 165 天前 1
我感觉很多人既不懂加密、又不懂安全、又没有逻辑,楼主还是很懂的
|
82
gavin810 165 天前
我在做一个比较小的项目的时候,鉴权设计主要是听甲方的——前后端都得 hash 。
但我个人觉得觉得前端大多数情况下都没有必要做 hash ,https 和后端的加密就已经足够了。哲学上有个概念,奥卡姆剃刀原理,如无必要,勿增实体。代码设计上也有一种思想,worse is better ,我感觉有点不谋而合。 绝对的安全是很难保证的。 毕竟自己的网站设置再复杂的 hash 函数,迭代的次数再多,加再多的盐,也很难防止用户自己泄露或者其他网站的密码库泄露。不是所有人都会对每个网站设不同的用户名密码,也不是所有网站的安全性都很高,或者有些无良网站干脆就存明文。 在这种情况下,前端加 hash 唯一能提供的,就是开发人员给自己的一些合理的技术安慰,在泄密时保护了自己网站的密码,虽然这总归是好的。 所以加或者不加,很大程度上取决于你的需求。 参考《加盐 hash 保存密码的正确方式.md 》 https://github.com/su18/wooyun-drops/blob/b2a5416/papers/%E5%8A%A0%E7%9B%90hash%E4%BF%9D%E5%AD%98%E5%AF%86%E7%A0%81%E7%9A%84%E6%AD%A3%E7%A1%AE%E6%96%B9%E5%BC%8F.md |
83
fly2never 165 天前
如果有跨平台的 c++库(比如 openssl)支持还行, 很多 api 需要 多端调用, 用 c++开发复用才能做到最大范围覆盖
|
84
forgottencoast 165 天前
@danhahaha
现在只要稍微有点良心的网站都不会允许纯数字的密码,别说 123456 了。 |
85
BeautifulSoap 164 天前 via Android
看了这么多觉得前段 hash 是极有必要安全非常非常多的人的言论,我有一种感觉,是不是觉得前端 hash 有必要的基本都是前端工程师
要不然怎么会出现如此奇怪的在完全不相信后端的同时,又同时完全相信同一个公司做出的网页前端? |
86
hesetiema 164 天前
用 wasm 代替 js 做 hash 或者加密,是不是更好呢...
|
87
restkhz 164 天前
黑客看到普通登录框,没验证码:先上 burp 爆破
黑客看到楼主登录框,没验证码:咦?这个 burp 好像跑不了。(看源码) md 这个人怎么实现登录都搞这么 xxx 复杂!不搞了! burp 关闭! 黑客看到 md5: 查查 cmd5 ,没有?字典跑跑。还没有?算了。(他甚至没注意你有没有加盐) 黑客看到 bcrypt: 字典跑跑(五分钟以后)admin123 楼主你的安全不是因为你 hash 方法牛逼防止拖库后被破解, 而是实现方法足够复杂直接让黑客从开始就放弃。 退敌于千里,治病于腠理。实在是高! 底下讨论: 有些人正在重新发明 TLS , 有些人说一些有的没的, 只有 Chad0000 在努力证明楼主这样做不值得。 实话说,我也觉得没必要。但是想想也不是不行,理由如上。 |
88
hesetiema 164 天前
|
89
Chad0000 164 天前
@restkhz #87
我理解 OP 想避免被脱库,但增加脱库难度有很多方案,OP 偏偏选了一种“御敌 999 ,自损 300”的做法。我上面的说法就是在说这种自损是否值得。就没更好更优雅的方案了么?当然有啊,其中之一就是脱库者都不知道加密方式(未拿到软件系统),盐也不在 DB 中(算出来的),每个用户加密会根据用户本身信息而变(比如用户密码版本号),那对脱库的也是一种打击。 |
90
xuanbg 164 天前
随便加个盐或 2 次 md5 彩虹表就废了,密码传输的安全性早有定论,不明白这种问题为何还成了月经贴
|
91
ZhiyuanLin 164 天前
2024 年了,可以用 Passkey 。
别瞎折腾密码了。 |
92
baobao1270 164 天前
1. crypto.subtle 这个 API 其实出现很久了,但是主流的厂商并没有使用。我也不知道为啥,反正我是用了。具体的兼容性可以看 https://caniuse.com/cryptography
2. 这个 API 既有哈希算法,又有非对称/对称加密算法。个人其实还是推荐前端做 RSA 加密而不是直接把哈希给后端。这么做有两个原因:一是盐始终应该在服务器端随机生成且客户端不可知(这就排除了客户端生成或者使用用户名做盐的做法);二是处于业务的需要,服务器是有必要知道用户的明文密码的,场景包括: a. 在后端也应该过一遍密码强度检查,不可以直接信任用户的输入 b. 对常见易猜或已知泄露的密码进行检测并提示用户 c. 对密码进行敏感检查(我也不知道为啥有这个需求,但是新浪一直有这个机制) d. 过滤密码中的不可显示字符和非 ASCII 字符(我是认为应该禁止用户使用非 ASCII 字符作为密码的——虽然会牺牲一定安全性,但是从业务的角度考虑,如果用户在一个设备上不小心输入了什么奇怪的字符、在另一个设备上输不进去了,搞不好会怪你——说到底还是业务和安全的 trade-off ) 3. 哈希算法的话,也是 argon2id 优于 PBKDF2 ,只是前者没有 FIPS 认证,对于需要安全认证的业务还是只能用 PBKDF2 。 |