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. JS获取访客IP进行自动跳转

    因业务需要进行地区判断跳转指定站点,下面是我个人实现的办法,分享给大家,仅供参考,切勿做非法用途 第一步,获取IP并判断归属地 直接使用搜狐的IP库查询接口 <script type=" ...

  2. python无法导入自己的模块的解决办法

  3. 用Wget下载的文件在哪里可以找到。。

    输入命令: cd ~ 然后 ls 就ok了.

  4. 洛谷 [USACO05DEC] 布局 题解

    今天学了差分约束系统, 这是一道板子题. 核心:a[v]>a[u]+d 相当于从u到v连一条长度为d的有向边.由于要判断有环,所以要从0点先跑一遍spfa因为1点不一定能到所有的点. #incl ...

  5. Greenplum 6 新功能 在线扩容工具GPExpand (转载)

    Gpexpand是Greenplum数据库的扩容工具,可以为集群增加新节点从而可以存储更多的数据,提供更高的计算能力.Greenplum 5及之前,集群扩容需要停机增加新节点,然后对表数据做重分布.因 ...

  6. Tomcat启动问题:严重[main] org.apache.catalina.core.AprLifecycleListener.init An incompatible version...

    今天观察tomcat启动日志,有一些以前没注意到的信息: 严重 [main] org.apache.catalina.core.AprLifecycleListener.init An incompa ...

  7. ARC093F Dark Horse 【容斥,状压dp】

    题目链接:gfoj 神仙计数题. 可以转化为求\(p_1,p_2,\ldots,p_{2^n}\),使得\(b_i=\min\limits_{j=2^i+1}^{2^{i+1}}p_j\)都不属于\( ...

  8. USACO 2009 Feb 股票市场 Stock Market

    USACO 2009 Feb 股票市场 Stock Market Description 尽管奶牛们天生谨慎,她们仍然在住房抵押信贷市场中大受打击,现在她们准备在股市 上碰碰运气.贝西开挂了,她知道S ...

  9. 【BIRT】汉化设计器

    点击下面文字即可下载汉化包 birt汉化_plugins.rar 下载完成之后,解压: 将上述文件copy到目录../eclipse/plugins/下即可 文件复制后,重新启动eclipse,则已经 ...

  10. JavaScript中的内存溢出与内存泄漏

    内存溢出 是一种程序运行出现的错误: 当程序运行需要的内存超过了剩余的内存时, 就出抛出内存溢出的错误 var obj = {} for (var i = 0; i < 100000; i++) ...