当前位置: 首页 > news >正文

密码与加密基础篇(4):bcrypt / Argon2id 为什么比 MD5 更适合存密码?

前面几篇我们已经把几个基础问题讲清楚了:
密码与加密基础篇(1):别再说 MD5 加密了:编码、摘要、加密到底有什么区别?-CSDN博客
密码与加密基础篇(2):密码到底怎么存?为什么 MD5 已经过时?-CSDN博客
密码与加密基础篇(3):salt 和 pepper 到底是什么?为什么 salt 可以放数据库?-CSDN博客

第1篇:MD5 不是加密,而是摘要 / 哈希 第2篇:密码不能明文存,也不应该简单 MD5 存 第3篇:salt 是公开随机扰动,pepper 是服务端私有秘密

这一篇继续往下讲一个更核心的问题:

为什么 bcrypt / Argon2id 比 MD5 更适合存密码?

很多老项目里,密码存储可能还是这样:

String passwordHash = md5(rawPassword);

或者稍微增强一点:

String passwordHash = md5(rawPassword + salt);

这套方案能跑,也确实比明文存密码好。

但从现代密码安全角度看,它已经不够了。

不是因为 MD5 不能生成 hash,而是因为:

MD5 太快了,而密码哈希恰恰不能追求快。

这句话是理解 bcrypt / Argon2id 的关键。


一、先说结论:密码哈希不是越快越好

我们平时做程序,经常追求快:

接口要快 查询要快 缓存要快 算法要快 启动要快

但是密码存储这个场景很特殊。

密码哈希不能一味追求快。

因为密码哈希面对的不是普通业务计算,而是攻击者的离线破解。

如果数据库泄漏,攻击者拿到:

username salt password_hash

他就可以在自己的机器上疯狂猜密码:

hash(123456 + salt) hash(111111 + salt) hash(password + salt) hash(qwerty + salt)

然后和数据库里的password_hash比较。

这个过程不需要再请求你的服务器。

所以服务端的登录限流、验证码、风控,对这种离线破解帮助有限。

这时真正影响安全性的,是攻击者每猜一次密码的成本。

MD5 的问题就在这里:

它太快了。

而 bcrypt / Argon2id 的设计目标,就是让每次猜测都更贵。


二、MD5 的问题不是“不能 hash”,而是“太适合被暴力猜”

MD5 当然可以把密码变成一个摘要值。

比如:

123456 ↓ MD5 e10adc3949ba59abbe56e057f20f883e

它也不可逆。

也就是说,不能直接从:

e10adc3949ba59abbe56e057f20f883e

解密回:

123456

但是攻击者不需要解密。

攻击者只需要猜。

比如他提前准备常见密码表:

123456 111111 12345678 password qwerty admin123

然后逐个计算 MD5。

如果算出来的结果和数据库里一样,就说明猜中了。

所以很多人说“MD5 被破解了”,严格来说不一定是被解密,而是被猜中了。

密码越弱,越容易被猜中。

MD5 越快,攻击者猜起来越便宜。

这才是核心问题。


三、加 salt 以后,MD5 还不够吗?

加 salt 比不加 salt 好。

比如不用 salt:

用户A:MD5(123456) = xxx 用户B:MD5(123456) = xxx

两个用户密码一样,hash 也一样。

攻击者还能用通用彩虹表快速匹配。

加了 salt 后:

用户A:MD5(123456 + saltA) = hashA 用户B:MD5(123456 + saltB) = hashB

即使两个人密码一样,最终 hash 也不同。

这很好。

但是还不够。

因为数据库泄漏时,salt 通常也在数据库里。

攻击者拿到:

salt = abc001 password_hash = xxxxxx

他仍然可以针对这个 salt 猜:

MD5(123456 + abc001) MD5(111111 + abc001) MD5(password + abc001)

salt 的作用是让攻击者不能用一张通用表横扫所有用户。

但 salt 不能解决 MD5 太快的问题。

所以:

MD5 + salt 比 MD5 好,但仍然不是现代密码存储的推荐方案。


四、密码哈希到底需要什么能力?

密码哈希和普通文件摘要不一样。

普通文件摘要追求的是:

计算快 结果稳定 方便校验

比如:

MD5(file) SHA-256(file)

用来判断文件有没有损坏、有没有被修改。

但密码哈希追求的是:

不可逆 每个用户独立 salt 计算成本可调 抗暴力猜测 抗批量破解

所以密码哈希需要“慢”。

准确说,不是无脑慢,而是:

对正常用户来说,可以接受。 对攻击者批量猜测来说,成本很高。

正常用户登录一次,密码校验多花几十毫秒、几百毫秒,通常可以接受。

但攻击者如果要猜几千万、几亿次,这个成本就会被无限放大。

这就是 bcrypt / Argon2id 的价值。


五、什么是慢哈希?

慢哈希就是专门为密码存储设计的哈希方案。

它和 MD5 / SHA-256 这类快速哈希的区别是:

MD5 / SHA: 适合快速摘要、文件校验、完整性校验。 bcrypt / Argon2id / PBKDF2: 适合密码存储,故意提高计算成本。

也就是说,慢哈希不是算法写得差,而是它故意让计算变贵。

它的目标不是服务普通摘要场景,而是对抗密码破解场景。

一句话:

密码哈希慢,不是缺点,而是安全特性。


六、bcrypt 是什么?

bcrypt 是一种成熟的密码哈希算法。

它不是加密,不能解密。

它适合存密码,因为它有几个关键特点:

1. 不可逆 2. 自带 salt 3. cost 成本可调 4. 计算速度故意变慢 5. 生态成熟,工程落地简单

bcrypt 生成的结果一般长这样:

$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy

这串内容不是单纯的 hash。

它里面通常包含:

算法版本 cost 参数 salt 最终 hash 结果

所以使用 bcrypt 时,一般不需要自己额外保存 salt 字段。

框架会自动处理。


七、bcrypt 的 cost 是什么?

bcrypt 里有一个很重要的参数:

cost

可以理解为计算成本。

cost 越高,计算越慢。

比如:

cost = 10 cost = 12 cost = 14

数值越高,密码哈希和验证所需时间越长。

这对正常用户来说,影响的是一次登录校验。

但对攻击者来说,影响的是每一次猜测。

如果攻击者要猜一千万次,那么每次成本增加一点,总成本就会非常夸张。

这就是 cost 的意义:

让系统可以随着硬件性能提升,逐步提高密码破解成本。


八、bcrypt 在 Spring Security 里怎么用?

Spring Security 里常见写法是:

@Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }

注册时:

public void register(RegisterRequest request) { String rawPassword = request.getPassword(); String encodedPassword = passwordEncoder.encode(rawPassword); User user = new User(); user.setUsername(request.getUsername()); user.setPassword(encodedPassword); userMapper.insert(user); }

登录时:

public boolean login(LoginRequest request) { User user = userMapper.findByUsername(request.getUsername()); if (user == null) { return false; } return passwordEncoder.matches( request.getPassword(), user.getPassword() ); }

重点是:

passwordEncoder.matches(rawPassword, encodedPassword)

这里不是解密。

bcrypt 不能解密。

matches()的本质是:

从 encodedPassword 里解析出算法信息、cost、salt ↓ 用用户输入的 rawPassword 重新计算 ↓ 比较结果是否一致

所以密码验证不需要还原明文。


九、Argon2id 是什么?

Argon2 是新一代密码哈希算法家族。

其中 Argon2id 是更推荐用于密码存储的变体之一。

你可以先这样理解:

bcrypt: 成熟、稳定、工程生态好。 Argon2id: 更新、更强,除了 CPU 成本,还强调内存成本。

MD5 快,主要问题是攻击者可以用 GPU、专用硬件进行大规模并行计算。

bcrypt 通过 cost 增加计算成本。

Argon2id 更进一步,它不仅关注计算时间,还关注内存消耗。

也就是说,攻击者不是只要拼算力,还要拼内存。

这会让大规模并行破解变得更贵。


十、Argon2id 为什么强调内存成本?

攻击者破解密码时,常见思路是批量并行。

比如同时猜很多密码。

如果一个算法只消耗 CPU,而且非常快,那么攻击者可以通过大量硬件并行计算。

Argon2id 引入内存成本后,每次计算都需要占用一定内存。

这样攻击者想大规模并行,就不只是 CPU 问题,还会遇到内存成本问题。

所以 Argon2id 的目标是:

提高时间成本 提高内存成本 提高并行破解成本

这就是它比传统快速哈希更适合密码存储的原因。


十一、bcrypt 和 Argon2id 怎么选?

工程里不要陷入绝对化。

可以这样看:

1. 普通 Spring Boot / Spring Security 项目

优先用 bcrypt。

原因很简单:

生态成熟 团队容易理解 Spring Security 支持好 落地成本低 线上经验多

对大多数普通业务系统来说,bcrypt 已经比 MD5 好很多。

2. 新系统、安全要求更高

可以考虑 Argon2id。

比如:

金融 医疗 政企 高价值账户体系 安全要求较高的新系统

Argon2id 的安全设计更新,也更强调抗硬件并行破解。

3. 兼容性、合规、老系统场景

PBKDF2 也可以考虑。

PBKDF2 的优势是:

标准化时间久 兼容性强 一些平台和合规场景支持好

所以不是只有一个正确答案。

更重要的是:

不要再用 MD5 当密码存储方案。

十二、bcrypt / Argon2id / PBKDF2 对比

可以用这张表理解:

算法是否适合密码存储主要特点工程建议
MD5不推荐太快,容易被暴力猜测不要用于新系统密码存储
SHA-256不推荐直接用也是快速哈希不要直接 hash 密码
MD5 + salt不推荐比裸 MD5 好,但仍然太快老系统过渡方案
PBKDF2可用多轮迭代,兼容性好合规、兼容场景可选
bcrypt推荐自带 salt,cost 可调,成熟普通项目优先选择
Argon2id推荐时间成本 + 内存成本高安全新系统优先考虑

记住:

MD5 的问题不是不能哈希,而是太快;bcrypt / Argon2id 的价值,是让攻击者猜密码的成本变高。


十三、密码哈希参数怎么定?

很多人会问:

bcrypt cost 设置多少合适?
Argon2id 内存参数设置多少合适?

这个没有一个脱离业务的固定答案。

因为它和服务器性能、并发量、登录接口压力、安全等级都有关系。

原则是:

在用户体验和服务器承载能力可接受的范围内,让密码校验尽量贵。

不要盲目设置过高。

如果 cost 太高,可能会导致:

登录接口变慢 服务器 CPU 压力变大 高并发登录时被打爆 攻击者没打进来,自己先扛不住

所以实际项目里应该:

本地压测 测试环境压测 结合登录 QPS 结合服务器资源 选择一个可接受的成本参数

安全不是只看算法名,也要看参数。

同样是 bcrypt:

cost 太低,安全性不足。 cost 太高,系统扛不住。

需要平衡。


十四、为什么不能用 AES 加密密码存数据库?

有些人会说:

那我不用 MD5,我用 AES 把密码加密存数据库不行吗?

不推荐。

因为 AES 是可逆加密。

如果后端能解密出用户原始密码,就说明系统存在一把可以还原所有密码的密钥。

一旦这个密钥泄漏,所有用户密码都可能被还原。

而密码存储的原则是:

系统不应该有能力还原用户原始密码。

所以密码应该用不可逆的密码哈希算法,而不是可逆加密。

正确方向是:

密码: bcrypt / Argon2id / PBKDF2 Token: AES-GCM + Android Keystore

因为密码不需要还原。

Token 后续还要拿出来请求接口,所以 Token 需要可逆加密。

这两个不要混。


十五、客户端要不要 bcrypt 后再传给后端?

一般不需要。

App 登录时通常是:

用户输入原始密码 ↓ HTTPS/TLS 加密传输 ↓ 后端收到原始密码 ↓ 后端使用 bcrypt / Argon2id 校验

如果客户端先 bcrypt 再传,后端又认可这个 bcrypt 值登录,那么这个 bcrypt 值本身就会变成“等价密码”。

攻击者拿到这个值,仍然可能直接登录。

所以客户端不应该把“先 hash 一下”当成传输安全方案。

传输安全靠 HTTPS。

密码存储安全靠后端 bcrypt / Argon2id。

客户端应该做的是:

不保存密码 不打印密码 不把密码放 URL 不把密钥写死到 APK 通过 HTTPS 请求登录接口

十六、老项目 MD5 怎么迁移到 bcrypt?

如果数据库里已经是:

MD5(password)

或者:

MD5(password + salt)

不能直接把历史数据改成 bcrypt。

因为你没有用户原始密码。

正确做法是:

兼容旧算法 登录成功后升级

流程:

用户登录 ↓ 判断数据库密码格式 ↓ 如果是旧 MD5,就用旧逻辑校验 ↓ 校验成功后,拿用户本次输入的原始密码 ↓ 重新生成 bcrypt ↓ 更新数据库

这样用户无感知,系统逐步升级。


十七、建议给密码字段加算法标识

为了支持迁移,数据库里的密码值最好能看出算法。

比如:

{md5}e10adc3949ba59abbe56e057f20f883e {bcrypt}$2a$10$xxxxxxxxxxxxxxxxxxxx

登录时:

public boolean login(String username, String rawPassword) { User user = userMapper.findByUsername(username); if (user == null) { return false; } String storedPassword = user.getPassword(); if (storedPassword.startsWith("{md5}")) { String oldHash = storedPassword.replace("{md5}", ""); if (oldHash.equalsIgnoreCase(md5(rawPassword))) { String newHash = "{bcrypt}" + passwordEncoder.encode(rawPassword); userMapper.updatePassword(user.getId(), newHash); return true; } return false; } if (storedPassword.startsWith("{bcrypt}")) { String bcryptHash = storedPassword.replace("{bcrypt}", ""); return passwordEncoder.matches(rawPassword, bcryptHash); } return false; }

这个思路很重要:

不要指望一次性把所有历史密码升级掉,而是让用户登录成功时顺手升级。

如果安全风险比较高,也可以要求用户重置密码。


十八、Spring Security 的 DelegatingPasswordEncoder 思路

Spring Security 里有一种很适合多算法兼容的设计:

DelegatingPasswordEncoder

它的思想是:

通过前缀标识算法 根据算法选择对应 PasswordEncoder

类似:

{bcrypt}$2a$10$xxxx {pbkdf2}xxxx {noop}xxxx

这样系统就能支持多种历史密码格式。

这对老系统迁移非常有用。

核心思想可以借鉴到自己的认证系统里:

密码字段不要只存 hash。 最好能知道这个 hash 是用什么算法生成的。

十九、bcrypt / Argon2id 不是万能的

虽然 bcrypt / Argon2id 比 MD5 更适合存密码,但它们不是万能药。

它们主要解决的是:

数据库泄漏后,攻击者离线破解密码的成本问题。

它们不能直接解决:

用户密码太弱 登录接口被在线爆破 密码被日志打印 客户端把密码存本地 Token 泄漏 钓鱼网站骗密码 撞库攻击

所以完整认证安全还需要:

HTTPS 密码不打日志 登录失败限流 验证码 MFA / 二次验证 弱密码限制 改密后 Token 失效 异常登录风控 Token 安全存储

安全不是单靠一个算法解决的。


二十、常见误区

误区 1:MD5 不可逆,所以很安全

不对。

不可逆只是基本要求。

MD5 太快,容易被批量猜测。


误区 2:MD5 + salt 就够了

不够。

salt 解决相同密码同 hash 和预计算表问题,但 MD5 仍然太快。


误区 3:bcrypt 是加密算法

不准确。

bcrypt 是密码哈希算法,不是加密算法,不能解密。


误区 4:密码应该加密存,登录时解密比较

不推荐。

密码不应该可逆存储。

应该用不可逆密码哈希。


误区 5:客户端先 hash 一下就安全了

不对。

客户端 hash 值可能变成等价密码。

传输安全应该靠 HTTPS。


误区 6:用了 bcrypt 就不需要其他安全措施

不对。

bcrypt 解决的是密码存储问题,不解决所有认证安全问题。


二十一、最终总结

bcrypt / Argon2id 为什么比 MD5 更适合存密码?

因为密码存储的核心不是简单生成一个摘要,而是提高攻击者猜密码的成本。

MD5 的问题是:

太快 适合文件校验 不适合密码存储 即使加 salt,仍然容易被离线暴力猜测

bcrypt 的优势是:

自带 salt cost 可调 计算成本可控 工程成熟 Spring Security 落地简单

Argon2id 的优势是:

更新一代密码哈希方案 同时强调时间成本和内存成本 更适合高安全新系统

如果压缩成一句话:

MD5 适合做快速摘要,bcrypt / Argon2id 适合做密码哈希;密码哈希不是要快,而是要让攻击者猜不起。

再放到整个认证体系里:

登录传输靠 HTTPS。 密码存储靠 bcrypt / Argon2id / PBKDF2。 salt 让每个用户的 hash 独立。 pepper 作为服务端秘密增强保护。 Token 本地存储靠 AES-GCM + Android Keystore。 Token 失效靠后端 Redis / jti / tokenVersion。

这套分工清楚了,密码安全和 Token 安全就不会再混在一起。

http://www.jsqmd.com/news/1181589/

相关文章:

  • 选购指南:2026 闭环低温蒸发机组厂家哪家靠谱?实力厂家汇总推荐 - 商业新知
  • 知乎算法已悄然升级!2024Q2真实抓取日志显示:ChatGPT生成内容需满足这8项隐性质量阈值(稀缺版检测工具已开源)
  • Kimi-K2-Thinking-MXFP4-AttnFP8量化技术详解:MXFP4与FP8混合精度优化
  • 深入机器人碰撞检测:Oriented Bounding Boxes (OBB)的实现与应用
  • 智慧金融——解读129页银行风险预警管理体系规划方案【附全文阅读】
  • 【WorkBuddy专栏18】WorkBuddy API深度解析——打造开发者友好的AI生态
  • 10分钟上手Vue Carousel:从安装到实现第一个轮播的快速教程
  • 计算机毕业设计之jsp社区医疗信息服务管理系统
  • Cursor格式化配置失效?90%开发者忽略的4个隐藏参数正在拖慢你的开发效率
  • 3分钟免费解锁WeMod专业版:Wand-Enhancer完整破解教程
  • Vue Carousel源码探秘:核心实现原理与性能优化技巧
  • Wand-Enhancer终极指南:如何免费解锁WeMod Pro高级功能
  • 2026年青岛全案整装公司推荐:高品质整装服务深度测评 - 装修新知
  • Phi-3-mini-4k-instruct_rai_1.7.1_npu_4K震撼发布:AMD Ryzen AI NPU部署的终极解决方案
  • 密码与加密基础篇(5):AES 和 RSA 到底怎么配合?一次讲清 App 接口混合加密
  • 揭秘Gemma-4-E4B-IT-OptiQ-4bit的6领域校准混合策略:提升量化质量的关键技术
  • Linux 面试题2
  • Wand-Enhancer完全指南:解锁WeMod高级功能的终极开源增强工具
  • 计算机毕业设计之基于springboot实现的个人健康管理系统
  • Mistral-7B-Instruct-v0.3_rai_1.7.1_npu_4K内存优化:4096序列长度下的KV缓存管理终极指南
  • 为什么选择Nemotron-3进行内容安全检测?对比传统方法的10大优势
  • YOLOv8主动学习:优化数据标注与模型性能
  • 最新北京婚恋服务口碑排行 附真实用户反馈汇总 - 信息热点
  • 图神经网络 (GNN) 核心机制:从消息传递到工业级应用
  • HTTPS安全认证体系详解:SSL/TLS证书验证机制与部署实践
  • ChatGPT写代码总出错?揭秘92%开发者忽略的5个上下文对齐漏洞(附可复用调试模板)
  • Palworld存档工具“非压缩存档“错误诊断与解决方案
  • 动态规划:最少硬币凑金额问题详解
  • MXFP4与FP8双量化技术实战:gpt-oss-120b模型优化完整教程
  • 从原理到实践:深度解析特征图融合与拼接的核心技术与场景选择