Appearance
Spring 中的加密、解密与密码存储
很多人一提到安全里的“加密”,第一反应往往是:把数据变成看不懂的字符串。
这个理解不算错,但还远远不够。
真放到 Spring 应用里看,加密、解密通常都会落到下面这些问题上:
- 敏感数据该不该明文存储
- 密码为什么不能直接加密后再解密校验
- 配置文件里的密钥怎么管理
- 接口传输、数据库落盘、日志输出,分别该保护什么
这篇文章重点讲:
- 加密、解密、哈希到底分别是什么意思
- Spring 项目里最常见的加密场景有哪些
- 密码存储为什么更推荐哈希而不是可逆加密
PasswordEncoder、BCrypt这些词到底在说什么- 工程里真正容易踩的坑是什么
1. 把几个最容易混的词分开
1.1 加密
加密 可以看成 把明文按一定规则变成密文,只有持有正确密钥的人才能还原。
它解决的是 数据即使被别人拿到,也不能直接看懂内容。
1.2 解密
解密 可以理解成 把密文还原回明文。
这意味着:加密和解密通常是一对可逆过程。
1.3 哈希
哈希 不是“加密”,而是:把输入按算法映射成固定长度摘要。
它和加密最关键的区别是:哈希通常不强调可逆。
这也是为什么密码存储更常讨论:
MD5SHABCryptArgon2
而不是把密码“解密回来”。
1.4 一句话区分
把这一层先分开:
- 加密 / 解密:可逆,重点是保密传输和保密存储
- 哈希:不可逆,重点是校验和安全存储
2. Spring 应用里最常见的加密场景
如果把企业项目里的常见场景拆开看,通常会有这几类:
- 用户密码存储
- 敏感配置保护
- 接口字段加密
- 数据库存储前加密
- token、签名、摘要校验
要注意的是:不是所有敏感数据都用同一种方式处理。
例如:
- 密码更适合哈希存储
- 身份证号、手机号这类需要后续展示或使用的数据,才更可能需要可逆加密
- 接口签名更常是摘要或 HMAC,而不是简单“加密一下”
2.1 常见加密方案到底该怎么比较
很多人在看安全方案时,最容易掉进一个坑:把 MD5、BCrypt、AES、RSA 这些词直接放到一张表里横着比,然后想找出一个“最强算法”。
这样比通常会越比越乱。
因为它们解决的问题本来就不完全一样:
MD5、SHA-256、BCrypt、Argon2更偏摘要或密码存储AES更偏可逆加密RSA、ECC更偏密钥交换、签名或少量数据加密HMAC更偏消息完整性校验和接口签名
所以更稳妥的比较方式是:
- 看它是不是可逆
- 再看它解决的是“密码存储”“字段加密”还是“签名验签”
- 最后再比较安全性、性能和工程成本
🌟 可以直接记住下面这几个选型结论:
- 密码存储:优先考虑
Argon2、BCrypt、PBKDF2 - 业务字段可逆加密:优先考虑
AES - 接口签名或消息认证:优先考虑
HMAC-SHA256 - 非对称签名或密钥交换:常见是
RSA或ECC
2.2 密码存储相关方案对比
看最容易混的这一组:它们很多都和“密码”有关,但并不是都适合拿来存密码。
| 方案 | 类型 | 是否可逆 | 安全性 | 性能 | 典型场景 | 工程建议 |
|---|---|---|---|---|---|---|
MD5 | 快速摘要算法 | 否 | 很低 | 很高 | 旧系统摘要、非安全去重 | 不要用于密码存储 |
SHA-256 | 快速摘要算法 | 否 | 中 | 很高 | 文件摘要、完整性校验 | 不要单独用于密码存储 |
PBKDF2 | 慢哈希 / 密码派生 | 否 | 高 | 中 | 密码存储、密钥派生 | 兼容性好,很多系统可用 |
BCrypt | 慢哈希 / 密码存储 | 否 | 高 | 中偏低 | 用户密码存储 | Spring 项目默认选择很常见 |
SCrypt | 慢哈希 / 内存开销更高 | 否 | 高 | 低 | 抗暴力破解的密码存储 | 适合高安全场景,但资源开销更大 |
Argon2 | 新一代慢哈希 | 否 | 很高 | 低到中 | 用户密码存储 | 新项目里通常是更先进的选择 |
这张表里最重要的结论不是谁“绝对更强”,而是:
MD5、SHA-256这类“快摘要”不适合直接存密码- 密码存储需要的是“故意更慢”的算法
BCrypt在 Spring 生态里最常见,Argon2在理念上通常更先进
更直白一点说:密码存储不是追求算得快,而是追求攻击者算得慢。
2.3 可逆加密、签名与非对称方案对比
另一组常见方案则更偏“字段保密”“签名验签”“密钥交换”。
| 方案 | 类型 | 是否可逆 | 安全性 | 性能 | 典型场景 | 工程建议 |
|---|---|---|---|---|---|---|
AES | 对称加密 | 是 | 高 | 很高 | 数据库字段加密、配置加密、文件加密 | 最常见的可逆加密方案 |
RSA | 非对称加密 / 签名 | 部分可逆 | 高 | 低 | 数字签名、密钥交换、少量数据加密 | 不适合直接加密大体积业务数据 |
ECC | 非对称加密 / 签名 | 部分可逆 | 高 | 中 | 签名、密钥交换、移动端或资源敏感场景 | 更小密钥下可获得较高安全性 |
HMAC-SHA256 | 消息认证码 | 否 | 高 | 很高 | 接口签名、请求防篡改、服务间验签 | 它不是加密,而是完整性和来源校验 |
这里有几个特别容易误解的点:
AES适合做真正的数据加密,因为它快,而且适合处理大量数据RSA、ECC更常用来做签名、验签和密钥交换,而不是直接加密整段大文本HMAC-SHA256不是“把内容藏起来”,而是证明“内容没被改、请求确实来自持有密钥的一方”
2.4 如果按“安全性、性能、场景”三维来选
如果只想快速做工程判断,看这张压缩表:
| 需求 | 更适合的方案 | 为什么 |
|---|---|---|
| 用户密码存储 | BCrypt / Argon2 / PBKDF2 | 不可逆,且故意较慢,更适合抗暴力破解 |
| 手机号、身份证号等字段加密 | AES | 可逆、性能高、适合批量字段处理 |
| 接口签名、防篡改 | HMAC-SHA256 | 快、实现简单、适合请求验签 |
| 公私钥签名、证书体系 | RSA / ECC | 适合签名验签和密钥交换 |
| 大文件或大报文直接加密 | AES | 非对称加密太慢,不适合大数据量 |
🌟 真正落到工程里,通常不是“全站只用一个算法”,而是:
- 密码用慢哈希
- 字段保密用对称加密
- 验签和证书体系用非对称算法
- 接口防篡改用摘要或
HMAC
3. 为什么密码存储不该用“可逆加密”
这是企业开发里最常见的误区之一。
很多人会想:用户密码存库前先 AES 加密,登录时再解密对比,不就行了吗。
真正的问题在于:只要系统能把密码解回来,说明某个地方一定持有解密能力。
一旦密钥泄漏,所有密码都可能一起暴露。
所以密码存储更稳妥的思路通常是:
- 不保存原文
- 不要求能解密回原密码
- 只在用户登录时,把输入密码做同样规则处理后再比较
这就是为什么密码存储更推荐:哈希 + 盐值 + 慢哈希算法
4. PasswordEncoder 到底在做什么
在 Spring Security 里,密码处理最常见的入口是:PasswordEncoder
它可以看成 Spring 对密码编码、校验这件事做的一层统一抽象。
它最常见的两个动作是:
encode(rawPassword):把原始密码编码后存储matches(rawPassword, encodedPassword):校验用户输入密码是否匹配
例如:
java
@Configuration
public class PasswordConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}业务里常见用法:
java
@Service
public class UserService {
private final PasswordEncoder passwordEncoder;
public UserService(PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
public void register(String username, String rawPassword) {
String encodedPassword = passwordEncoder.encode(rawPassword);
System.out.println("save user=" + username + ", password=" + encodedPassword);
}
public boolean login(String rawPassword, String encodedPassword) {
return passwordEncoder.matches(rawPassword, encodedPassword);
}
}要注意的是:登录校验不是“把数据库里的密码解密出来再比较”,而是让用户输入再次经过编码逻辑,然后判断是否匹配。
5. 为什么很多项目默认选 BCrypt
BCrypt 可以看成 专门为密码存储设计的慢哈希算法。
它和普通摘要算法相比,更适合密码存储的原因通常有:
- 自带盐值处理思路
- 计算成本更高
- 更不利于暴力破解和彩虹表攻击
工程上更重要的一点是:密码存储要故意“慢一点”,而不是越快越好。
因为:攻击者一旦拿到密码摘要,越快的算法越利于高频暴力尝试。
6. 如果业务字段真的需要可逆加密,该怎么理解
并不是所有敏感数据都像密码一样不需要解密。
例如:
- 银行卡号
- 身份证号
- 手机号
- 某些第三方密钥
如果系统后续还要展示、调用或回传,就可能需要:可逆加密
在 Java / Spring 项目里,最常见的思路通常是:
- 用对称加密算法,比如
AES - 在进入数据库前先加密
- 在真正需要展示或使用时再解密
一个简化示意:
java
@Service
public class CryptoService {
private static final String ALGORITHM = "AES";
private static final String SECRET = "1234567890abcdef";
public String encrypt(String plainText) throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(SECRET.getBytes(StandardCharsets.UTF_8), ALGORITHM);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
byte[] encrypted = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encrypted);
}
public String decrypt(String cipherText) throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(SECRET.getBytes(StandardCharsets.UTF_8), ALGORITHM);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, keySpec);
byte[] decoded = Base64.getDecoder().decode(cipherText);
return new String(cipher.doFinal(decoded), StandardCharsets.UTF_8);
}
}这个例子只是帮助理解“可逆加密”主线。
真正工程里要继续注意的不是 API 会不会写,而是:
- 密钥不能硬编码在代码里
- 不同数据最好有明确加密边界
- 模式、填充、密钥轮换都要考虑
- 日志和异常输出不能把明文泄漏出去
7. 配置文件里的敏感信息怎么处理
Spring 项目里,另一个很常见的场景是:
- 数据库密码
- 第三方 API Key
- 对称加密密钥
- token 签名密钥
这些信息如果直接明文写进:
application.yml- Git 仓库
- 镜像文件
风险会非常高。
更稳妥的工程习惯通常包括:
- 使用环境变量
- 使用密钥管理系统或配置中心密文能力
- 区分开发、测试、生产环境的密钥来源
- 对密钥做轮换和权限控制
也就是说:真正的安全问题,很多时候不在“算法选错”,而在“密钥管理太随意”。
8. 接口签名和加密是一回事吗
不完全是一回事。
很多接口安全场景里常见会一起出现:
- 加密
- 摘要
- 签名
但它们解决的问题并不一样。
8.1 加密
重点是:别人看不懂内容。
8.2 摘要
重点是:内容有没有被改过。
8.3 签名
重点通常是:请求是不是可信来源发出来的,内容有没有被篡改。
所以接口安全里很常见的组合其实是:
- 传输层靠
HTTPS - 请求身份靠 token 或签名
- 敏感字段再按需要做业务层加密
9. 工程上最容易踩的坑
9.1 误区一:密码也用可逆加密
密码更推荐哈希存储,不应该设计成“能解回来”。
9.2 误区二:把密钥写死在代码里
这类问题在真实项目里非常常见,而且破坏性通常比“算法选错”更直接。
9.3 误区三:只保护数据库,不保护日志
即使数据库里存的是密文,如果日志里把明文打出来,保护就形同虚设。
9.4 误区四:把加密当成所有安全问题的答案
加密只能解决一部分保密问题,不替代:
- 认证
- 授权
- 脱敏
- 审计
- 安全传输
10. 一份更实用的落地建议
如果你在 Spring 项目里真的要处理敏感数据,下面这套习惯通常更稳:
- 密码统一走
PasswordEncoder - 敏感业务字段按是否需要可逆读取来决定哈希还是加密
- 传输层默认使用
HTTPS - 密钥不要硬编码,统一走环境变量或密钥管理系统
- 日志、异常、监控中避免输出明文敏感字段
- 对高敏数据配合数据脱敏、访问控制和审计
11. 总结
把 Spring 应用里的加密、解密与密码存储压缩成最核心的几句话,就是:
- 密码存储更推荐哈希,不推荐可逆加密
- 需要后续读取的敏感业务字段,才更可能需要可逆加密
PasswordEncoder是 Spring 里最常见的密码处理入口- 真正的工程关键不只是算法本身,还包括密钥管理、日志保护和使用边界