赞
踩
密码加密方式是指通过使用特定的算法将原始密码转换成一个(在理想情况下)无法还原的不可读形式,以确保即使数据被泄露,密码也不会轻易地被恶意用户获取。在计算机安全中,这个过程通常称为散列(Hashing)。
密码在存储时通常会使用散列函数处理。散列函数能够接收任意长度的输入(比如一个密码),并产生一个固定长度的输出(即散列值或哈希值)。这个过程应该是单向的,也就是说,从散列值获得原始输入应该是不可能的或极其困难的。
加密后, 密文可以反向解密得明文原文;
1). 对称加密
指加密和解密使用相同密钥的加密算法。
优点: 对称加密算法的优点是算法公开、计算量小、加密速度快、加密效率高。
缺点: 没有非对称加密安全。
常见的对称加密算法:DES、3DES、DESX、Blowfish、RC4、RC5、RC6和AES
说白了加密和解密都使用同一个秘钥处理;
2). 非对称加密
指加密和解密使用不同密钥的加密算法,也称为公私钥加密。假设两个用户要加密交换数据,双方交换公钥,使用时一方用对方的公钥加密,另一方即可用自己的私钥解密。
加密和解密:
优点: 非对称加密与对称加密相比,其安全性更好;
缺点: 非对称加密的缺点是加密和解密花费时间长、速度慢,只适合对少量数据进行加密。
一旦加密就不能反向解密得到密码原文 。通常用于密码数据加密。
常见的不可逆加密算法有: MD5 、SHA、HMAC
1).MD5
MD5是比较常见的加密算法,广泛的应用于软件开发中的密码加密,通过MD5生成的密文,是无法解密得到明文密码的。但是现在在大数据背景下,很多的网站通过大数据可以将简单的MD5加密的密码破解。
可以在用户注册时,限制用户输入密码的长度及复杂度,从而增加破解难度。
2). Bcrypt
用户表的密码通常使用 MD5 等不可逆算法加密后存储,为防止彩虹表破解,会先使用一个特定的字符串(如域名)加密,然后再使用一个随机的 salt(盐值)加密。 特定字符串是程序代码中固定的,salt 是每个密码单独随机,一般给用户表加一个字段单独存储,比较麻烦。
BCrypt 算法将 salt 随机并混入最终加密后的密码,验证时也无需单独提供之前的salt,从而无需单独处理 salt 问题。
在SecurityConfig配置类配置密码加密匹配器:
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
加密密码:
@Autoware
private BCryptPasswordEncoder bCryptPasswordEncoder;
@Test
public void test01(){
for (int i = 0; i < 5; i++) {
System.out.println(bCryptPasswordEncoder.encode("123456"));
}
}
得到结果:
$2a$10$C6YynRFeJsSy7D/kg3d30OWnuwko7KQIEK5JrX0mWND.vuz2TqwpK
$2a$10$aSJfxH2oBtopFMbkMJ.PQ.sbSBXJH9g.9bv1mCyte/BtcU9VTs7lG
$2a$10$nVoB.eV5Uhc9FNUC36Pn0OosGh7aKlp7Sjfxaiml8NCSJ6PX1q6.m
$2a$10$2RM3mRNjz1LoZ5eeLdj.Hu15vlWIIj2zJC09vwTevBlIi5rjJStam
$2a$10$c2sZT/LtM1ExWfZjO0yIPeTGSqMSlX7oi.SvliMbeZpT9Y4qIBDue
验证密码:
boolean matches =
bCryptPasswordEncoder.matches("123456", "$2a$10$c2sZT/LtM1ExWfZjO0yIPeTGSqMSlX7oi.SvliMbeZpT9Y4qIBDue");
System.out.println(matches);//返回值为true, 则代表验证通过; 反之, 验证不通过
注意:此时重新启动security_demo测试工程,security底层会自动调用PasswordEncoder类型bean进行密码校验处理;
SpringSecurity提供了实现PasswordEncoder接口的密码加密工具类:BcryptPasswordEncoder,该类基于Bcrypt强哈希算法来加密密码,更加安全;
Bcrypt强哈希方法每次加密相同的明文得到的密文结果都不一样,这样即使数据库泄露,黑客也很难破解密码;
1)Bcrypt加密
一般在注册用户时,Bcrypt使用SHA-256加密算法+随机盐值+秘钥(明文密码)进行加密处理,得到的密文存入数据库中;
@Test
public void testPwd(){
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String enPwd1 = passwordEncoder.encode("123456");
System.out.println("enPwd1:"+enPwd1);
String enPwd2 = passwordEncoder.encode("123456");
System.out.println("enPwd2:"+enPwd2);
}
相同的明文加密得到的密文各不相同:
enPwd1:$2a$10$yZJPn5tlBVSW3Udt926HcO8AiyzBKcLi4gKaJrVNG1Yw6LlmYCZhm
enPwd2:$2a$10$H4P8eWCGM.kpy8/.ayYfruoJbFMyi4lnIxjBYP2yB7d6yqiQS8NTi
进入encode方法:
public String encode(CharSequence rawPassword) {
if (rawPassword == null) {
throw new IllegalArgumentException("rawPassword cannot be null");
} else {
String salt = this.getSalt();
return BCrypt.hashpw(rawPassword.toString(), salt);
}
}
生成加密的代码
public static String hashpw(String password, String salt) { byte[] passwordb = password.getBytes(StandardCharsets.UTF_8); return hashpw(passwordb, salt); } public static String hashpw(byte[] passwordb, String salt) { char minor = 0; StringBuilder rs = new StringBuilder(); if (salt == null) { throw new IllegalArgumentException("salt cannot be null"); } else { int saltLength = salt.length(); if (saltLength < 28) { throw new IllegalArgumentException("Invalid salt"); } else if (salt.charAt(0) == '$' && salt.charAt(1) == '2') { byte off; if (salt.charAt(2) == '$') { off = 3; } else { minor = salt.charAt(2); if (minor != 'a' && minor != 'x' && minor != 'y' && minor != 'b' || salt.charAt(3) != '$') { throw new IllegalArgumentException("Invalid salt revision"); } off = 4; } if (salt.charAt(off + 2) > '$') { throw new IllegalArgumentException("Missing salt rounds"); } else if (off == 4 && saltLength < 29) { throw new IllegalArgumentException("Invalid salt"); } else { int rounds = Integer.parseInt(salt.substring(off, off + 2)); String real_salt = salt.substring(off + 3, off + 25); byte[] saltb = decode_base64(real_salt, 16); if (minor >= 'a') { passwordb = Arrays.copyOf(passwordb, passwordb.length + 1); } BCrypt B = new BCrypt(); byte[] hashed = B.crypt_raw(passwordb, saltb, rounds, minor == 'x', minor == 'a' ? 65536 : 0); rs.append("$2"); if (minor >= 'a') { rs.append(minor); } rs.append("$"); if (rounds < 10) { rs.append("0"); } rs.append(rounds); rs.append("$"); encode_base64(saltb, saltb.length, rs); encode_base64(hashed, bf_crypt_ciphertext.length * 4 - 1, rs); return rs.toString(); } } else { throw new IllegalArgumentException("Invalid salt version"); } } }
我们发现明文密码加密时,Bcrypt底层生成一个随机盐值参与加密运算,同时也会将盐值和加密后的密文一并形成最终密文;
2)Bcrypt密码匹配
一般在用户认证登录时,业务方法首先根据用户信息获取对应的密文信息,然后将明文和密码经过matches方法获取匹配结果为true则表示密码一致;
Bcrypt底层是先根据密文获取生成的盐值,然后再将盐值与明文加密得到密文,这个密文如果与从数据库获取的密文一致,则说明输入的名称是正确的。
而且根据下面的分析,可以发现,解密的过程中会根据传递过来的明文和密文经过一系列计算获取real_salt
真实的盐值————>真实的盐值是salt
盐值的某一部分。而具体多少位是有效的需要根据你的明文进行判断得到,然后根据真实的盐值再将明文加密后与盐salt
组合后获取密文。最后与密文信息进行匹配,这样大大提高了安全性。
@Test
public void testMatcher() {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String enPwd="$2a$10$yZJPn5tlBVSW3Udt926HcO8AiyzBKcLi4gKaJrVNG1Yw6LlmYCZhm";
boolean isSuccess = passwordEncoder.matches("123456", enPwd);
System.out.println(isSuccess);
}
public boolean matches(CharSequence rawPassword, String encodedPassword) { if (rawPassword == null) { throw new IllegalArgumentException("rawPassword cannot be null"); } else if (encodedPassword != null && encodedPassword.length() != 0) { if (!this.BCRYPT_PATTERN.matcher(encodedPassword).matches()) { this.logger.warn("Encoded password does not look like BCrypt"); return false; } else { //这里拿到了 return BCrypt.checkpw(rawPassword.toString(), encodedPassword); } } else { this.logger.warn("Empty encoded password"); return false; } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。