几乎所有正规网站的”忘记密码”都是让你重设一个新密码,而不是把旧密码发给你。原因很简单:服务端根本不知道你的原密码——它只存了一个无法还原的哈希值。反过来说,能把原密码告诉你的网站,存储方式一定有严重问题。

明文存储的灾难性后果

假设数据库直接存明文密码,会发生什么:

  1. 拖库即团灭。攻击者拿到数据库,所有用户的账号密码立刻全部可读,无需任何破解。
  2. 内部人员零门槛作恶。任何一个有库表读权限的运维或开发都能直接看到用户密码。
  3. 殃及其他平台。大量用户在不同网站复用同一套密码,一家明文泄露,攻击者拿着这批凭证去撞库,用户在其他平台的账号跟着沦陷。

加密存储(比如 AES)也不行:能解密就意味着密钥所在之处即是全部密码的所在之处,密钥和密文往往一起泄露。所以密码存储的要求不是”加密”,而是”服务器自己也无法还原“。

不可逆哈希 + 加盐:验证而不存储

满足这个要求的是哈希算法:把密码算成一个固定长度的摘要,过程单向,从摘要推不出原文。登录时把用户输入用同样方式再算一遍,比对两个摘要是否一致——验证通过,但全程没人见过原密码。

光哈希还不够,要加两样东西:

  • 盐(Salt):每个用户独立的随机值,和密码一起参与哈希,随结果一起存。盐不需要保密,它保证两个用 123456 的用户哈希值完全不同,让预计算的彩虹表彻底失效。
  • 慢哈希:MD5、SHA-256 这类通用哈希太快,显卡每秒可试几十亿次,拖库后弱密码瞬间被穷举。bcrypt、scrypt、Argon2id 专为口令设计,故意消耗几十到几百毫秒的 CPU/内存:登录接口无感,离线爆破成本放大亿倍。Argon2id 是当前首选,bcrypt 用 BCryptPasswordEncoder(cost) 即可,cost 要随硬件提升定期上调。

存储格式通常是一串自描述编码(含算法标识、参数、盐、哈希),验证时交给密码库的 matches 方法完成,不要自己拼盐比对字符串。

“找回密码”等于承认明文可逆

现在可以回答标题的问题了:如果系统能提供”找回原密码”,只可能有两种实现——明文存了,或者用可逆加密存了。两者都意味着数据库(或密钥)泄露时所有凭证即刻暴露。所以”能找回原密码”不是贴心的功能,而是安全事故的预告。遇到这种网站,正确反应是改掉该站密码,并检查自己是否在别处复用了它。

顺带一提,有些网站重置时提示”新密码不能与旧密码相同”——它不是知道旧密码,而是拿你输入的新密码去和旧哈希做了一次 matches 验证。

正确的重置流程设计

“忘记密码”是账号体系里攻击价值最高的接口之一,设计要点:

  1. 一次性令牌:点击”重置”后生成密码学安全随机数令牌(至少 128 位熵),服务端只存令牌的哈希,链接发至用户邮箱/手机。令牌用一次即作废,有效期控制在 15~30 分钟。
  2. 频控与防枚举:对请求接口和校验接口限流(同 IP、同账号计数);无论账号是否存在都返回相同提示文案,响应时间尽量接近,避免攻击者探测注册邮箱。
  3. 绑定与过期:令牌绑定具体账号,只能用于该账号的一次重置;重置成功或过期后立即失效。
  4. 重置后的收尾:新密码走与注册一致的加盐慢哈希存储;使该账号所有旧会话/Token 失效;向用户发送”密码已变更”的安全通知,若非本人操作可一键冻结。
  5. 链路安全:全程 HTTPS,重置链接只用自有可信域名,避免令牌经 Referer 泄露给第三方资源;日志、埋点、异常堆栈中绝不记录密码与令牌原文。

小结

  • 只能重置不能找回,因为服务端存的是加盐慢哈希,单向不可逆,连服务器自己都不知道原密码。
  • 明文或加密存储都是灾难:拖库即全量泄露,还会因密码复用殃及其他平台。
  • 慢哈希(Argon2id/bcrypt)+ 每用户随机盐,是抗彩虹表和离线穷举的标准方案。
  • 重置流程的安全核心是:一次性短时令牌(只存哈希)、接口限流、防账号枚举、重置后旧会话全部失效。