解锁Spring Security:密码加密算法
1. 密码加密方式
1.1 明文密码
最初,密码以明文形式存储在数据库中。但是恶意用户可能会通过SQL注入等手段获取明文密码,同时程序员将数据库数据泄露的情况也可能发生。明文存储密码是很危险的事情。
1.2 Hash算法
Spring Security的 PasswordEncoder
接口对于密码进行 单向转换 ,从而将密码安全的存储,对密码单向转换需要用到 哈希算法 ,例如MD5、SHA-256、SHA-512等,哈希算法是单向的,只能加密,不能解密。 因此,数据库中存储的是单向转换后的密码 ,Spring Security在进行用户身份验证时需要将用户输入的密码进行单向转换,然后与数据库的密码进行比较。如果发生数据泄露,只有密码的单项哈希会被暴露,由于哈希是单向的,并且在给定哈希的情况下只能通过 暴力破解的方式猜测密码 。
1.3 彩虹表
恶意用户创建称为 彩虹表 的查找表。一个庞大的、针对各种可能的字母组合预先生成的哈希值集合,有了它就可以快速破解各类密码,越是复杂的密码,需要的彩虹表就越庞大。主流的彩虹表都是100G以上,目前主要的算法有LM,NTLM,MD5,SHA1,MYSQLSHA1,HALFLMCHALL,NTLMCHALL,ORACLE-SYSTEM,MD5-HALF
1.4加盐密码
为了防治彩虹表,开发人员开始使用加盐密码。不再只是用密码作为哈希函数的输入,而是为每个用户的密码生成随机字节(盐)。 盐和用户的密码将一起经过哈希函数运算,生成一个唯一的哈希值。盐将以明文形式与用户密码一起储存。然后当用户尝试进行身份验证时,盐和用户输入的密码一起经过哈希函数运输,再与存储的密码进行比较。唯一的盐意味着彩虹表不再有效,因为对于每个盐和密码的组合,哈希值都是不同的。
1.5 自适应单向函数:
随着硬件的不断发展,加盐哈希也不再安全,计算机可以每秒执行数十亿次哈希计算。这意味着可以轻松的使用暴力破解的方式拿到任何密码。
现在开发人员开始使用自适应单向函数来存储密码。使用自适应单向函数验证密码时,故意占用资源 。自适应单向函数允许配置一个 ”工作因子“ ,随着硬件的改进而增加。我们将“工作因子”调整到系统中验证密码需要约一秒钟的时间。这种权衡是为了 使攻击者难以暴力破解密码 。自适应单向函数包括 bcrypt
PBKDF2
argon2
2. PasswordEncoder 实现详解
在Spring Security中,PasswordEncoder
接口的主要作用是对密码进行编码(即哈希处理)和验证。选择合适的编码器不仅能增强系统的安全性,还能有效抵御密码破解攻击。下面详细介绍几种常用的PasswordEncoder
实现。
2.1 BCryptPasswordEncoder
BCryptPasswordEncoder
使用广泛支持的bcrypt算法对密码进行哈希处理。bcrypt算法设计为一种自适应单向函数,意味着它能够随着计算能力的提升,通过调整工作因子(cost factor)来增加破解难度。默认情况下,BCryptPasswordEncoder
的工作因子为10,可以在大多数系统上提供约1秒的验证时间。这种故意设计得较慢的算法,有效地抵御了暴力破解和彩虹表攻击。
优点:
- 支持跨平台,广泛兼容。
- 默认情况下提供良好的安全性。
- 工作因子可调,灵活性高。
示例代码:
1 | PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(10); |
2.2 Argon2PasswordEncoder
Argon2PasswordEncoder
使用的是Argon2算法,这是一种更为现代化的密码哈希算法,并且是2015年密码哈希竞赛的获胜者。Argon2的设计目标是最大限度地抵抗基于自定义硬件(如GPU和ASIC)的攻击,因此它不仅依赖于计算能力,还要求大量内存。为了使用Argon2PasswordEncoder
,需要在项目中引入BouncyCastle库。
优点:
- 算法现代,具备较高的安全性。
- 设计上能够抵抗基于硬件的破解攻击。
注意事项:
- 需要额外引入BouncyCastle库。
- 相较于bcrypt,配置和使用稍微复杂。
示例代码:
1 | PasswordEncoder passwordEncoder = new Argon2PasswordEncoder(); |
2.3 Pbkdf2PasswordEncoder
Pbkdf2PasswordEncoder
使用的是PBKDF2(Password-Based Key Derivation Function 2)算法,它同样是一种故意设计得较慢的哈希算法,旨在抵御基于硬件的破解攻击。PBKDF2通过反复多次哈希处理,提高了密码的破解难度。它在许多安全标准中被推荐使用,如NIST SP 800-132。
优点:
- 广泛使用,支持度高。
- 可调整迭代次数以增加破解难度。
示例代码:
1 | PasswordEncoder passwordEncoder = new Pbkdf2PasswordEncoder(); |
3. 密码加密测试
在test中添加testPassword方法
1 |
|
4. DelegatingPasswordEncoder
DelegatingPasswordEncoder
的设计旨在处理多个密码编码方案并支持向后兼容。它允许你在应用中使用不同的编码器,并能够识别并正确处理存储在数据库中的不同编码格式的密码。
工作原理:
- 委托机制:
DelegatingPasswordEncoder
会根据密码的前缀标识符,将验证和编码操作委托给特定的编码器。例如,一个以{bcrypt}
为前缀的密码将使用BCryptPasswordEncoder
进行处理。 - 向后兼容: 通过使用
DelegatingPasswordEncoder
,可以在更新密码哈希策略时继续支持旧的密码编码格式,而无需强制用户立即更新他们的密码。 - 默认编码器:
DelegatingPasswordEncoder
允许设置一个默认编码器用于新密码的编码。当没有指定前缀时,默认编码器会被使用。
典型用法:
1 | // 创建编码器的映射关系 |
优势:
- 灵活性: 允许在不同的密码编码器之间灵活切换。
- 向后兼容性: 在不强制要求用户更改密码的情况下,可以逐步迁移到更强的密码编码方案。
- 扩展性: 可以轻松添加自定义编码器以满足特殊需求。
DelegatingPasswordEncoder
是大型应用程序中常用的工具,特别是在需要支持多种密码编码方案或逐步过渡到新的哈希算法时。它确保了密码的安全性,同时提供了良好的兼容性和灵活性。
在文章的结尾推荐一些密码安全领域的经典参考资料