大家好呀,我是小黑。
我们在开发应用时,只要涉及到用户,登录注册功能则是必不可少的。
但是,并不是所有人都能做好登录注册功能。比如最基本的密码应该如何保存?应该用哪种加密方式对密码进行加密都不是很清楚。
一旦出现数据库泄漏,密码外泄等问题,会对用户造成极大的损失。
密码该如何保存?
如果我们要在服务器中对用户进行身份验证,我们需要完成以下的步骤:
- 获取到要登录用户的用户名和密码;
- 根据用户名在数据库中查找到用户;
- 比较用户提供的密码和数据库中的密码是否一致。
那我们应该如何存储用户的密码呢?我们来看看都有哪些方式,以及存在的问题。
明文保存
将用户的密码以明文方式保存。
很显然,有点常识的人都应该知道,密码不能用明文保存的。但是话又说回来,系统都是由人开发的,开发系统的人可能并不专业。比如之前某个大型中文开发者社区,因为数据库泄露,导致大批用户的密码泄漏,而他们的密码就是明文保存的。
HASH保存
使用Hash函数计算出密码的hash值保存,可以解决密码直接暴露的问题。
Hash函数是一个单向函数,不能通过结果值反向得出原始值,Hash函数可以将一串密码转换成一个固定长度的字符串。
- 在用户注册时,将用户的密码使用Hash函数计算出Hash值后保存到数据库;
- 当用户登录时,对用户提交的密码使用相同的Hash函数计算出Hash值,和数据库中的Hash值进行比较。
这样可以避免让攻击者直接获取到用户的密码明文,攻击者想通过暴力攻击将字符串计算出hash值则需要花费巨大的精力,并且Hash值越长破解难度越大。
但是通过彩虹表攻击,攻击者仍然可以成功破解。 彩虹表是一个包含许多提前计算出Hash值的表,其中包含数百万个密码对应的hash值,对于一些简单密码可以非常快的破解。
所以,如果你不确定你注册的服务是采用哪种方式保存的密码,尽量将密码复杂度设置高一些。
加盐Hash
为了防止彩虹表攻击,可以使用Hash算法加盐处理。
盐是在进行Hash计算时,和原始密码拼接在一起进行计算的一个随机序列。
- 用户注册时,将密码和盐值组合后进行Hash计算,得到密码结果保存在数据库中;
- 当用户在登录验证时,将原始密码加盐后进行Hash计算,得到结果值和数据库中的密码进行比较。
因为彩虹表中的密码和加盐后的密码不一样,可以防止彩虹表攻击。如果盐值足够长并且随机,那么就可以保证在彩虹表中不能找到和密码相同的hash值。
但是,由于攻击者是有可能获取到盐值的,攻击者可以调整彩虹表生成的算法,用获取到的盐值计算出新的彩虹表,同样可以获取到密码。虽然计算一个新的彩虹表花费的时间巨大,但是随着硬件条件越来越好,要计算出一张彩虹表会变得越来越容易。
所以,使用Hash算法加盐处理,可以保证密码不被快速破解,但是还不够安全。
密码加密函数
Hash函数设计的初衷并不仅仅是对密码进行Hash计算,所以Hash函数的运算速度非常快,但是这样一来,攻击者也能快速计算hash值,进行暴力破解。
为了解决这个问题,我们可以让Hash加密函数变慢。
我们只要让密码加密的时间在用户能接受的时间内,尽量的慢,这样攻击者蛮力破解将会花费无限的时间。
有以下一些专门用来加密密码的算法:
- bcrypt
- scrypt
- PBKDF2
- argon2
这些算法使用一些复杂的加密算法,并会故意让计算变慢。
工作因子
可以通过在算法中配置工作因子,来调整加密函数计算时间的缓慢程度。
每个密码加密算法都有自己的工作因子。工作因子影响密码编码的速度。例如,bcrypt
有参数strength
,该算法将使2的strength
次方来计算哈希值。数字越大,编码越慢。
使用Spring Security加密密码
现在让我们看看 Spring Security 如何支持这些算法,以及我们如何使用它们加密密码。
PasswordEncoder
在Spring Security 中有一个PasswordEncoder
接口。所有密码编码器都实现了该接口。
public interface PasswordEncoder {
String encode(CharSequence rawPassword);
boolean matches(CharSequence rawPassword, String encodedPassword);
default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}
该接口中有两个方法:
encode()
方法用户将明文密码转换为密文形式;
matches()
方法用户将明文密码与密文密码进行比较。
BCryptPasswordEncoder
String plainPassword = "123456";
// 工作因子
int strength = 10;
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(strength, new SecureRandom());
String encodedPassword = bCryptPasswordEncoder.encode(plainPassword);
System.out.println(encodedPassword);
BCryptPasswordEncoder
中的参数strength
是密码加密算法的工作因子,Spring Security中的默认值为10
。
在创建时指定SecureRandom
作为随机加盐生成器。
$2a$10$pYxXvggEgN7znYKofHIr/uRTw.dsYeW9mbxzNMSNOoGIYZU8twXNG
Pbkdf2PasswordEncoder
PBKDF2 算法不是为密码编码专门设计的,而是为了从密码中派生出密钥而设计的。当我们想用密码对某些数据进行加密时,通常需要密钥,但密码的强度不足以用作加密密钥。
String plainPassword = "123456";
//加密秘钥