密码安全基础:哈希算法与密码存储

聊密码安全,绕不开哈希。说白了,哈希就是“不可逆的指纹”。你给它任意长度的数据,它吐出一段固定长度的摘要,而且你没法从摘要反推出原始数据。这个特性,让它成了存储密码的首选方案。

但别急着高兴——不是所有哈希都适合存密码。我见过不少项目,直接拿SHA-256存用户密码,以为这样就安全了。结果?嗯,后面的故事不太好看。

SHA-256:计算快,但存密码是灾难

SHA-256是SHA-2家族的一员,输出256位(32字节)摘要。它的设计目标是防篡改和防碰撞,不是防暴力破解。恰恰相反,SHA-256太快了——现代GPU一秒能算几十亿次。你想想看,如果数据库泄露,攻击者用跑字典的速度几乎是实时的。

那SHA-256就完全没用吗?当然不是。文件校验、数字签名、区块链都靠它。但存密码?千万别。

踩坑实录: 我曾经接手过一个遗留系统,密码直接SHA-256且无盐。用户表泄露后,攻击者一小时内就把弱口令全破了。后来我们花了两周强制所有用户改密码,还被投诉——这就是“图省事”的代价。

加盐:让你的密码与众不同

为什么需要加盐?你看,两个用户如果密码相同,没加盐时哈希值也一样。攻击者只要破解一次,就能知道所有同密码的用户。加盐就是在密码后面贴一段随机字符串(盐),再算哈希。这样,即使密码相同,哈希值也不同。

我个人的习惯:盐至少16字节,用密码学安全的随机数生成器(比如 os.urandom 或 SecureRandom),每个用户独立的盐。盐本身不需要保密,可以明文存在数据库里。

加盐的注意事项

  • 盐必须随机且足够长:不要用用户名、时间戳做盐,太容易预测。
  • 每个用户/每次密码重置都要重新生成盐:我用过一个库,它重复使用了固定的盐……简直是灾难。
  • 加盐后仍需慢哈希:加盐只解决彩虹表问题,不解决暴力破解的速度问题。SHA-256+盐依然快,攻击者照样可以每秒跑上亿次。
一句话总结: 加盐是必须的,但单独加盐不够。你必须配合“慢哈希算法”来对抗暴力破解。

BCrypt vs Argon2:慢哈希的典范

慢哈希,就是故意让计算变慢。通过增加迭代次数、内存消耗,让单次验证耗时可控(比如0.1秒),但攻击者想大规模跑字典就变得极其昂贵。

目前最主流的两个慢哈希:BCrypt 和 Argon2。直接对比表格更直观。

特性 BCrypt Argon2
出生年份 1999 2015(PHC冠军)
可调参数 工作因子(cost,即迭代次数) 时间成本、内存成本、并行度
抗GPU/ASIC 中等(主要是CPU bound) 强(内存硬,GPU没法作弊)
输出格式 $2b$10$...(自带盐和版本) $argon2id$v=19$...
密码长度限制 72字节(超过截断) 无硬性限制
推荐场景 传统Web应用,兼容性好 新系统、高性能环境、对抗硬件攻击

我在实际项目中两个都用过。BCrypt的最大优点是生态老、库多、坑少。几乎所有语言都有成熟实现,默认配置就够用。但要注意它的密码长度上限——72字节。如果用户输入超长密码(比如 >72字符),会被悄悄截断,导致后来永远验证失败。我遇到过这类工单,排查了半天才发现是BCrypt的锅。

Argon2是2015年密码哈希竞赛的冠军,它有三种变体:Argon2d(抗侧信道)、Argon2i(抗时间攻击)、Argon2id(混合)。我建议直接用Argon2id,它兼顾了安全性和抗攻击性。它的参数可以独立控制时间、内存、并行度,灵活性比BCrypt高一个量级。

我的选择: 如果是新项目,我会用Argon2id。如果是维护老系统或者兼容现有用户数据,BCrypt是个稳妥的过渡方案。别纠结“哪个最好”,选一个对的、库可靠的就够了。

密码哈希的完整流程图

下面这张图展示了我推荐的核心流程:

密码哈希安全流程 用户密码 生成随机盐 盐 + 密码 BCrypt / Argon2id 慢哈希 存储哈希结果 ⚠ 永远不会把原始密码或SHA-256哈希存到数据库。 盐和最终哈希一起存储,格式如 $argon2id$v=19$... 或 $2b$10$...

选型建议与总结

如果你还在犹豫,我直接给结论:

  • 不要用 SHA-256 / MD5 存密码,哪怕加了盐。
  • 优先用 Argon2id,参数设成 时间成本=3, 内存=64MB, 并行度=4(或适配你的运行环境,保证单次验证 ≤ 0.5秒)。
  • 如果兼容性优先(比如Java Spring Security默认支持BCrypt),用BCrypt,cost至少设为10,最好12。
  • 一定要加盐,盐由安全随机数生成,长度16字节以上。
  • 密码长度限制:BCrypt注意72字节截断,建议在前端主动截断或提醒;Argon2无此问题。
一个小经验: 我习惯在密码哈希的配置里写一段注释,说明当前使用的算法、参数和选择理由。这样半年后自己回头看,或者别的同事接手,不会一脸懵。

总结下来,密码存储安全不在于算法多炫,而在于你是否用了慢+盐+抗硬件的组合。记住一句话:让攻击者算一个密码的成本,大于他放弃的成本。 这就是BCrypt和Argon2的价值。