0 前言
众所周知,密码肯定不能用明文存储。
之前一直使用MD5进行加密,现在才知道有彩虹表这回事。所以记录一下对应的处理方式:加密盐。
1 彩虹表
例如用MD5加密,随便没法破解,但是有些常用的字符被收集到彩虹表里那就危险了。
例如:123456
使用MD5加密(32小写)后为:e10adc3949ba59abbe56e057f20f883e
一旦这个字符串被泄露,那么使用这个密码的用户就有极大的风险。
2 加密盐
加密盐(Salt)是一种在密码学中用于增加密码存储安全性的技术。盐通常是一个随机生成的字符串,它被添加到原始密码的前端、后端或嵌入到密码中间,然后再与密码一起进行哈希运算。这样做的目的是为了增加密码破解的难度,特别是针对彩虹表攻击(一种预先计算好的哈希值对照表,用于快速查找密码)和暴力破解攻击。
2.1 手搓加密盐
2.1.1 密码加盐(加密)
输入密码
↓
产生盐值 -> 唯一字符串
↓
盐值拼接字符串 生成加盐后的密码
↓
加盐后的密码再次拼接输入的密码,生成最终的密文
2.1.2 密码验证
输入用户+密码
↓
查出用户的密码
↓
密码分割,找出密码的盐值
↓
用户输入的密码+盐值 进行加密
↓
拼接后 得到的加密值与查出的密码进行对比验证
↓
后续操作
2.1.3 工具类
/**
* 1.加盐并生成最终的密码
*
* @param password 明文的密码
* @return 最终生成的密码
*/
public static String encrypt(String password) {
//a.产生盐值
//UUID.randomUUID()会生成32位数字+4位-,是随机的唯一的,将4位-去掉就得到32位数字的盐值
String salt = UUID.randomUUID().toString().replace("-", "");
//生成加盐后的密码(需要使用MD5)
String saltPassword = DigestUtils.md5DigestAsHex((salt + password).getBytes());
//生成最终的密码格式
String finalPassword = salt + "#" + saltPassword;
return finalPassword;
}
/**
* 2.加盐并生成最终密码格式(方法一的重载),区别于上面的方法:这个方法是用来解密的,给定了盐值,生成一个最终密码,
* 后面要和正确的最终密码进行比对
*
* @param password 需要验证的明文密码
* @param salt
* @return
*/
public static String encrypt(String password, String salt) {
//1.生成一个加密后的密码
String saltPassword = DigestUtils.md5DigestAsHex((salt + password).getBytes());
//2.生成最终的密码(待验证)
String finalPassword = salt + "#" + saltPassword;
return finalPassword;
}
/**
* 3.验证密码
*
* @param inputPassword 登录用户输入的明文密码
* @param finalPassword 数据库中实际的最终密码格式
* @return
*/
public static boolean check(String inputPassword, String finalPassword) {
//首先判断这两个参数到底有没有值,数据库中的最终密码是不是65位
if (StringUtils.hasLength(inputPassword) && StringUtils.hasLength(finalPassword)
&& finalPassword.length() == 65) {
//a.首先从最终的密码中得到盐值
//使用$将finalPassword划分成两个部分,前面的32位的部分就是盐值
//注意:这里的#是被认为是一个通配符,所以要转义一下
String salt = finalPassword.split("\\#")[0];
//b.使用之前加密的方法,生成最终的密码格式(待验证)
String checkPassword = encrypt(inputPassword, salt);
if (checkPassword.equals(finalPassword)) {
return true;
}
}
return false;
}
2.1.4 验证
我在这里 对 "woshimima"这个字符串进行了五次加密盐操作,输出的值每次都不一样。
for (int i = 0; i < 5; i++) {
String woshimima = this.encrypt("woshimima");
System.out.println(woshimima);
}
2c07fb2d8a8c480e9e028d3f877484d1#b188f278a2274491401a0dc98b900fb4
f5299668dc9947818bcdbe0e758e8e31#090582f9c286319ddb563da2a7a3ee48
15fc5201bccb4088975f9acf0f6ccfca#7e293be19e4d985cab4cbcf2eddb530c
a9596645403e45cf9cf852268310eb5f#1f686cc11f5ea73b87ac0de68b7527d5
7817e5dfc0c44aa3b94fe835717aef43#3c6e228769d8809477ba741f954e8ee0
无论拿哪一个去验证都会是正确
2.2 使用spring security 的密码加盐算法
2.2.1 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2.2.2 测试
在启动类上加入(exclude = SecurityAutoC onfiguration.class) 关闭注册登录的验证
@SpringBootApplication(exclude = SecurityAutoConfiguration.class)
public class AdminApplication {
public static void main(String[] args) {
SpringApplication.run(MeduAdminApplication.class, args);
}
}
单元测试:
@Test
void contextLoads() {
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
String str = "123456";
//进行加密
String finalPassword = bCryptPasswordEncoder.encode(str);
System.out.println(finalPassword);
//验证密码
String inputPassword1 = "123456";
String inputPassword2 = "666666";
//inputPassword是用户输入的密码(待验证),finalPassword是存储在数据库中的最终密码格式
System.out.println(bCryptPasswordEncoder.matches(inputPassword1,finalPassword));
System.out.println(bCryptPasswordEncoder.matches(inputPassword2,finalPassword));
}
3 写在最后
这样的话,泄露加密盐加密后的密码也是大不了的事,根本破解不了,即使是123456也展现为 无数种字符串形式