密码加密算法和JWT无状态认证
加密算法分类
一、单向散列算法(不可逆,推荐用于密码存储)
单向散列算法通过哈希函数将任意长度的密码转换为固定长度的哈希值(密文),且无法从哈希值反推原始密码,是密码存储的首选方案。为增强安全性,通常会结合盐值(Salt) 使用(给每个密码添加随机字符串后再哈希,防止彩虹表攻击)。
1. MD5(Message-Digest Algorithm 5)
- 特点:生成 128 位(16 字节)哈希值,运算速度快,但安全性极低。
- 缺陷:已被证明存在严重漏洞,可通过碰撞攻击伪造相同哈希值的不同明文,不适合密码存储。
- 现状:仅用于非安全场景(如文件校验),严禁用于密码加密。
2. SHA 系列(Secure Hash Algorithm)
- SHA-1:生成 160 位哈希值,安全性低于 SHA-2,已被破解,不推荐用于密码。
- SHA-2:包括 SHA-256、SHA-384、SHA-512 等,生成 256/384/512 位哈希值,安全性高,应用广泛。
- 例:SHA-256 是目前主流选择,抗碰撞能力强,适合结合盐值存储密码。
- SHA-3:SHA-2 的替代方案,基于全新算法设计,安全性更高,但目前应用较少。
3. BCrypt
- 特点:专为密码哈希设计,基于 Blowfish 加密算法,自带盐值机制,且可通过 “工作因子” 调整运算复杂度(随硬件升级提高破解难度)。
- 优势:不可逆、抗暴力破解能力强,是业界推荐的密码加密算法之一(如 Spring Security 默认支持)。
二、可逆加密算法(不推荐直接用于密码存储)
可逆加密算法(对称 / 非对称)可通过密钥解密得到原始密码,若密钥泄露,密码会直接暴露,因此不适合直接存储密码,仅用于临时加密传输场景。
1. 对称加密算法
- AES(Advanced Encryption Standard):目前最流行的对称加密算法,支持 128/192/256 位密钥,安全性高、速度快。
- DES/3DES:DES 已被破解,3DES 是 DES 的改进版但效率低,逐渐被 AES 替代。
2. 非对称加密算法
- RSA:基于大数分解难题,用于密钥交换或数字签名,加密速度慢,不适合大量数据(如密码)加密。
三、密码加密的最佳实践
- 优先使用专用哈希算法:选择 BCrypt、Argon2 或 PBKDF2,避免 MD5、SHA-1。
- 强制使用盐值:每个密码对应唯一随机盐值,与哈希值一起存储(盐值无需保密)。
- 动态调整复杂度:根据硬件性能提高算法的迭代次数或内存消耗(如 BCrypt 的工作因子、Argon2 的内存参数)。
- 禁止可逆加密:除非特殊场景(如临时密码传输),否则不使用 AES、RSA 等可逆算法存储密码。
总结:Argon2 和 BCrypt 是当前密码加密的最优选择,兼顾安全性和实用性;SHA-256 结合盐值和高迭代次数可作为次选;MD5、DES 等已彻底淘汰。
BCrypt加密原理
代码实现:
##加密部分 public String encodePassword(String rawPassword) {if (rawPassword == null || rawPassword.isEmpty()) {throw new IllegalArgumentException("密码不能为空");}String encodedPassword = passwordEncoder.encode(rawPassword);log.debug("密码已加密");return encodedPassword; } ##匹配部分 public boolean matches(String rawPassword, String encodedPassword) {if (rawPassword == null || encodedPassword == null) {return false;}boolean matches = passwordEncoder.matches(rawPassword, encodedPassword);log.debug("密码匹配结果: {}", matches);return matches; }
1. 核心原理:加密算法的 “验证逻辑”
现代密码加密算法(如 BCrypt、Argon2、PBKDF2 等)的 matches
方法并非简单的 “重新加密原始密码并比较密文”,而是基于算法特性设计的验证逻辑:
- 加密时,算法会自动生成随机盐值(salt),并将盐值与加密后的哈希值一起存储在
encodedPassword
中(密文通常包含盐值 + 哈希值 + 算法参数,格式如$2a$10$N9qo8uLOickgx2ZMRZo5MeVQ82iT1M3Q
)。 - 验证时,
matches
方法会:- 从
encodedPassword
中解析出盐值、算法参数(如迭代次数、工作因子等)。 - 使用相同的盐值和参数,对用户输入的
rawPassword
执行相同的加密流程,生成临时哈希值。 - 比较临时哈希值与
encodedPassword
中存储的哈希值是否一致,一致则返回true
。
- 从
2. 示例流程(以 BCrypt 为例)
假设你的 passwordEncoder
是 BCrypt 加密器:
加密时:
passwordEncoder.encode("123456")
会生成类似$2a$10$N9qo8uLOickgx2ZMRZo5MeVQ82iT1M3Q
的密文,其中:$2a$
表示 BCrypt 算法版本。10$
表示工作因子(迭代次数)。N9qo8uLOickgx2ZMRZo5Me
是随机盐值。- 后续部分是盐值 + 原始密码的哈希结果。
验证时:
passwordEncoder.matches("123456", "$2a$10$N9qo8uLOickgx2ZMRZo5MeVQ82iT1M3Q")
会:- 从密文中解析出盐值
N9qo8uLOickgx2ZMRZo5Me
和工作因子10
。 - 用同样的盐值和工作因子,对输入的
123456
重新执行 BCrypt 加密。 - 比较新生成的哈希值与密文中的哈希部分是否相同,相同则返回
true
。
- 从密文中解析出盐值
3. 为什么不需要手动处理盐值?
现代加密框架(如 Spring Security 的 PasswordEncoder
)已封装了盐值的生成、存储和提取逻辑:
- 盐值无需单独存储,而是嵌入在
encodedPassword
中。 matches
方法内部会自动解析盐值并复用加密参数,开发者无需关心细节。
总结
passwordEncoder.matches()
的核心是 “用密文中的盐值和参数重新加密原始密码,再对比哈希结果”,这保证了即使相同原始密码生成的密文不同(因盐值随机),仍能正确验证。这种机制既避免了盐值管理的复杂性,又保证了密码验证的安全性。
JWT无状态认证token 包含用户信息和过期时间
"token": "eyJhbGciOiJIUzUxMiJ9.eyJ1c2VySWQiOjEsInVzZXJuYW1lIjoic2hhb3plbWluZyIsInN1YiI6InNoYW96ZW1pbmciLCJpYXQiOjE3NTU2ODA3MDYsImV4cCI6MTc1NTY4MTYwNn0.e_2SZzCHe_9uFXYicpyfyf-AM2WCuhp3x5IRZVsEwjsuQyx1mJ_KI_DkNqKqgu6UxDgdULiwCArVVOP2G6XddQ",
解码分析
(1)Header(头部)
解码字符串:eyJhbGciOiJIUzUxMiJ9
Base64 解码后为 JSON:
{"alg": "HS512"
}
- 含义:声明该 JWT 使用
HS512
算法进行签名(与你之前代码中使用的算法一致)。
(2)Payload(载荷)
解码字符串:eyJ1c2VySWQiOjEsInVzZXJuYW1lIjoic2hhb3plbWluZyIsInN1YiI6InNoYW96ZW1pbmciLCJpYXQiOjE3NTU2ODA3MDYsImV4cCI6MTc1NTY4MTYwNn0
Base64 解码后为 JSON:
{"userId": 1,"username": "shaozeming","sub": "shaozeming","iat": 1755680706,"exp": 1755681606
}
(3)Signature(签名)
最后一部分是签名:e_2SZzCHe_9uFXYicpyfyf-AM2WCuhp3x5IRZVsEwjsuQyx1mJ_KI_DkNqKqgu6UxDgdULiwCArVVOP2G6XddQ
作用:由头部指定的
HS512
算法,结合服务器端的密钥对 Header 和 Payload 进行签名生成,用于验证令牌的完整性和真实性(防止被篡改)。无法解码,仅能通过服务器端的密钥验证有效性。