🎉🎉🎉点进来你就是我的人了
博主主页:🙈🙈🙈戳一戳,欢迎大佬指点!欢迎志同道合的朋友一起加油喔🤺🤺🤺
目录
在当今的信息时代,数据安全的重要性不言而喻。我们的系统中包含了许多敏感数据,其中就包括用户的密码。尽管密码是敏感的,但直接存储明文密码会导致严重的安全问题。因此,对密码进行哈希加密以提供更好的安全性是常见的做法。在这篇文章中,我将带你深入理解加盐哈希加密算法,并演示如何在Java中实现它。
一、什么是哈希加密?
哈希加密是一种通过哈希函数对输入数据进行转换,输出一个固定长度的字符串的过程。这个输出称为哈希值或者哈希码。哈希函数具有以下特性:
- 同样的输入将产生同样的输出。
- 不同的输入尽可能产生不同的输出。
- 无法从哈希值反推回输入值。
对于密码存储来说,哈希加密非常有用。我们将用户的明文密码通过哈希函数转换为哈希值,然后将这个哈希值存储在数据库中,而非明文密码。在验证用户身份时,我们只需要将用户输入的密码经过同样的哈希函数,然后比较哈希值是否一致即可。
二、哈希加密的问题
然而,纯粹的哈希加密也有其弱点。如果两个用户使用相同的密码,那么他们的哈希值也将相同。这就意味着,如果一个用户的哈希密码被泄露,那么使用相同密码的其他用户也会受到威胁。此外,由于哈希函数是公开的,黑客可以通过“彩虹表”攻击预先计算并存储哈希值与原始密码的对应关系,从而反推出明文密码。
彩虹表是一种用于破解哈希函数的预计算表。它包含了许多预计算的哈希值和对应的原始数据(密码)。攻击者可以通过比对彩虹表,找出与泄露的哈希值相匹配的原始数据,从而破解出原始密码。这种攻击方式对于那些使用简单密码的用户尤其有效,因为这些简单密码很可能已经被包含在彩虹表中。
三、加盐哈希加密
- 为了防止彩虹表攻击,就出现了"加盐"的概念。"加盐"指的是在用户密码哈希之前,先将一个额外的随机数据(称为“盐”)添加到用户密码中。然后,将这个新的字符串(即原始密码+盐)进行哈希,得到的结果再存储到数据库中。同时,这个“盐”也需要被存储,以便在验证密码时使用。
- 加盐的目的是,即使两个用户使用相同的密码,由于"盐"的随机性,他们的哈希值也会是不同的。这使得攻击者不能使用彩虹表进行攻击,因为他们需要为每个可能的盐值构建一个新的彩虹表,这在实践中是非常困难的。
- 在实际应用中,为了增加破解的难度,"盐"的值通常会足够长,并且每次用户更改密码时,都会生成新的"盐
- 所以,简单来说,"加盐"加密就是给你的密码加点"调料",让每个人的密码都有独特的味道,这样就算有人偷偷尝试了你的密码,他们也尝不出原始的味道。
为什么说一个盐值对应一个彩虹表呢?
- 哈希算法中,"key"是你要加密的原始数据(例如密码),而"value"是生成的哈希值。盐值的加入会改变原始数据(也就是"key"),从而导致生成的哈希值(也就是"value")发生变化。
- 如果攻击者只知道哈希值,而不知道盐值,那么他就无法通过查找彩虹表(即通过哈希值查找原始密码)来获取原始密码,因为彩虹表是基于未加盐的密码生成的。
- 换句话说,即使攻击者拥有一份完整的彩虹表(这已经非常难以实现了),如果他不知道盐值,他仍然无法通过哈希值找到原始的密码,因为彩虹表中的所有哈希值都是对未加盐的密码进行哈希运算得到的。
- 总的来说,每个盐值对应一张彩虹表的原因在于盐值的加入改变了密码的哈希值,使得攻击者不能再用一张彩虹表来破解所有用户的密码,而需要为每个盐值分别创建一张彩虹表。
四、Java中的加盐哈希加密实践
接下来,我将以Java为例,介绍如何实现加盐加密和验证加盐加密密码。
1. 加盐加密代码实例
/**
* 加盐加密
* @param password 明文密码
* @return 加盐加密的密码
*/
public static String encrypt(String password) {
// 这是一个静态方法,输入的参数是你想要加密的原始密码。
//1.产生盐值
String salt = UUID.randomUUID().toString().replace("-","");
// UUID.randomUUID().toString() 用于生成一个随机的唯一识别码,这个码的格式是一个32位的数字,包含四个"-"。
// replace("-","") 是为了将这些"-"去除,得到一个完全由数字和字母组成的32位字符串,这就是所谓的"盐值"。
//2.使用(盐值+明文密码) 得到加密的密码
String finalPassword = DigestUtils.md5DigestAsHex((salt + password).getBytes());
// salt + password 是将盐值和密码拼接在一起,getBytes() 方法是将拼接后的字符串转化为字节数组,以便后面进行哈希运算。
// DigestUtils.md5DigestAsHex 是Spring框架提供的一个工具类,用于进行MD5哈希运算,并将结果转化为16进制的字符串。
// 这样我们就得到了加盐后的密码。
//3.将盐值和加密的密码共同返回(合并盐值和加密码)
String dbPassword = salt + "$" + finalPassword;
// 最后,我们将盐值、一个"$"和加密后的密码拼接在一起,这样在验证密码时,我们可以知道盐值是什么。
// "$"在这里是作为盐值和密码之间的分隔符,也可以选择其他不会在盐值和密码中出现的字符。
return dbPassword;
// 将最终的字符串返回,这就是存入数据库的密码。
}
通过这种方法,即使两个用户的原始密码相同,他们在数据库中存储的密码也会不同,因为盐值是随机生成的。同时,由于盐值是与密码一同存储的,所以在验证密码时也能知道盐值是什么,从而正确地验证密码。
2. 验证加盐加密的密码代码实例
/**
* 验证加盐加密密码
* @param password 明文密码(不一定对, 需要验证明文密码)
* @param dbPassword 数据库存储的密码(包含: salt+$+加盐加密密码)
* @return
*/
public static boolean decrypt(String password, String dbPassword) {
// 这个方法是静态方法,输入参数是用户输入的待验证密码和数据库中存储的密码。方法将返回一个布尔值,表示密码是否正确。
boolean result = false;
// 默认的结果是 false,也就是默认密码是错误的。
if(StringUtils.hasLength(password) && StringUtils.hasLength(dbPassword)
&& dbPassword.length() == 65 && dbPassword.contains("$")) { //参数正确
// 这个if语句是进行一些基本的参数检查,检查输入的密码和数据库中存储的密码是否都不为空,
// 数据库中存储的密码长度是否是65(因为盐值长度是32,加密后的密码长度也是32,再加上一个"$",总长度就是65),
// 以及数据库中存储的密码是否包含"$"。
//1. 得到盐值
String[] passwordArr = dbPassword.split("\\$");
// 使用split方法将数据库中的密码以"$"为分隔符分割成两部分,第一部分是盐值,第二部分是加盐后的密码。
//1.1 盐值
String salt = passwordArr[0];
// 获取盐值,这是分割后的第一部分。
//1.2 得到正确密码的加盐加密密码
String finalPassword = passwordArr[1];
// 获取加盐后的密码,这是分割后的第二部分。
//2. 生成验证密码的加盐加密密码
String checkPassword = encrypt(password, salt);
// 使用用户输入的待验证密码和盐值进行同样的加密操作,得到待验证的加盐后的密码。
if(dbPassword.equals(checkPassword)) {
result = true;
}
// 如果待验证的加盐后的密码和数据库中存储的加盐后的密码相等,那么说明密码正确,将结果设为true。
}
return result;
// 返回验证结果。
}
这种方法的优点是即使攻击者拿到了数据库中存储的密码和盐值,也无法直接知道原始的密码是什么,因为加盐后的密码和原始的密码是通过不可逆的哈希函数得到的。只有知道了原始的密码,才能通过同样的加盐和哈希操作得到同样的结果,从而验证密码是否正确。
五、MD5加密流程对比加盐加密流程
-
MD5加密流程:
-
用户在输入密码后,系统首先将用户的密码作为输入参数传给MD5函数。
-
MD5函数会对输入进行一系列复杂的数学运算,生成一个128位的哈希值,也就是我们所说的MD5值。
-
这个MD5值通常以32位的十六进制数表示,我们将这个值存储在数据库中。
-
当用户下次登录时,系统会再次将输入的密码进行MD5运算,并将得到的MD5值与存储在数据库中的值进行比较。如果两者相同,则验证成功,用户登录成功。
-
-
加盐加密流程:
-
首先,系统会生成一个随机的字符串,我们称之为"盐"。
-
然后,系统会将用户输入的密码和这个盐值拼接在一起,形成一个新的字符串。
-
这个新的字符串会作为输入参数传给哈希函数,通常我们仍然可以使用MD5函数或者其他更安全的哈希函数如SHA-256。
-
哈希函数会对这个新的字符串进行哈希运算,得到一个哈希值。我们将这个哈希值和盐值一起存储在数据库中。
-
当用户下次登录时,系统会将用户输入的密码和存储在数据库中的盐值进行拼接,并进行哈希运算。然后将得到的哈希值与存储在数据库中的哈希值进行比较。如果两者相同,则验证成功,用户登录成功。
-
加盐加密在MD5的基础上增加了一个随机的盐值,这样就使得即使两个用户的密码相同,他们的哈希值也会因为盐值的不同而不同。这大大增加了破解的难度,因为攻击者现在需要针对每一个盐值都进行一次攻击,而不能像攻击MD5那样只需要一次攻击就可以破解所有密码。
六、spring框架实现加盐加密
Spring Security是Spring框架下的一个子框架,它主要负责应用程序的安全性,包括身份验证和授权。在Spring Security中,有一个类BCryptPasswordEncoder
,可以用于使用加盐的BCrypt算法对密码进行哈希。
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class PasswordEncoderUtil {
private static BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
//加密密码
public static String encodePassword(String rawPassword) {
return passwordEncoder.encode(rawPassword);
}
// 验证密码
public static boolean matches(String rawPassword, String encodedPassword) {
return passwordEncoder.matches(rawPassword, encodedPassword);
}
}
在上述代码中,
BCryptPasswordEncoder
类用于创建基于BCrypt的加盐哈希密码。在创建对象时,BCryptPasswordEncoder会自动在每次加密时生成一个随机盐,并将其合并到哈希值中。对于相同的明文,每次生成的加密文本都会不同。在密码验证时,
matches()
方法会自动从存储的哈希值中提取盐,与提供的明文密码一起进行哈希运算,并将结果与存储的哈希值进行比较。在使用上述工具类对密码进行加密后,你可以将加密后的密码存储在数据库中。当需要验证密码时,只需调用
matches()
方法进行验证即可。
下面是如何使用Spring Security进行加盐哈希密码的步骤解析:
1. 在你的项目中,需要先在POM.xml文件或build.gradle中加入Spring Security的依赖。对于Maven项目,你可以在POM.xml中添加以下代码:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2. 创建一个BCryptPasswordEncoder
对象:
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
3. 你可以使用BCryptPasswordEncoder
对象的encode
方法对密码进行哈希:
String hashedPassword = passwordEncoder.encode(plainPassword);
其中plainPassword
是原始的未加密的密码。encode
方法会对原始密码进行BCrypt哈希,并添加一个随机的盐值。然后,你可以将hashedPassword
保存到数据库中。
4. 当需要验证密码时,你可以使用BCryptPasswordEncoder
对象的matches
方法:
boolean isPasswordMatch = passwordEncoder.matches(rawPassword, hashedPassword);
其中rawPassword
是用户输入的密码,hashedPassword
是存储在数据库中的哈希密码。matches
方法会先将存储在哈希密码中的盐值添加到用户输入的密码上,然后进行哈希,最后将哈希后的结果和存储在数据库中的哈希密码进行比较。如果两者相同,那么matches
方法将返回true
,否则返回false
。
注意,Spring Security使用的BCrypt算法已经内置了添加和使用盐值的机制,你不需要自己生成和存储盐值。这大大简化了使用加盐哈希密码的流程。
如果你觉得这篇文章有价值,或者你喜欢它,那么请点赞并分享给你的朋友。你的支持是我创作更多有用内容的动力,感谢你的阅读和支持。祝你有个美好的一天!