1 用户密码加密的必要性
web系统中通常都会有登录的功能,登录功能的逻辑是这样的:一个用户拥有一个用户名为zhangsan,密码为123456的账号,在登录时,前端去调用后端的登录接口,并传入zhangsan与123456作为参数;在后端代码内容运行时,会根据zhangsan这一用户名去查询数据库的用户表,查询出的内容会包含密码,将这一个密码与用户所输入的密码做一个比对,如果相等才能继续完成登录功能。而在数据库的用户表中,我们通常可以看到密码字段中存放的通常是一段我们看不懂的密文(比如以bcrypt加密的密文是这样的——$2a$10$mWTEg1Byt3u/dAQl6GdJ5.UjVozRzCpxG4R64IvIvkwrJkwlfT5NG),也就是将真正的密码进行了加密,这样做的目的是为了防止数据库信息的泄露导致用户密码被盗用,加密后的字符串即使是被其他人所获取到依然是不可能用的,因为在登录逻辑中后端对密码进行判断时会将用户所输入的密码也进行加密后再与数据库中存放的密文进行对比,通过这样的方式,加强了系统的安全性。
2 MD5
2.1 MD5信息摘要算法
信息摘要算法的主要特征是加密不需要密钥,并且经过加密后的数据无法被解密,只有输入相同的明文数据经过相同的信息摘要算法才能得到相同的密文。MD5信息摘要算法(英语:MD5 Message-Digest Algorithm),是一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值(hash value),用于确保信息传输完整一致。MD5由美国密码学家罗纳德 李维斯特(Ronald Linn Rivest)设计,于1992年公开,用以取代MD4算法。
2.2 在mysql中使用MD5
mysql数据库中内置了MD5的函数,因此我们可以通过查询dual查看明文经过MD5加密后的密文。比如在sql命令中输入以下sql语句可以得到123456经过MD5加密后的结果。
SELECT MD5('123456') FROM dual;
结果为:e10adc3949ba59abbe56e057f20f883e
2.3 MD5加盐
经过信息摘要算法加密后的数据是不能够解密的,也就是说我们可以通过123456加密后得到e10adc3949ba59abbe56e057f20f883e字符串,但不能通过e10adc3949ba59abbe56e057f20f883e解密得到123456,而且对补一个字符串经过MD5加密后的字符串是相同的,那么将123456通过MD5加密后永远都能得到e10adc3949ba59abbe56e057f20f883e。由于这一个特性,我们可以创建数据库,将密文与明文的信息写入这一个数据库,对于数据库当中已有的数据,可以通过密文找到对应的明文。
这样,就有了MD5 + SALT的方式,也就是MD5加盐,比如以下的例子就是以字符串‘55555’作为盐,在将123456通过MD5加密后,与作为SALT的55555作拼接后再进行MD5加密,得到最终加密后的数据。
SELECT MD5(CONCAT(MD5('123456'), '55555')) FROM dual;
在实际的web系统中,可能会以用户名作为盐参与加密,又或者在用户表中创建一个SALT字段,用一个随机数作为盐,并将SALT值写入数据表。
3 bcrypt
bcrypt目前比较常用的密码加密算法,也是Spring Security推荐的加密算法,它的特点是加密后的密文依然不可以解密,而且同一个字符串每一次通过bcrypt加密得到的结果都不一样,比如我使用Spring Security所提供的的bcrypt的工具进行测试。以for循环的方式执行10次对123456的加密,得到的结果每次都不一样,而且将10个不同的密文与内容为123456的名为进行校验,得到的结果都是true。测试代码如下。
import org.junit.Test;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class TestBCrypt {
@Test
public void testGenerateByBCrypt() {
String password = "123456";
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
for(int i = 0; i < 10; i++) {
// 得到加密后的字符串
String encodePassword = bCryptPasswordEncoder.encode(password);
System.out.println(encodePassword);
// 将明文与加密后的密文作比对,返回为true表示密文是由明文得到的
System.out.println(bCryptPasswordEncoder.matches(password, encodePassword));
}
}
}
打印结果如下。
$2a$10$t6CoTxlTuD5Z/Siod9Fx2OBBdBDHPwhstvjoz4VC5dh23niqrwC2e
true
$2a$10$ed4rhp76k5k3opUHonHS1OOB/Gh53uwl46h1pgn9Y653QBQH4xdKa
true
$2a$10$bc49GDLzKDzoPBm6g56IGOz741CzorBO98sa1impRd1WCMYIkcFWe
true
$2a$10$snaF9NlIXQ49qX9M3CNFMOHvv.jchvXRzCLWAgRwCFedSvwEdBVsO
true
$2a$10$C3pagIFlNd8vA2wDfZ6DauLKYXotgjCPy7eRv815FRNTDRu4cCiJ2
true
$2a$10$VPdphXIVDWIARiUxN9DZ.uqqkqQXAfC1A6FS3UgpwuY8QCuNUcqlW
true
$2a$10$zvZ.b6IulMEEpoHzvKOQ.OZXqJob8zRl.vrU6sk4VOPmSuC0yXIVe
true
$2a$10$OGxbOLVLV/abSKtm5a4O3OsWaCEb4Hkwp8XyBbhCLHY9J9e53pKn.
true
$2a$10$w7hLUssNzaeKYjiwprArn.1tcJYX/ERqsART1x8jynRHSL35XttGa
true
$2a$10$fu0EGMY3Jstf0Yb0lJTSieVoK92oKt33YubTgOZ0NONaJt9jjHcVq
true