密码认证:静态密码的存储与传输安全、加盐哈希、密码策略与强度评估
密码认证,说白了就是最古老也最常用的身份验证方式。你输入一串字符,系统验证它是不是对的。听起来简单,但这里面的坑,我踩过不少。
我记得刚入行那会儿,有个项目直接把用户密码明文存数据库里。当时我觉得没什么问题,直到被安全审计骂了个狗血淋头。嗯,从那以后,我对密码的存储和传输就格外上心。
静态密码的存储安全
密码存哪儿?怎么存?这是第一个要解决的问题。
千万别存明文。这应该是常识了,但我见过太多初创公司把用户密码直接扔进数据库。一旦数据泄露,用户的账号就全完了。更可怕的是,很多人所有平台用同一个密码,你这边泄露了,人家银行账号也跟着遭殃。
正确的做法是:只存密码的哈希值。哈希是个单向函数,你从哈希值推不出原始密码。但光哈希还不够,你得加盐。
核心原则:永远不要存储用户密码的明文或可逆加密形式。只存储加盐后的哈希值。
为什么需要加盐?
不加盐的哈希有个大问题:彩虹表攻击。攻击者提前算好常见密码的哈希值,一比对就知道你密码是什么。加盐就是在每个密码后面拼一段随机字符串,再算哈希。这样就算两个用户密码一样,哈希值也不同。
我个人习惯用 16 字节以上的随机盐,每个用户单独生成。盐不需要保密,跟哈希值一起存就行。
bcrypt 与 argon2
现在主流推荐两个算法:bcrypt 和 argon2。我重点说说它们。
| 特性 | bcrypt | argon2 |
|---|---|---|
| 设计时间 | 1999 年 | 2015 年(PHC 优胜者) |
| 抗 GPU 攻击 | 较好(内存占用固定) | 优秀(可调内存/并行度) |
| 抗 ASIC 攻击 | 一般 | 优秀(内存硬) |
| 配置参数 | cost factor(工作因子) | 时间成本、内存成本、并行度 |
| 推荐场景 | 通用场景,兼容性好 | 新系统,对安全性要求极高 |
bcrypt 我用了很多年,稳定可靠。它的 cost factor 我一般设到 10-12,服务器响应时间还能接受。argon2 是新一代算法,拿过密码哈希竞赛冠军。我在一个新项目里试过,确实更安全,但配置参数多了点,需要调优。
我的建议:如果项目刚起步,用 bcrypt 就够了。如果对安全性有极致要求,或者用户量不大,上 argon2。别自己实现哈希算法,用现成的库。
代码示例(Node.js 用 bcrypt):
const bcrypt = require('bcrypt');
const saltRounds = 12;
// 注册时:哈希密码
const hash = await bcrypt.hash(password, saltRounds);
// 存 hash 到数据库
// 登录时:比对密码
const match = await bcrypt.compare(password, storedHash);
if (match) {
// 密码正确
} else {
// 密码错误
}
静态密码的传输安全
存好了,怎么传过来?
你想想看,用户在浏览器输入密码,然后通过网络发到服务器。如果中间被人截了,那存得再安全也没用。
第一道防线:HTTPS。这不用我说了吧?所有密码传输必须走 TLS 加密。我在项目中遇到过有人为了省事,在内部网络用 HTTP,结果被同事抓包抓到了测试账号的密码。尴尬不?
第二道防线:前端哈希?这个问题有争议。有些人说前端哈希可以防止明文密码泄露,但我个人不建议。为什么?因为前端哈希后,哈希值就成了新的“密码”,攻击者拿到哈希值照样能登录。而且前端哈希容易受 JavaScript 篡改影响。
注意:前端哈希不能替代 HTTPS。它最多算个辅助手段。真正的安全靠的是传输层加密和服务端加盐哈希。
我现在的做法是:前端不做任何密码变换,直接通过 HTTPS 发送明文密码到服务端。服务端收到后立即用 bcrypt/argon2 处理。这样最干净,也最可控。
密码策略与强度评估
用户设密码,你不能不管。太简单的密码,比如 "123456"、"password",跟没设一样。但管得太严,用户记不住,最后贴便签在显示器上,更不安全。
这里有个平衡点。我一般建议的策略:
- 最小长度:8 位以上。12 位更安全。
- 字符类型:至少包含大写、小写、数字、特殊字符中的三种。
- 禁止常见密码:维护一个黑名单,包含 "password"、"123456"、"qwerty" 等。
- 禁止与用户名相同:避免用户用邮箱前缀当密码。
- 密码过期:这个有争议。我个人觉得 90 天强制改密码反而让用户设更弱的密码。不如只在检测到泄露时要求改。
强度评估怎么做?我习惯用 zxcvbn 这个库。它比传统的长度+字符类型判断准得多。传统方法会说 "P@ssw0rd" 很强,但 zxcvbn 知道它是个常见模式,评分很低。
避坑指南:我曾经在项目里只用了长度和字符类型判断,结果用户设了 "Password1!",系统说强,其实弱爆了。后来换成 zxcvbn,效果好很多。
密码强度评分示例(zxcvbn):
const zxcvbn = require('zxcvbn');
const result = zxcvbn('P@ssw0rd');
console.log(result.score); // 0-4,4 为最强
// 结果:1(很弱)
// 因为 "P@ssw0rd" 是常见模式
下面这张图,是我总结的密码认证核心流程。你看一眼,基本就全明白了。
最后说一句,密码认证虽然基础,但做好不容易。从存储到传输,从算法选择到策略制定,每个环节都可能出问题。我见过太多因为密码存储不当导致的数据泄露事件,代价惨重。
嗯,这一章就到这儿。记住:加盐哈希是底线,HTTPS 是标配,密码策略要平衡安全与用户体验。做到这三点,你的密码认证基本就稳了。