⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 http://www.iocoder.cn/Fight/mianshiguanweishenmewangjimimayaozhongzhierbushigaosuniyuanmimawocunyuanmimadetongshiyijingbeikaile/ 「芋道源码」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

这道题面试官真正想筛什么

「为什么忘了密码只能重置不能告诉你原密码」——表面看是个「为什么」式问题,听起来是科普题。

但面试官在筛 3 个能力:

  • 能不能区分对称加密 vs 哈希——「我把密码加密了存数据库」和「我把密码哈希了存数据库」根本不是一回事——搞不清这一层,候选人 90% 写过用 AES 加密用户密码的代码(典型生产事故);
  • 能不能讲清当代密码方案为啥是 BCrypt / Argon2,不是 MD5 + Salt——MD5 早被淘汰,光说”加盐”不够;
  • 能不能把存储 + 传输全链路串起来讲——只讲存储一半分,这道题真正考”完整安全意识”。

按这三档,下面把答案分成 30 / 60 / 90 分。

30 分答案:能说出”哈希不可逆”

最简短答案:因为服务端也不知道你的原密码是什么——存原密码的程序员已经被开了。

如果服务端知道你的原密码,那就是严重的安全事故:

  • ❌ 数据库被脱库——所有用户密码裸奔;
  • ❌ 内部员工有 DB 权限——可以查到你密码登录你账号;
  • ❌ 黑客打进来后获取所有用户密码——你所有用同样密码的网站全部沦陷。

所以服务端绝对不能存明文——必须先做哈希再存。

哈希的两个关键特性:

特性 含义 在密码场景的作用
不可逆 从哈希值算不出原值 服务端永远不知道你密码
确定性 同样输入永远同样输出 验证时只比哈希值就行

比喻:密码哈希就像切土豆丝——能从土豆切成丝、不能从丝复原成土豆。验证密码的方式是把你新输的密码也切成丝,看看两盘丝长不长得一样。

哈希算法效果演示

30 分线就到这——能讲清”哈希不可逆 + 服务端不存明文”足够过基础线。但只答到这就停的话,面试官当场摇头——这是大学课本水平。

60 分答案:MD5 早被淘汰,加盐 + 慢哈希才是当代方案

到 60 分线,要能讲清为什么不能只用 MD5 + Salt——以及当代方案到底用什么。

为什么 MD5 早被淘汰

问题 后果
抗碰撞性差 不同输入可能哈希出同样值——理论上找替代密码
哈希值短(128 位) 彩虹表攻击直接秒破常见密码
计算速度太快 暴力破解每秒能跑亿级别——8 位以内密码秒破

关键的反直觉点:密码哈希算法越「慢」越好——不是 bug,是 feature。MD5 设计目标是快,所以它根本就不适合做密码哈希。

Salt(盐)解决什么

光哈希不加盐 → 彩虹表攻击直接拿下。彩虹表是预先算好的”常见密码 → 哈希值”对照表——攻击者拿到数据库的 MD5 值后查表就能反查出密码。

加盐做了什么:

  • ✅ 每个用户的盐都不同 → 同样的密码哈希出不同值;
  • ✅ 攻击者没法预先算彩虹表(每个用户都得单独算);
  • ✅ 即使两个用户密码一样,哈希值也不同。

当代方案:BCrypt / Argon2(慢哈希)

// Spring Security 官方推荐方式
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

BCrypt 内置了 salt + cost 双保险:

字段 作用
salt 自动生成的随机字符串(每个用户都不同)
cost 控制迭代次数——cost=10 就要算 1024 次、cost=12 算 4096 次

核心思想:让单次密码哈希故意变慢(比如 100ms),用户登录感觉不到——但攻击者每秒能跑的数量从亿级降到几十次——爆破成本指数级上升。

能讲到这就到 60 分线——MD5 / Salt / BCrypt 三件套讲清。

90 分答案:按 OWASP 标准给完整链路

到 90 分线要把完整安全链路串起来——但优先级要按 OWASP 来排:HTTPS 是底座、慢哈希存储是必做、客户端加密只在特定场景下补一层——三者权重完全不同。

一个常见的诡问:「员工知道加密方式岂不是能拦包破解?」

这是个埋了陷阱的追问。正确答法:传输安全靠 TLS、存储安全靠慢哈希——员工知道存储算法反查不出原密码(哈希不可逆),知道传输协议也解不开抓包(除非他能拿到 TLS 私钥)。

OWASP 推荐的优先级:

🥇 必做:HTTPS / TLS 全链路加密
↓ (传输底座、不可省)
🥇 必做:服务端慢哈希存储(Argon2id 优先 / BCrypt / PBKDF2)
↓ (脱库后唯一防线)
🥈 可选:客户端密码加密(特定场景的附加层)

逐层讲清楚:

对称加密示意

非对称加密示意

第 1 层(必做):HTTPS / TLS 全链路加密

HTTPS 是安全底座、不可省——Strict-Transport-Security 强制 HTTPS、固定域名 + 证书校验、关闭 SSL/早期 TLS 协议。

OWASP 的硬性建议(TLS Cheat Sheet):TLS 1.2+ / 强加密套件 / 启用 HSTS preload / 服务端拒绝降级——做完这些,普通中间人就拿不到明文了。

第 2 层(必做):服务端慢哈希存储

服务端拿到密码后立即慢哈希、绝不能直接存——Argon2id 是 OWASP 当前首选,BCrypt / PBKDF2 是可接受替代:

// Spring Security 6 推荐:DelegatingPasswordEncoder + 多算法兼容
@Bean
public PasswordEncoder passwordEncoder() {
// createDelegatingPasswordEncoder() 默认编码为 BCrypt
// 想改成 Argon2id?需要显式配置 idForEncode + 提供 Argon2PasswordEncoder
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}

关键点:这一层即使数据库被脱、即使内部员工有 DB 权限——他们拿到的只是哈希值、反查不出原密码。

第 3 层(可选):客户端加密——特定场景的附加防线

⚠️ 重点:客户端加密 ≠ 替代 HTTPS、也不是 90 分必备——它只在特定场景下加一层兜底:

真有用的场景 防不了的场景
应用日志可能落明文(防开发 / 运维误抓) 前端 JS 被 XSS 或篡改——客户端整个被控、加密毫无意义
内网链路 / 第三方网关有解 TLS 的能力 MITM 攻击——HTTPS 都被破了,客户端公钥可能也被篡改
强合规场景(金融 / 医疗)的纵深防御 密钥换发——公钥过期 / 撤销策略要单独设计
// 仅在你确实有上面那种场景时再做:
const encryptedPassword = rsaEncrypt(password, publicKey);

实践建议:普通业务系统做好前 2 层就足够——别先想客户端加密、先把 HTTPS 配置 + 慢哈希算法做正确——这是 OWASP 反复强调的优先级。

重置密码时怎么判断”新密码 = 旧密码”

很多网站重置时会提示「新密码不能和旧密码一样」——但服务端不知道你旧密码啊?

实际原理:

  1. 用户输入新密码;
  2. 服务端用该用户的盐算新密码哈希值;
  3. 拿新哈希 vs 数据库里旧密码的哈希比对;
  4. 一样 → 拒绝修改。

还是在比”两盘土豆丝”——服务端从头到尾都不知道你旧密码是什么。

登录验证 + 密码重置流程

90 分线就到这——能把「HTTPS 底座 + 慢哈希存储 + 按场景加客户端加密」按 OWASP 优先级讲清就是 90 分

直接掉分的几种答法

  • ❌ “用 AES 加密密码存数据库,需要时解密” → 直接拒——这是典型的没有安全意识。AES 是对称加密、能解密 = 服务端知道你原密码 = 数据库一脱所有密码全裸。
  • ❌ “MD5 + 盐就够了” → -50 分——MD5 在 2010 年后已经不被推荐,主流方案是 BCrypt / Argon2。
  • ❌ 只讲存储不讲传输 → 顶多 60 分——这道题考的是完整安全意识,光讲一半就漏了一半。
  • ❌ “客户端必须 RSA 加密、不然就不安全” → 这是常见的”过度纵深”——HTTPS 配好 + 服务端慢哈希才是 OWASP 强制项,客户端加密是特定场景的补充——说成必备会被安全同事挑刺。
  • ❌ “我们用 SHA-256 + 盐,性能好” → 这个答案在 2010 年是对的、今天是错的——SHA-256 是通用哈希、不是密码哈希——速度太快、容易爆破——密码哈希就是要慢。

高频追问怎么接

追问 1:BCrypt 的 cost 参数怎么选?

OWASP 直接说没有黄金规则、必须按服务器压测——核心约束是单次哈希要明显低于 1 秒、用户登录不会卡顿,至于具体多少、需要平衡你机器的负载和能接受的登录延迟。

怎么选:

  • 拿生产同等机型压一下——cost=10 / 11 / 12 / 13 各跑一万次、看平均耗时;
  • 业界常见的工程经验是落在 250-500ms 区间——给登录留一点反应时间但又不卡用户;
  • 每年复盘一次——CPU 性能跟着摩尔定律涨,同样的 cost 时间会越跑越短,到时候 cost +1 重新压测。

现成起点(仅作参考、生产必须自己压):BCrypt cost=12 在主流服务器上约 200-400ms 之间。

追问 2:为啥不用 Argon2 而用 BCrypt?

Argon2 更现代——2015 年密码哈希竞赛冠军,理论上比 BCrypt 更抗 GPU 爆破。但 BCrypt 已经在 Spring Security 默认推荐 + 全球大量项目在用——生态成熟、踩坑少。

新项目:可以直接上 Argon2(Spring Security 也支持);老项目:BCrypt 先用着、没有强动机别迁。

追问 3:盐应该多长?

至少 16 字节(128 位)随机。BCrypt 自动生成 22 个 base64 字符的盐——够长、随机、和哈希值绑在一起存——不用单独建盐字段。

追问 4:用 AI 写代码怎么避免它存明文密码?

这是 AI Coding 时代的实战题——AI 经常会生成「user.setPassword(req.getPassword()) 然后存」这种灾难代码。

修法 3 步:

  1. 明确 prompt:「用 BCryptPasswordEncoder 哈希后存储,绝不存明文」;
  2. 配 lint 规则:禁止任何 setter 直接传入用户密码字段;
  3. Code Review 必查:所有 password 字段写入路径必须经过 PasswordEncoder。

我的判断

这道题答到 30 分容易、答到 90 分要把哈希 / 慢哈希 / HTTPS / 何时加客户端加密这几个概念按 OWASP 优先级讲清——很多人答完看着对、被一句”为什么客户端加密反而不该是 90 分必备”就破功。

面试时的 3 步走法:

  1. 第一句话甩”哈希不可逆” → 过基础线;
  2. 第二段讲慢哈希(Argon2id / BCrypt)+ cost 按服务器压测 → 显示当代知识;
  3. 第三段按 OWASP 优先级讲完整链路(HTTPS 必做 → 慢哈希必做 → 客户端加密看场景) → 90 分线,被追问也不慌。

说到底:密码安全不是「做得越多越安全」——是按 OWASP 优先级把必做项做扎实。HTTPS 配置 + 慢哈希算法这两关做对、密码存储就稳一大半——剩下的客户端加密、定期 cost 压测、PasswordEncoder 升级——是锦上添花不是雪中送炭。

最后一个真心建议——你自己日常别在所有网站用同一个密码。再厉害的服务端哈希,也救不了你「一个网站泄漏 → 你所有账号沦陷」这种自己挖的坑。装个密码管理器,每个网站不同密码——这是 2026 年程序员该有的安全自觉。

文章目录
  1. 1. 这道题面试官真正想筛什么
  2. 2. 30 分答案:能说出”哈希不可逆”
  3. 3. 60 分答案:MD5 早被淘汰,加盐 + 慢哈希才是当代方案
    1. 3.0.1. 为什么 MD5 早被淘汰
    2. 3.0.2. Salt(盐)解决什么
  • 4. 90 分答案:按 OWASP 标准给完整链路
    1. 4.0.1. 一个常见的诡问:「员工知道加密方式岂不是能拦包破解?」
    2. 4.0.2. 第 1 层(必做):HTTPS / TLS 全链路加密
    3. 4.0.3. 第 2 层(必做):服务端慢哈希存储
    4. 4.0.4. 第 3 层(可选):客户端加密——特定场景的附加防线
    5. 4.0.5. 重置密码时怎么判断”新密码 = 旧密码”
    6. 4.0.6. 90 分线就到这——能把「HTTPS 底座 + 慢哈希存储 + 按场景加客户端加密」按 OWASP 优先级讲清就是 90 分
  • 5. 直接掉分的几种答法
  • 6. 高频追问怎么接
    1. 6.0.1. 追问 1:BCrypt 的 cost 参数怎么选?
    2. 6.0.2. 追问 2:为啥不用 Argon2 而用 BCrypt?
    3. 6.0.3. 追问 3:盐应该多长?
    4. 6.0.4. 追问 4:用 AI 写代码怎么避免它存明文密码?
  • 7. 我的判断