BCryptPasswordEncoder加密和对密码验证的原理

上一篇:spring security进阶2 添加账户并对账户密码进行加密

spring security中提供了一个加密类BCryptPasswordEncoder,可以用来对密码字符串进行加密,得到加密后的字符串。它采用哈希算法 SHA-256 +随机盐+密钥对密码进行加密

一、加密算法和hash算法的区别

加密算法是一种可逆的算法,基本过程就是对原来为明文的文件或数据按某种算法进行处理,使其成为不可读的一段代码为“密文”,但在用相应的密钥进行操作之后就可以得到原来的内容 。

哈希算法是一种不可逆的算法,是把任意长度的输入通过散列算法变换成固定长度的输出,输出就是散列值,不同的输入可能会散列成相同的输出,所以不可能从散列值来确定唯一的输入值。

二、源码解析

BCryptPasswordEncoder类实现了PasswordEncoder接口,这个接口中定义了两个方法

public interface PasswordEncoder {
String encode(CharSequence rawPassword);
boolean matches(CharSequence rawPassword, String encodedPassword);
}

其中encode(...)是对字符串进行加密的方法,matches使用来校验传入的明文密码rawPassword是否和加密密码encodedPassword相匹配的方法。即对密码进行加密时调用encode,登录认证时调用matches

下面我们来看下BCryptPasswordEncoder类中这两个方法的具体实现

1. encode方法

public String encode(CharSequence rawPassword) {
String salt;
if (strength > 0) {
if (random != null) {
salt = BCrypt.gensalt(strength, random);
}
else {
salt = BCrypt.gensalt(strength);
}
}
else {
salt = BCrypt.gensalt();
}
return BCrypt.hashpw(rawPassword.toString(), salt);
}

可以看到,这个方法中先基于某种规则得到了一个盐值,然后在调用BCrypt.hashpw方法,传入明文密码和盐值salt。所以我们再看下BCrypt.hashpw方法中做了什么

2. BCrypt.hashpw方法

public static String hashpw(String password, String salt) throws IllegalArgumentException {
BCrypt B;
String real_salt;
byte passwordb[], saltb[], hashed[];
char minor = (char) 0;
int rounds, off = 0;
StringBuilder rs = new StringBuilder(); if (salt == null) {
throw new IllegalArgumentException("salt cannot be null");
} int saltLength = salt.length(); if (saltLength < 28) {
throw new IllegalArgumentException("Invalid salt");
} if (salt.charAt(0) != '$' || salt.charAt(1) != '2') {
throw new IllegalArgumentException("Invalid salt version");
}
if (salt.charAt(2) == '$') {
off = 3;
}
else {
minor = salt.charAt(2);
if (minor != 'a' || salt.charAt(3) != '$') {
throw new IllegalArgumentException("Invalid salt revision");
}
off = 4;
} if (saltLength - off < 25) {
throw new IllegalArgumentException("Invalid salt");
} // Extract number of rounds
if (salt.charAt(off + 2) > '$') {
throw new IllegalArgumentException("Missing salt rounds");
}
rounds = Integer.parseInt(salt.substring(off, off + 2)); real_salt = salt.substring(off + 3, off + 25);
try {
passwordb = (password + (minor >= 'a' ? "\000" : "")).getBytes("UTF-8");
}
catch (UnsupportedEncodingException uee) {
throw new AssertionError("UTF-8 is not supported");
} saltb = decode_base64(real_salt, BCRYPT_SALT_LEN); B = new BCrypt();
hashed = B.crypt_raw(passwordb, saltb, rounds); 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();
}

可以看到,这个方法中先根据传入的盐值salt,然后基于某种规则从salt得到real_salt,后续的操作都是用这个real_salt来进行,最终得到加密字符串。

所以这里有一个重点:传入的盐值salt并不是最终用来加密的盐,方法中通过salt得到了real_salt,记住这一点,因为后边的匹配方法matches中要用到这一点。

3. matches方法

matches方法用来判断一个明文是否和一个加密字符串对应。

public boolean matches(CharSequence rawPassword, String encodedPassword) {
if (encodedPassword == null || encodedPassword.length() == 0) {
logger.warn("Empty encoded password");
return false;
} if (!BCRYPT_PATTERN.matcher(encodedPassword).matches()) {
logger.warn("Encoded password does not look like BCrypt");
return false;
} return BCrypt.checkpw(rawPassword.toString(), encodedPassword);
}

这个方法中先对密文字符串进行了一些校验,如果不符合规则直接返回不匹配,然后调用校验方法BCrypt.checkpw,第一个参数是明文,第二个参数是加密后的字符串。

public static boolean checkpw(String plaintext, String hashed) {
return equalsNoEarlyReturn(hashed, hashpw(plaintext, hashed));
} static boolean equalsNoEarlyReturn(String a, String b) {
char[] caa = a.toCharArray();
char[] cab = b.toCharArray(); if (caa.length != cab.length) {
return false;
} byte ret = 0;
for (int i = 0; i < caa.length; i++) {
ret |= caa[i] ^ cab[i];
}
return ret == 0;
}

注意 equalsNoEarlyReturn(hashed, hashpw(plaintext, hashed))这里,第一个参数是加密后的字符串,而第二个参数是用刚才提过的hashpw方法对明文字符串进行加密。

hashpw(plaintext, hashed)第一个参数是明文,第二个参数是加密字符串,但是在这里是作为盐值salt传入的,所以就用到了刚才说的 hashpw 内部通过传入的salt得到real_salt,这样就保证了对现在要校验的明文的加密和得到已有密文的加密用的是同样的加密策略,算法和盐值都相同,这样如果新产生的密文和原来的密文相同,则这两个密文对应的明文字符串就是相等的。

这也说明了加密时使用的盐值被写在了最终生成的加密字符串中。

三、总结

BCryptPasswordEncoder使用哈希算法+随机盐来对字符串加密。因为哈希是一种不可逆算法,所以密码认证时需要使用相同的算法+盐值来对待校验的明文进行加密,然后比较这两个密文来进行验证。BCryptPasswordEncoder在加密时通过从传入的salt中获取real_salt用来加密,保证了这一点。

spring security的BCryptPasswordEncoder加密和对密码验证的原理的更多相关文章

  1. spring security +MySQL + BCryptPasswordEncoder 单向加密验证 + 权限拦截 --- 心得

    1.前言 前面学习了 security的登录与登出 , 但是用户信息 是 application 配置 或内存直接注入进去的 ,不具有实用性,实际上的使用还需要权限管理,有些 访问接口需要某些权限才可 ...

  2. Spring Security项目的搭建以及Spring Security的BCrypt加密

    .personSunflowerP { background: rgba(51, 153, 0, 0.66); border-bottom: 1px solid rgba(0, 102, 0, 1); ...

  3. spring boot:spring security整合jwt实现登录和权限验证(spring boot 2.3.3)

    一,为什么使用jwt? 1,什么是jwt? Json Web Token, 它是JSON风格的轻量级的授权和身份认证规范, 可以实现无状态.分布式的Web应用授权 2,jwt的官网: https:// ...

  4. spring security (BCryptPasswordEncoder)加密及判断密码是否相同

    通过BCryptPasswordEncoder的加密的相同字符串的结果是不同的,如果需要判断是否是原来的密码,需要用它自带的方法. 加密: BCryptPasswordEncoder encode = ...

  5. Spring Security 中的加密BCryptPasswordEncoder

    // // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler ...

  6. BCryptPasswordEncoder加密及判断密码是否相同

    项目中用到了BCryptPasswordEncoder对密码进行二次加密,需要注意的是,加密后的字符串比较长,数据库的长度至少为60位. 通过BCryptPasswordEncoder的加密的相同字符 ...

  7. spring security实现记住我下次自动登录功能

    目录 spring security实现记住我下次自动登录功能 一.原理分析 二.实现方式 2.1 简单实现方式 2.2 数据库实现方式 三.区分是密码登录还是rememberme登录 spring ...

  8. 255.Spring Boot+Spring Security:使用md5加密

    说明 (1)JDK版本:1.8 (2)Spring Boot 2.0.6 (3)Spring Security 5.0.9 (4)Spring Data JPA 2.0.11.RELEASE (5)h ...

  9. spring security进阶2 添加账户并对账户密码进行加密

    目录 spring security 添加账户并对账户密码进行加密 一.原理分析 1.1加密原理 1.2加密后的登录过程 二.代码实现 2.1添加用户的页面如下, register.html 2.2c ...

随机推荐

  1. 10 分钟了解 Actor 模型

    http://www.moye.me/2016/08/14/akka-in-action_actor-model/ 过去十几年CPU一直遵循着摩尔定律发展,单核频率越来越快,但是最近这几年,摩尔定律已 ...

  2. MyBatis框架的insert节点-向数据库中插入数据

    需求:使用mybatis框架中的insert元素节点向数据库中插入数据 UserMapper.xml UserMapper.java 编写测试方法: @Test public void testAdd ...

  3. mybatis的判定时间字段问题 java.lang.IllegalArgumentException: invalid comparison: cn.hutool.core.date.DateTime and java.lang.String

    今天听组员说: mybatis在3.30版本及以上判定时间时 <if test="date_time != null and date_time != '' "> da ...

  4. 【loj3120】【CTS2019】珍珠

    题目 ​ $laofu $出的题 ​ \(n\)个离散型随机变量\(X_i\)可能的值为\([1,D]\) ,求有至少\(m\)对的概率 ​ $0 \le m \le 10^9  ,  1 \le n ...

  5. [bzoj1001]狼抓兔子 最小割

    题意概述:给出一张无向图,每条边有一个权值,割掉这条边代价为它的权值,求使起点不能到达终点的最小代价. 显然能看出这是个最小割嘛,然后最小割=最大流,建图的时候特殊处理一下再跑个最大流就好了. #in ...

  6. @submit.native.prevent作用

    <el-form :inline="true" :model="geCarManageData" class="demo-form-inline ...

  7. clion下批量删除断点

  8. 多维矩阵转一维数组(c++)【转载】

    在由二维矩阵转为一维数组时,我们有两种方式:以列为主和以行为主. 以列为主的二维矩阵转为一维数组时,转换公式为: index=column+row×行数 以行为主的二维矩阵转为一维数组时,转换公式为: ...

  9. assert(0)的作用

    捕捉逻辑错误.可以在程序逻辑必须为真的条件上设置断言.除非发生逻辑错误,否则断言对程序无任何影响.即预防性的错误检查,在认为不可能的执行到的情况下加一句ASSERT(0),如果运行到此,代码逻辑或条件 ...

  10. crontab定时任务接入

    # 查看 $ crontab -l # 创建 $ crontab -e # 每分钟输出一次当前时间 * * * * * echo `date` >> /demo.log # 查看定时 $ ...