哈喽,大家好呀!这里是码农后端。本篇将带你了解一些常见的密码加密方式。毋庸置疑,密码的安全性对于用户来说是非常重要的,如何保证密码的安全性使其不被破解也是一直以来的一个非常重要的话题。

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 自适应单向函数

随着硬件的不断发展,加盐哈希也不再安全。因为计算机可以每秒执行数十亿次哈希计算,并轻松地破解每个密码。

现在,开发人员开始使用自适应单向函数来存储密码。使用自适应单向函数验证密码时,故意占用资源(故意使用大量的CPU、内存或其他资源)。自适应单向函数允许配置一个“工作因子”,随着硬件的改进而增加。

一般建议将“工作因子”调整到系统中验证密码需要约一秒钟的时间,这种权衡可以让攻击者难以破解密码。由于计算机可以每秒执行数十亿次哈希计算,如果验证密码需要约一秒钟时间的话,就很难再破解了我们的密码了。

自适应单向函数包括bcrypt、PBKDF2、scrypt和argon2

2、PasswordEncoder

PasswordEncoder是一个密码解析器。

2.1 BCryptPasswordEncoder

使用广泛支持的bcrypt算法来对密码进行哈希。为了增加对密码破解的抵抗力,bcrypt故意设计得较慢。和其他自适应单向函数一样,应该调整其参数,使其在您的系统上验证一个密码大约需要1秒的时间。

BCryptPasswordEncoder的默认实现使用强度10。建议在自己的系统上调整和测试强度参数,以便验证密码时大约需要1秒的时间。

2.2 Argon2PasswordEncoder

使用Argon2算法对密码进行哈希处理。Argon2是密码哈希比赛的获胜者。为了防止在自定义硬件上进行密码破解,Argon2是一种故意缓慢的算法,需要大量内存。与其他自适应单向函数一样,它应该在系统上调整为大约1秒来验证一个密码。

当前的Argon2PasswordEncoder实现需要使用BouncyCastle库。

2.3 Pbkdf2PasswordEncoder

使用PBKDF2算法对密码进行哈希处理。为了防止密码破解,PBKDF2是一种故意缓慢的算法。与其他自适应单向函数一样,它应该在系统上调整为大约1秒来验证一个密码。

当需要FIPS认证时,这种算法是一个很好的选择。

2.4 SCryptPasswordEncoder

使用scrypt算法对密码进行哈希处理。为了防止在自定义硬件上进行密码破解,scrypt是一种故意缓慢的算法,需要大量内存。与其他自适应单向函数一样,它应该在系统上调整为大约1秒来验证一个密码。

3、密码加密测试

在测试类中编写一个测试方法

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
void testPassword() {

// 工作因子,默认值是10,最小值是4,最大值是31,值越大运算速度越慢
PasswordEncoder encoder = new BCryptPasswordEncoder(4);
//明文:"password"
//密文:result,即使明文密码相同,每次生成的密文也不一致
String result = encoder.encode("password");
System.out.println(result);

//密码校验
Assert.isTrue(encoder.matches("password", result), "密码不一致");
}

运行得到密码

1
$2a$04$ngVQ.lRu8NdXFM0FkarNKe1Pi0hgtQW1TUxijIMIJvfX7mYGT2kyy

注:每次运行得到的密码是不一样的,因为在生成密码的过程中有一个随机数参与了盐运算

4、DelegatingPasswordEncoder

探索存储的密码形式:{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW

Ctrl + N检索DelegatingPasswordEncoder.class源码,并添加断点跟踪,重新启动调试项目。在浏览器输入http://localhost:8080/ ,进行登录调试。

通过如下源码可知:可通过{bcrypt}前缀动态获取和密码的形式类型一致的PasswordEncoder对象

目的:方便随时做密码策略的升级,兼容数据库中的老版本密码策略生成的密码