Spring Boot 中常见的加密方案
- MD5
-
- [MD5 基本概念](#MD5 基本概念)
- [MD5 的核心思想](#MD5 的核心思想)
- [MD5 使用方法](#MD5 使用方法)
- [SHA 系列](#SHA 系列)
-
- [SHA 系列概述](#SHA 系列概述)
- [SHA 的工作原理(通用流程)](#SHA 的工作原理(通用流程))
- [SHA 使用方法](#SHA 使用方法)
- BCrypt
-
- [BCrypt 概念](#BCrypt 概念)
- [BCrypt 的工作原理(内部流程)](#BCrypt 的工作原理(内部流程))
- [BCrypt 使用方法](#BCrypt 使用方法)
- Argon2
-
- [Argon2 概念](#Argon2 概念)
- [Argon2 设计思想](#Argon2 设计思想)
- [Argon2 使用方法](#Argon2 使用方法)
- [Spring Boot 项目使用](#Spring Boot 项目使用)
- 总结
MD5
MD5 基本概念
MD5 全称是 Message Digest Algorithm 5 (信息摘要算法第 5 版),由密码学家 Ron Rivest 在 1991 年提出。
它的作用是:
把任意长度的数据(字符串、文件等)"压缩"成一个固定长度的 128 位(16 字节)摘要值。
这个摘要值通常用 32 位十六进制字符串表示。
示例
输入: "hello"
输出: 5d41402abc4b2a76b9719d911017c592
即使只改变一个字符:
输入: "Hello"
输出: 8b1a9953c4611296a827abf8c47804d7
可以看到:输入微小变化,输出完全不同。
MD5 的核心思想
MD5 并不是"加密",而是一种哈希算法(Hash Function)。
即:
- 不可逆(你无法从 MD5 值反推出原文)
- 定长输出(无论输入多长,输出始终是 128 位)
- 雪崩效应(输入改动一位,输出几乎全变)
- 高速计算(适合快速校验)
原理简述
MD5 的内部逻辑大致如下:
步骤 | 描述 |
---|---|
填充(Padding) | 将输入填充到长度 ≡ 448 mod 512,确保数据能被分为 512 位的块。 |
附加长度 | 在数据末尾追加原始长度(64 位)。 |
初始化缓冲区 | 定义 4 个 32 位寄存器:A、B、C、D(初始常量)。 |
迭代运算 | 每个 512 位块通过一系列非线性函数和位运算(如 AND、OR、XOR、左移)混合处理。 |
输出结果 | 最终拼接 A、B、C、D 的值 → 128 位摘要。 |
简单来说:MD5 把输入"分块 → 混合 → 扰乱 → 压缩"成固定长度摘要。
MD5 的特点
特性 | 说明 |
---|---|
输出长度固定 | 128 位(16 字节),常以 32 个十六进制字符表示。 |
不可逆 | 无法解密回原始内容。 |
雪崩效应 | 输入变化 1 bit,输出变化约 50%。 |
快速计算 | 算法效率高。 |
碰撞风险 | 不同的输入可能生成相同的输出(已被证明)。 |
MD5 的常见用途
应用场景 | 说明 |
---|---|
文件完整性校验 | 下载软件时验证文件未被篡改。 |
数据校验码 | 快速比较数据内容是否一致。 |
用户密码加密(旧系统) | 曾经常用于存储密码(但已不安全)。 |
数字签名 | 在安全性较低场景中生成签名摘要。 |
MD5 使用方法
使用 Spring 自带工具类
Spring 框架提供了简单的 MD5 工具:
java
import org.springframework.util.DigestUtils;
public class MD5Example {
public static void main(String[] args) {
String input = "hello";
String md5 = DigestUtils.md5DigestAsHex(input.getBytes());
System.out.println(md5);
}
}
输出:
5d41402abc4b2a76b9719d911017c592
使用 Java 原生 API
java
import java.security.MessageDigest;
public class MD5Example {
public static void main(String[] args) throws Exception {
String input = "hello";
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] hashBytes = md.digest(input.getBytes("UTF-8"));
StringBuilder hex = new StringBuilder();
for (byte b : hashBytes) {
hex.append(String.format("%02x", b));
}
System.out.println(hex.toString());
}
}
给密码加盐(增强安全)
加盐(Salt)可以防止彩虹表攻击。
java
import java.security.MessageDigest;
public class SaltedMD5 {
public static void main(String[] args) throws Exception {
String password = "123456";
String salt = "MyApp#2025";
String input = password + salt;
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] bytes = md.digest(input.getBytes("UTF-8"));
StringBuilder hex = new StringBuilder();
for (byte b : bytes) hex.append(String.format("%02x", b));
System.out.println(hex.toString());
}
}
MD5 的安全性问题
MD5 已不安全,不推荐用于密码或安全签名。
原因如下:
问题 | 描述 |
---|---|
碰撞攻击 | 不同输入可得相同 MD5 值。2004 年被正式破解。 |
彩虹表攻击 | 攻击者用预计算表快速反查 MD5 值。 |
暴力破解 | 由于算法速度太快,适合 GPU 暴力破解。 |
SHA 系列
SHA 系列概述
SHA(Secure Hash Algorithm) 是由美国 NSA(国家安全局) 设计、NIST(国家标准与技术研究院) 发布的加密哈希家族。
它的作用与 MD5 类似:将任意长度的数据映射为固定长度的"摘要"(digest),用于校验完整性、签名、认证等。
SHA 家族主要分为以下几代:
系列 | 代表算法 | 输出长度(bit) | 状态 | 说明 |
---|---|---|---|---|
SHA-0 | SHA-0 | 160 | 已废弃 | 原版算法(1993),发现安全漏洞,被迅速弃用 |
SHA-1 | SHA-1 | 160 | 已被破解 | 曾广泛用于 SSL、Git、证书签名等,现在不安全 |
SHA-2 | SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256 | 224~512 | 主流标准 | 当前主流使用的安全哈希算法族(JWT、TLS、密码哈希) |
SHA-3 | SHA3-224、SHA3-256、SHA3-384、SHA3-512 | 224~512 | 新一代标准 | 与 SHA-2 完全不同,基于 Keccak 算法,更抗攻击 |
SHA 的工作原理(通用流程)
SHA 的计算过程与 MD5 类似,但逻辑更复杂、更安全:
阶段 | 说明 |
---|---|
预处理 | 填充数据到 512 位的整数倍,附加长度信息 |
分块 | 每个 512 位分块单独处理 |
初始化变量 | 设定若干 32 位或 64 位寄存器(A、B、C、D...) |
循环压缩 | 通过逻辑函数、位移、模加等运算混合数据 |
拼接结果 | 输出固定长度摘要(如 256 位 = 32 字节) |
SHA 的核心是"不可逆的数学混合"。
输入稍有改动,输出完全不同(雪崩效应)。
SHA 使用方法
SHA-1(Secure Hash Algorithm 1)
- 输出长度:160 位(20 字节)
- 常见输出格式:40 位十六进制字符串
- 算法速度:快
- 安全性:已被破解
java
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] result = md.digest("hello".getBytes());
曾用于:
- SSL/TLS 证书签名(已弃用)
- Git 文件版本校验(正在过渡到 SHA-256)
已被 Google 和荷兰CWI研究所于 2017 年展示 实际碰撞攻击(SHAttered)。
SHA-2 系列(主流标准)
SHA-2 实际是多个算法族的统称:
算法 | 输出长度 | 常见用途 |
---|---|---|
SHA-224 | 224 位 | 嵌入式、低功耗设备 |
SHA-256 | 256 位 | 最常用(JWT、文件校验) |
SHA-384 | 384 位 | 高强度安全 |
SHA-512 | 512 位 | 适合 64 位系统 |
SHA-256 是当前最推荐的通用版本。
SHA-512 性能更高(在 64 位机器上),更安全。
Java 实现示例(SHA-256)
java
import java.security.MessageDigest;
public class SHA256Example {
public static void main(String[] args) throws Exception {
String input = "hello world";
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(input.getBytes("UTF-8"));
StringBuilder hex = new StringBuilder();
for (byte b : hash) {
hex.append(String.format("%02x", b));
}
System.out.println(hex.toString());
}
}
输出(固定 64 位十六进制串):
b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9
Spring Boot 工具类实现(SHA-512)
java
import org.apache.commons.codec.digest.DigestUtils;
String sha512 = DigestUtils.sha512Hex("password123");
System.out.println(sha512);
文件校验场景
java
import java.io.*;
import java.security.MessageDigest;
public static String getFileSHA256(File file) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
try (InputStream fis = new FileInputStream(file)) {
byte[] buffer = new byte[8192];
int n;
while ((n = fis.read(buffer)) > 0) {
digest.update(buffer, 0, n);
}
}
byte[] hash = digest.digest();
StringBuilder sb = new StringBuilder();
for (byte b : hash) sb.append(String.format("%02x", b));
return sb.toString();
}
SHA-3 系列(Keccak)
- 发布年份:2015(NIST 新标准)
- 算法基础:基于海绵结构(Sponge Construction)
- 代表算法:SHA3-224、SHA3-256、SHA3-384、SHA3-512
- 区别:与 SHA-2 完全不同的内部结构,更抗攻击。
java
MessageDigest sha3 = MessageDigest.getInstance("SHA3-256");
byte[] hash = sha3.digest("Hello SHA3".getBytes());
特点:
- 不与 SHA-2 兼容;
- 可抗长度扩展攻击;
- 设计为长期替代 SHA-2。
补充说明:
概念 | 说明 |
---|---|
SHA-2 并不是单一算法 | 它是一整套算法族(不同输出长度的变种)。例如 SHA-256 和 SHA-512 都属于 SHA-2 家族。 |
SHA-3 并不是 SHA-2 的改进版 | 它是全新设计(Keccak 结构),没有继承关系。 |
输出长度 | 对应输出摘要长度(bit 位数),例如:SHA-256 输出 256 位(32 字节)。 |
安全状态 | SHA-1 已可被构造碰撞,SHA-2 目前仍安全,SHA-3 为未来标准。 |
正确理解层级关系
mathematica
SHA(安全哈希算法家族)
├── SHA-0(不安全,废弃)
├── SHA-1(不安全,已被破解)
├── SHA-2(主流安全标准)
│ ├── SHA-224
│ ├── SHA-256
│ ├── SHA-384
│ └── SHA-512
└── SHA-3(新标准,基于 Keccak)
├── SHA3-224
├── SHA3-256
├── SHA3-384
└── SHA3-512
BCrypt
BCrypt 概念
BCrypt 是什么?
BCrypt 是一种基于 Blowfish 加密算法 的密码哈希函数,由 Niels Provos 和 David Mazières 在 1999 年设计,最初出现在 OpenBSD 系统中。
它属于 单向加密算法(Hashing Algorithm),常用于存储用户密码。
BCrypt 的核心特点
特点 | 说明 |
---|---|
单向不可逆 | 无法从哈希值反推明文密码。 |
自动加盐(Salt) | 每次加密自动生成随机盐,避免彩虹表攻击。 |
可调"复杂度因子"(Cost Factor) | 可以调整计算强度,防止暴力破解。 |
输出固定长度(60字符) | 无论输入多长,输出总是 60 个字符。 |
跨语言支持 | 在 Java、Python、PHP、Go 等语言中都有官方或成熟实现。 |
BCrypt 的工作原理(内部流程)
BCrypt 的计算过程大致如下:
1. 生成随机盐(Salt)
↓
2. 将盐与明文密码结合
↓
3. 使用 Blowfish 加密算法多轮加密(2^cost 次)
↓
4. 输出最终的哈希字符串(包含盐和成本信息)
BCrypt 哈希的格式如下:
mathematica
$2a$10$EIXCh1l6Z7CzWIKQ0s1t8u8mvjU7qg3sK9fRf6RPSZ6bEJ3iRzZ6G
│ │ │ │ └───────────────────────────── 密文部分(Base64编码)
│ │ │ └── 22字符的盐(Base64编码)
│ │ └──── 10 表示成本(2^10 = 1024 次加密)
│ └─────── 算法版本(2a/2b)
└───────── 起始符号 $
成本因子(Cost Factor)
BCrypt 的强度主要取决于 cost 参数(也叫 work factor)。
默认一般是 10
,表示进行 2^10 = 1024
轮加密。
Cost | 大约耗时(现代 CPU) |
---|---|
8 | < 100ms |
10 | ~300ms |
12 | ~1s |
14 | ~4s |
建议值:
Web 应用中推荐使用 10~12 ,兼顾安全与性能。
越高越安全,但登录时耗时也更长。
BCrypt 的安全机制详解
-
自动加盐
每次加密都会自动生成 随机盐(16字节) 。
因此即使两个人使用相同密码,哈希值也完全不同。
$2a$10$abc123... ← 盐不一样 $2a$10$xyz987... ← 哈希也不一样
-
自带盐与成本参数
BCrypt 生成的字符串内部就包含盐和 cost,因此不需要单独存储盐。
BCrypt 使用方法
Spring Security 内置了 BCryptPasswordEncoder
,使用极其方便。
示例一:生成密码哈希
java
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class BCryptExample {
public static void main(String[] args) {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String rawPassword = "mySecret123";
String hashedPassword = encoder.encode(rawPassword);
System.out.println("原始密码: " + rawPassword);
System.out.println("加密后: " + hashedPassword);
}
}
输出示例:
原始密码: mySecret123
加密后: $2a$10$e0NR9ZnPS2D2X4xDJH5lBO8xL61jSwB1KkzIUVwFovYCKNH4I5C.O
示例二:验证密码是否匹配
java
String inputPassword = "mySecret123";
boolean matches = encoder.matches(inputPassword, hashedPassword);
System.out.println(matches ? "密码正确" : "密码错误");
Argon2
Argon2 概念
什么是 Argon2?
Argon2 是一种用于**密码哈希(Password Hashing)**的算法,诞生于 2015 年,由 Alex Biryukov、Daniel Dinu、Dmitry Khovratovich 等密码学家设计。
它在 2015 年赢得了 Password Hashing Competition (PHC),成为国际标准密码哈希算法。
目标 :
抵抗暴力破解、GPU 攻击、ASIC 硬件攻击 ------ 并提供可调的计算复杂度与内存占用。
Argon2 的三个变种
版本 | 主要特征 | 推荐用途 |
---|---|---|
Argon2d | 使用数据相关内存访问(速度快) | 抵御 GPU 攻击(不适合并行攻击) |
Argon2i | 使用数据独立访问(更安全) | 防止侧信道攻击(如时间推测) |
Argon2id | 混合模式(先 i 后 d) | 最推荐,兼顾两者优点 |
在实际项目中:
Argon2id 是目前密码存储的官方推荐版本(RFC 9106 标准)
Argon2 设计思想
传统算法(如 BCrypt)仅考虑"计算耗时",
而 Argon2 同时考虑 CPU + 内存消耗,让暴力破解更难:
因素 | 含义 | 攻击难度影响 |
---|---|---|
Time Cost | 运算迭代次数 | 增加计算量 |
Memory Cost | 占用内存(KB/MB) | 降低 GPU 并行破解效率 |
Parallelism | 并行线程数 | 提升合法加密速度 |
工作原理(简化流程)
- 生成随机盐(16字节或更长)
- 结合明文密码 + 盐 + 参数(内存/时间/并行度)
- 进行多轮基于内存的哈希计算
- 输出哈希字符串(包含所有参数)
输出结果示例:
mathematica
$argon2id$v=19$m=65536,t=3,p=4$QWERTYasdfgh1234$NqM6cN6Lk5mZtP4xGVg4xg
│───────││───── 参数说明 ─────││─── Base64盐 ───││────── 哈希结果 ──────│
│ 算法 ││版本│内存=64MB,迭代3次,4线程│
Argon2 参数详解
参数 | 含义 | 推荐值(Web 应用) |
---|---|---|
t (Time Cost) | 迭代次数 | 2~4 |
m (Memory Cost) | 使用内存大小(KB) | 65536 (≈64MB) |
p (Parallelism) | 并行线程数 | 1~4 |
salt length | 盐长度 | ≥16字节 |
hash length | 输出哈希长度 | 32字节或更长 |
安全建议:
- 盐必须随机且唯一
- 增加
m
(内存)比增加t
(次数)更能防 GPU 攻击- 建议使用 Argon2id 模式
为什么 Argon2 比 BCrypt 更安全?
特性 | BCrypt | Argon2 |
---|---|---|
加盐机制 | 自动加盐 | 自动加盐 |
可调复杂度 | cost | time + memory + parallel |
GPU 防御 | 弱 | 强(高内存占用) |
并行支持 | 无 | 有 |
性能 | 中等 | 高 |
安全标准 | 无官方标准 | RFC 9106 标准 |
Argon2 使用方法
Spring Security 从 5.0 版本开始原生支持 Argon2 。
使用方式与 BCrypt 类似。
添加依赖
在 Spring Boot 项目中引入 Spring Security:
xml
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
</dependency>
创建 Argon2PasswordEncoder
java
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
public class Argon2Example {
public static void main(String[] args) {
// 参数:saltLength, hashLength, parallelism, memoryCost, iterations
PasswordEncoder encoder = new Argon2PasswordEncoder(16, 32, 1, 65536, 3);
String rawPassword = "mySecret123";
String encoded = encoder.encode(rawPassword);
System.out.println("原始密码: " + rawPassword);
System.out.println("加密后: " + encoded);
boolean match = encoder.matches("mySecret123", encoded);
System.out.println("匹配结果: " + match);
}
}
输出示例:
原始密码: mySecret123
加密后: $argon2id$v=19$m=65536,t=3,p=1$xA4xv2x4lQv4a7g3xA$F6xK3v2n8dQOpxV1u+3Bfw
匹配结果: true
Spring Boot 项目使用
MD5
-
性质 :哈希算法(不可逆),Spring 不提供原生封装,只是工具类(
DigestUtils
) -
使用方式:直接调用工具方法即可
javaimport org.springframework.util.DigestUtils; String md5 = DigestUtils.md5DigestAsHex("password".getBytes());
-
配置类 :不需要
-
注意事项:
- MD5 已不安全,不建议用作密码存储
- 可以用于文件校验、数据完整性等场景
SHA 系列(SHA-1、SHA-256、SHA-512、SHA3 等)
-
性质:哈希算法(不可逆)
-
使用方式:
-
Java 原生:
javaMessageDigest md = MessageDigest.getInstance("SHA-256"); byte[] hash = md.digest("password".getBytes());
-
或 Apache Commons Codec / Spring
DigestUtils
-
-
配置类 :不需要,直接在业务代码中调用即可
-
注意事项:
- 如果用于密码存储,必须加盐 + 多轮迭代
BCrypt
-
性质:单向加密(哈希 + 自动加盐 + 可调复杂度)
-
Spring Boot 支持 :原生提供
BCryptPasswordEncoder
-
使用方式:
-
可以直接在业务中 new 一个对象用:
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); String hash = encoder.encode("password"); boolean match = encoder.matches("password", hash);
-
推荐写配置类注入
PasswordEncoder
,方便 Spring Security 集成:javaimport org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @Configuration public class PasswordConfig { /** * BCryptPasswordEncoder 默认 cost = 10 * cost 越大,计算越慢,安全性越高 */ @Bean public PasswordEncoder bCryptPasswordEncoder() { return new BCryptPasswordEncoder(10); // 可以根据性能调整 } }
使用示例
java@Autowired private PasswordEncoder passwordEncoder; public void register(String rawPassword) { String encoded = passwordEncoder.encode(rawPassword); // 保存 encoded 到数据库 } public boolean login(String rawPassword, String storedHash) { return passwordEncoder.matches(rawPassword, storedHash); }
-
-
配置类 :推荐,但不是必须
- 如果项目不使用 Spring Security,也可以直接
new BCryptPasswordEncoder()
- 如果项目不使用 Spring Security,也可以直接
Argon2(Argon2id)
-
性质:现代密码哈希(内存硬化 + 自动加盐 + 可调复杂度)
-
Spring Boot 支持 :Spring Security 5+ 原生提供
Argon2PasswordEncoder
-
使用方式:
-
直接 new 使用:
javaArgon2PasswordEncoder encoder = new Argon2PasswordEncoder(); String hash = encoder.encode("password"); boolean match = encoder.matches("password", hash);
-
推荐写配置类注入
PasswordEncoder
:javaimport org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.crypto.argon2.Argon2PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @Configuration public class PasswordConfig { /** * Argon2PasswordEncoder 参数说明: * saltLength: 盐长度(字节) * hashLength: 输出哈希长度(字节) * parallelism: 并行线程数 * memory: 内存消耗 (KB) * iterations: 哈希迭代次数 */ @Bean public PasswordEncoder argon2PasswordEncoder() { return new Argon2PasswordEncoder( 16, // salt length 32, // hash length 1, // parallelism 65536, // memory cost (64 MB) 3 // iterations ); } }
使用示例
java@Autowired private PasswordEncoder passwordEncoder; public void register(String rawPassword) { String encoded = passwordEncoder.encode(rawPassword); // 保存 encoded 到数据库 } public boolean login(String rawPassword, String storedHash) { return passwordEncoder.matches(rawPassword, storedHash); }
-
-
配置类 :推荐
- 方便全局统一使用,尤其是结合 Spring Security
总结
在 Spring Boot 项目 中,选择合适的哈希/加密算法主要取决于用途 和安全性需求:
算法 | 类型 | 输出长度 | 安全性 | 特点与适用场景 | Spring Boot 使用方式 |
---|---|---|---|---|---|
MD5 | 哈希算法 | 128 位(16 字节) | 已不安全 | 快速计算、雪崩效应强;适合文件校验、数据完整性检测 | DigestUtils.md5DigestAsHex() 调用即可,无需配置类 |
SHA 系列 | 哈希算法 | 160~512 位 | SHA-1 已不安全,SHA-2/3 安全 | 可靠的消息摘要算法;文件校验、签名、JWT、加密辅助 | Java MessageDigest 或 Apache DigestUtils 调用,无需配置类;用于密码需加盐+多轮 |
BCrypt | 密码哈希 | 60 字符 | 安全 | 自动加盐、可调成本(cost)、不可逆;适合用户密码存储 | 推荐创建配置类 PasswordEncoder Bean;可直接 new BCryptPasswordEncoder() |
Argon2 | 密码哈希 | 可变(Base64) | 高安全 | 自动加盐、可调时间/内存/并行度;防 GPU/ASIC 攻击,现代推荐算法 | 推荐创建配置类 PasswordEncoder Bean,使用 Argon2PasswordEncoder ,参数可自定义 |
建议
- 简单数据完整性 (如文件校验、数据摘要)
- 使用 MD5 或 SHA-2/3 即可
- 不需要配置类,直接工具类调用
- 用户密码存储
- 强烈建议 使用 BCrypt 或 Argon2id
- MD5、SHA-1、SHA-256 不安全,即使加盐也难抵抗 GPU 彩虹表攻击
- 在 Spring Boot 项目中,建议通过 配置类统一注入
PasswordEncoder
Bean,方便全局使用和与 Spring Security 集成
- 性能与安全平衡
- BCrypt:成本因子(cost)调整计算复杂度
- Argon2:可调
timeCost
、memoryCost
、parallelism
,更安全且支持并行
总结一句话:
文件校验用 MD5/SHA,密码存储用 BCrypt/Argon2(推荐 Argon2id),密码算法最好通过配置类统一注入
PasswordEncoder
,保证安全性与可维护性。