加密/加盐
加密介绍
在MySQL数据库中, 我们常常需要对密码, ⾝份证号, ⼿机号等敏感信息进⾏加密, 以保证数据的安全性。如果使⽤明⽂存储, 当⿊客⼊侵了数据库时, 就可以轻松获取到用户的相关信息, 从⽽对用户或者企业造成信息泄漏或者财产损失。
⽬前我们用户的密码还是明⽂设置的, 为了保护用户的密码信息, 我们需要对密码进⾏加密。
加密算法分类
密码算法主要分为三类: 对称密码算法, ⾮对称密码算法, 摘要算法
- 对称密码算法:是指加密密钥和解密密钥相同的密码算法。常⻅的对称密码算法有: AES, DES, 3DES,RC4, RC5, RC6 等。
- ⾮对称密码算法:是指加密密钥和解密密钥不同的密码算法. 该算法使⽤⼀个密钥(公钥)进⾏加密, ⽤另外⼀个密钥(私钥)进⾏解密。
- 加密密钥可以公开,⼜称为公钥
- 解密密钥必须保密,⼜称为私钥
常⻅的⾮对称密码算法有: RSA, DSA, ECDSA, ECC 等
- 摘要算法:是指把任意⻓度的输⼊消息数据转化为固定⻓度的输出数据的⼀种密码算法。摘要算法是不可逆的, 也就是无法解密。通常⽤来检验数据的完整性的重要技术, 即对数据进⾏哈希计算然后⽐较摘要值, 判断是否⼀致。常⻅的摘要算法有: MD5, SHA系列(SHA1, SHA2等), CRC(CRC8, CRC16,CRC32)
加密思路
博客系统中, 我们采⽤MD5算法来进⾏加密。
- MD5如何加密?
摘要算法:同样的明文经过同样的摘要算法,得到的结果是一样的。
验证方法:验证经过摘要算法处理之后的结果,如果密文一样,那么就认为明文是一样的。
(1)数据库存储的一定是密文
(2)用户输入的一定是明文
(3)把用户输入的明文,经过MD5处理之后,和数据库的密文进行比对,结果一样,就认为正确
虽然经过MD5加密后的密⽂⽆法解密, 但由于相同的字符串经过MD5加密之后的密文是相同的, 当存储用户密码的数据库泄露后, 攻击者会很容易便能找到相同密码的用户, 从⽽降低了破解密码的难度。因此, 在对用户密码进⾏加密时,需要考虑对密码进⾏包装, 即使是相同的密码, 也保存为不同的密文。即使用户输⼊的是弱密码, 也考虑进⾏增强, 从⽽增加密码被攻破的难度。
- 加入随机盐值
采⽤为⼀个密码拼接⼀个随机字符来进⾏加密, 这个随机字符我们称之为"盐"。假如有⼀个加盐后的加密串,⿊客通过⼀定手段破解这个加密串, 他拿到的明⽂并不是我们加密前的字符串, ⽽是加密前的字符串和盐组合的字符串, 这样相对来说⼜增加了字符串的安全性。
明文+随机盐值=复杂的明文----->进行MD5加密------>密文
- 在随机盐值的基础上,如何验证?
加密逻辑:明文+随机盐值,进行MD5加密,得到密文
数据库中存储的是:加密之后的密文,随机盐值
验证逻辑:待验证的明文+这个随机盐值,进行MD5加密,和数据库的密文进行对比
- 具体实现流程:
用户注册:
- 生成随机盐值
- 用户的明文密码+随机盐值,通过MD5进行加密
- 保存随机盐值和密文
用户登录(校验)
- 获取用户注册时的随机盐值
- 待验证的明文+第一步的随机盐值,通过MD5进行加密
- 判断第二步的密文和用户注册时数据库中存的密文是否相同
测试类
public class SecurityUtilsTest {
// 加密
@Test
public void encrypt() {
String password = "123456";// 明文
String md5Str = DigestUtils.md5DigestAsHex(password.getBytes());// 对明文进行MD5加密
System.out.println(md5Str);
String salt = UUID.randomUUID().toString().replace("-", "");// 生成随机盐值
System.out.println(salt);
// (salt+明文)-->MD5加密,得到密文
String securityPassword = DigestUtils.md5DigestAsHex((salt+password).getBytes());// 密文
// 数据库存储的是:salt+密文
String finalPassword = salt+securityPassword;
System.out.println(finalPassword);
}
}
运行程序,观察结果:
再次运行程序,会发现:相同的明文,经过加密之后,每次的密文都不相同:
@Test
public void verify() {
String inputPassword = "123456";// 用户输入的明文
// 数据库存储的信息:salt + MD5(salt+明文)
String sqlPassword = "a0bda3270f254be0af20a86787f1ae6e4c791fde602e8207e08fd3f38df178cf";
if (sqlPassword == null || sqlPassword.length() != 64) {
System.out.println("校验失败");
}
String salt = sqlPassword.substring(0,32);// 拿盐值:数据库存储的前32位
// MD5(salt+用户输入的明文)
String secretPassword = DigestUtils.md5DigestAsHex((salt+inputPassword).getBytes());
String finalPassword = salt+secretPassword;//最终的密文
if (finalPassword.equals(sqlPassword)) {
System.out.println("校验成功");
} else {
System.out.println("校验失败");
}
}
运行程序,验证结果:
重新生成一个密文,继续校验:
运行结果:
写加密/解密工具类
@Slf4j
public class SecurityUtils {
// 加密
public static String encrypt(String password) {
String salt = UUID.randomUUID().toString().replace("-", "");// 盐值
String securityPassword = DigestUtils.md5DigestAsHex((salt+password).getBytes());// 密文
String finalPassword = salt + securityPassword;// 存储在数据库中:salt + MD5(salt+明文)
return finalPassword;
}
// 校验
public static boolean verify(String inputPassword, String sqlPassword) {
if (sqlPassword == null || sqlPassword.length() != 64) {
log.error("数据库中的密码格式不对");
return false;
}
String salt = sqlPassword.substring(0,32);
String secretPassword = DigestUtils.md5DigestAsHex((salt+inputPassword).getBytes());
String finalPassword = salt + secretPassword;
// if (finalPassword.equals(sqlPassword)) {
// return true;
// }
// log.error("校验失败");
// return false;
return finalPassword.equals(sqlPassword);
}
}
修改数据库密码
数据库中的原始密码:
⽤测试类给密码123456⽣成密⽂:
846cc0edb8ea43c09f4fe8c1eef45b567c27e95c15f112417f0b27d098d14db7
7b5bd4825a5e461eb7be1128ff7033ff2c1ac41cec671b4a6bd3e2739b45ebed
修改数据库明⽂密码为密⽂, 执行SQL
update user set
password='846cc0edb8ea43c09f4fe8c1eef45b567c27e95c15f112417f0b27d098d14db7'
where id=1;
update user set
password='7b5bd4825a5e461eb7be1128ff7033ff2c1ac41cec671b4a6bd3e2739b45ebed'
where id=2;
修改登录接口
修改UserController中登录接口密码校验的逻辑:
启动程序,访问http://127.0.0.1:8080/blog_login.html,进行登录验证: