Skip to content

Spring 中的加密、解密与密码存储

很多人一提到安全里的“加密”,第一反应往往是:把数据变成看不懂的字符串。

这个理解不算错,但还远远不够。

真放到 Spring 应用里看,加密、解密通常都会落到下面这些问题上:

  1. 敏感数据该不该明文存储
  2. 密码为什么不能直接加密后再解密校验
  3. 配置文件里的密钥怎么管理
  4. 接口传输、数据库落盘、日志输出,分别该保护什么

这篇文章重点讲:

  1. 加密、解密、哈希到底分别是什么意思
  2. Spring 项目里最常见的加密场景有哪些
  3. 密码存储为什么更推荐哈希而不是可逆加密
  4. PasswordEncoderBCrypt 这些词到底在说什么
  5. 工程里真正容易踩的坑是什么

1. 把几个最容易混的词分开

1.1 加密

加密 可以看成 把明文按一定规则变成密文,只有持有正确密钥的人才能还原

它解决的是 数据即使被别人拿到,也不能直接看懂内容

1.2 解密

解密 可以理解成 把密文还原回明文

这意味着:加密和解密通常是一对可逆过程。

1.3 哈希

哈希 不是“加密”,而是:把输入按算法映射成固定长度摘要。

它和加密最关键的区别是:哈希通常不强调可逆。

这也是为什么密码存储更常讨论:

  1. MD5
  2. SHA
  3. BCrypt
  4. Argon2

而不是把密码“解密回来”。

1.4 一句话区分

把这一层先分开:

  1. 加密 / 解密:可逆,重点是保密传输和保密存储
  2. 哈希:不可逆,重点是校验和安全存储

2. Spring 应用里最常见的加密场景

如果把企业项目里的常见场景拆开看,通常会有这几类:

  1. 用户密码存储
  2. 敏感配置保护
  3. 接口字段加密
  4. 数据库存储前加密
  5. token、签名、摘要校验

要注意的是:不是所有敏感数据都用同一种方式处理。

例如:

  1. 密码更适合哈希存储
  2. 身份证号、手机号这类需要后续展示或使用的数据,才更可能需要可逆加密
  3. 接口签名更常是摘要或 HMAC,而不是简单“加密一下”

2.1 常见加密方案到底该怎么比较

很多人在看安全方案时,最容易掉进一个坑:把 MD5、BCrypt、AES、RSA 这些词直接放到一张表里横着比,然后想找出一个“最强算法”。

这样比通常会越比越乱。

因为它们解决的问题本来就不完全一样:

  1. MD5SHA-256BCryptArgon2 更偏摘要或密码存储
  2. AES 更偏可逆加密
  3. RSAECC 更偏密钥交换、签名或少量数据加密
  4. HMAC 更偏消息完整性校验和接口签名

所以更稳妥的比较方式是:

  1. 看它是不是可逆
  2. 再看它解决的是“密码存储”“字段加密”还是“签名验签”
  3. 最后再比较安全性、性能和工程成本

🌟 可以直接记住下面这几个选型结论:

  1. 密码存储:优先考虑 Argon2BCryptPBKDF2
  2. 业务字段可逆加密:优先考虑 AES
  3. 接口签名或消息认证:优先考虑 HMAC-SHA256
  4. 非对称签名或密钥交换:常见是 RSAECC

2.2 密码存储相关方案对比

看最容易混的这一组:它们很多都和“密码”有关,但并不是都适合拿来存密码。

方案类型是否可逆安全性性能典型场景工程建议
MD5快速摘要算法很低很高旧系统摘要、非安全去重不要用于密码存储
SHA-256快速摘要算法很高文件摘要、完整性校验不要单独用于密码存储
PBKDF2慢哈希 / 密码派生密码存储、密钥派生兼容性好,很多系统可用
BCrypt慢哈希 / 密码存储中偏低用户密码存储Spring 项目默认选择很常见
SCrypt慢哈希 / 内存开销更高抗暴力破解的密码存储适合高安全场景,但资源开销更大
Argon2新一代慢哈希很高低到中用户密码存储新项目里通常是更先进的选择

这张表里最重要的结论不是谁“绝对更强”,而是:

  1. MD5SHA-256 这类“快摘要”不适合直接存密码
  2. 密码存储需要的是“故意更慢”的算法
  3. BCrypt 在 Spring 生态里最常见,Argon2 在理念上通常更先进

更直白一点说:密码存储不是追求算得快,而是追求攻击者算得慢。

2.3 可逆加密、签名与非对称方案对比

另一组常见方案则更偏“字段保密”“签名验签”“密钥交换”。

方案类型是否可逆安全性性能典型场景工程建议
AES对称加密很高数据库字段加密、配置加密、文件加密最常见的可逆加密方案
RSA非对称加密 / 签名部分可逆数字签名、密钥交换、少量数据加密不适合直接加密大体积业务数据
ECC非对称加密 / 签名部分可逆签名、密钥交换、移动端或资源敏感场景更小密钥下可获得较高安全性
HMAC-SHA256消息认证码很高接口签名、请求防篡改、服务间验签它不是加密,而是完整性和来源校验

这里有几个特别容易误解的点:

  1. AES 适合做真正的数据加密,因为它快,而且适合处理大量数据
  2. RSAECC 更常用来做签名、验签和密钥交换,而不是直接加密整段大文本
  3. HMAC-SHA256 不是“把内容藏起来”,而是证明“内容没被改、请求确实来自持有密钥的一方”

2.4 如果按“安全性、性能、场景”三维来选

如果只想快速做工程判断,看这张压缩表:

需求更适合的方案为什么
用户密码存储BCrypt / Argon2 / PBKDF2不可逆,且故意较慢,更适合抗暴力破解
手机号、身份证号等字段加密AES可逆、性能高、适合批量字段处理
接口签名、防篡改HMAC-SHA256快、实现简单、适合请求验签
公私钥签名、证书体系RSA / ECC适合签名验签和密钥交换
大文件或大报文直接加密AES非对称加密太慢,不适合大数据量

🌟 真正落到工程里,通常不是“全站只用一个算法”,而是:

  1. 密码用慢哈希
  2. 字段保密用对称加密
  3. 验签和证书体系用非对称算法
  4. 接口防篡改用摘要或 HMAC

3. 为什么密码存储不该用“可逆加密”

这是企业开发里最常见的误区之一。

很多人会想:用户密码存库前先 AES 加密,登录时再解密对比,不就行了吗。

真正的问题在于:只要系统能把密码解回来,说明某个地方一定持有解密能力。

一旦密钥泄漏,所有密码都可能一起暴露。

所以密码存储更稳妥的思路通常是:

  1. 不保存原文
  2. 不要求能解密回原密码
  3. 只在用户登录时,把输入密码做同样规则处理后再比较

这就是为什么密码存储更推荐:哈希 + 盐值 + 慢哈希算法


4. PasswordEncoder 到底在做什么

在 Spring Security 里,密码处理最常见的入口是:PasswordEncoder

它可以看成 Spring 对密码编码、校验这件事做的一层统一抽象

它最常见的两个动作是:

  1. encode(rawPassword):把原始密码编码后存储
  2. 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 可以看成 专门为密码存储设计的慢哈希算法

它和普通摘要算法相比,更适合密码存储的原因通常有:

  1. 自带盐值处理思路
  2. 计算成本更高
  3. 更不利于暴力破解和彩虹表攻击

工程上更重要的一点是:密码存储要故意“慢一点”,而不是越快越好。

因为:攻击者一旦拿到密码摘要,越快的算法越利于高频暴力尝试。


6. 如果业务字段真的需要可逆加密,该怎么理解

并不是所有敏感数据都像密码一样不需要解密。

例如:

  1. 银行卡号
  2. 身份证号
  3. 手机号
  4. 某些第三方密钥

如果系统后续还要展示、调用或回传,就可能需要:可逆加密

在 Java / Spring 项目里,最常见的思路通常是:

  1. 用对称加密算法,比如 AES
  2. 在进入数据库前先加密
  3. 在真正需要展示或使用时再解密

一个简化示意:

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 会不会写,而是:

  1. 密钥不能硬编码在代码里
  2. 不同数据最好有明确加密边界
  3. 模式、填充、密钥轮换都要考虑
  4. 日志和异常输出不能把明文泄漏出去

7. 配置文件里的敏感信息怎么处理

Spring 项目里,另一个很常见的场景是:

  1. 数据库密码
  2. 第三方 API Key
  3. 对称加密密钥
  4. token 签名密钥

这些信息如果直接明文写进:

  1. application.yml
  2. Git 仓库
  3. 镜像文件

风险会非常高。

更稳妥的工程习惯通常包括:

  1. 使用环境变量
  2. 使用密钥管理系统或配置中心密文能力
  3. 区分开发、测试、生产环境的密钥来源
  4. 对密钥做轮换和权限控制

也就是说:真正的安全问题,很多时候不在“算法选错”,而在“密钥管理太随意”。


8. 接口签名和加密是一回事吗

不完全是一回事。

很多接口安全场景里常见会一起出现:

  1. 加密
  2. 摘要
  3. 签名

但它们解决的问题并不一样。

8.1 加密

重点是:别人看不懂内容。

8.2 摘要

重点是:内容有没有被改过。

8.3 签名

重点通常是:请求是不是可信来源发出来的,内容有没有被篡改。

所以接口安全里很常见的组合其实是:

  1. 传输层靠 HTTPS
  2. 请求身份靠 token 或签名
  3. 敏感字段再按需要做业务层加密

9. 工程上最容易踩的坑

9.1 误区一:密码也用可逆加密

密码更推荐哈希存储,不应该设计成“能解回来”。

9.2 误区二:把密钥写死在代码里

这类问题在真实项目里非常常见,而且破坏性通常比“算法选错”更直接。

9.3 误区三:只保护数据库,不保护日志

即使数据库里存的是密文,如果日志里把明文打出来,保护就形同虚设。

9.4 误区四:把加密当成所有安全问题的答案

加密只能解决一部分保密问题,不替代:

  1. 认证
  2. 授权
  3. 脱敏
  4. 审计
  5. 安全传输

10. 一份更实用的落地建议

如果你在 Spring 项目里真的要处理敏感数据,下面这套习惯通常更稳:

  1. 密码统一走 PasswordEncoder
  2. 敏感业务字段按是否需要可逆读取来决定哈希还是加密
  3. 传输层默认使用 HTTPS
  4. 密钥不要硬编码,统一走环境变量或密钥管理系统
  5. 日志、异常、监控中避免输出明文敏感字段
  6. 对高敏数据配合数据脱敏、访问控制和审计

11. 总结

把 Spring 应用里的加密、解密与密码存储压缩成最核心的几句话,就是:

  1. 密码存储更推荐哈希,不推荐可逆加密
  2. 需要后续读取的敏感业务字段,才更可能需要可逆加密
  3. PasswordEncoder 是 Spring 里最常见的密码处理入口
  4. 真正的工程关键不只是算法本身,还包括密钥管理、日志保护和使用边界

基于 VitePress 构建的个人技术笔记。