springsecurity 中的 BCrypt 加密算法

一、简介

1️⃣BCrypt 加密:一种加盐的单向 Hash,不可逆的加密算法,同一种明文,每次加密后的密文都不一样,而且不可反向破解生成明文,破解难度很大。每次加密的时候首先会生成一个随机数就是盐,之后将这个盐值与明文密码进行 hash,得到 一个hash值存到数据库中。其中生成的 hash 值中包含了之前生成的盐值(22个字符),用于后续 hash 值验证。

2️⃣MD5 加密:是不加盐的单向 Hash,不可逆的加密算法,同一个密码经过 hash 的时候生成的是同一个 hash 值,在大多数的情况下,有些经过 md5 加密的方法将会被破解。

二、过程

1️⃣生成盐值 salt。调用 BCrypt 类的构造函数,具体如下:

BCrypt.gensalt();
BCrypt.gensalt(String strength);
BCrypt.gensalt(String strength, SecureRandom secureRandom );

其中 strength 是散列数因子,范围是 4 到 31,默认是 10。random 是随机数产生器,也就是 new SecureRandom() 这个。

2️⃣生成加密字符串 hashed。调用 BCrypt 类的 hashpw(String password, String salt)。其中 password 是明文密码,salt 是第一步生成的盐值。

BCrypt.hashpw(rawPassword.toString(), salt)

3️⃣验证密文是否正确。调用 BCrypt 类的checkpw(String plaintext, String hashed)。其中 plaintext 是需要传入的明文,hashed 是第二步生成的密文。

BCrypt.checkpw(rawPassword.toString(), encodedPassword)

该方法返回布尔类型,true 表示验证通过,false 表示不通过。

进一步解析checkpw(String plaintext, String hashed)函数:

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;
}

源码中可以看出,先是把密文 hashed 当作盐值,和明文 plaintext 做加密算法,算法函数是hashpw(plaintext, hashed);。返回的加密结果跟之前计算的加密结果做对比,对比函数是equalsNoEarlyReturn(String a, String b);。一样则 true 表示验证通过,否则验证不通过。

三、BCryptPasswordEncoder

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class Test {
    public static void main(String[] args) {

        String password = "123456";
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(6);
        String hashedPassword1 = passwordEncoder.encode(password);
        String hashedPassword2 = passwordEncoder.encode(password);
        String hashedPassword3 = passwordEncoder.encode(password);
        String hashedPassword4 = passwordEncoder.encode(password);
        System.out.println(hashedPassword1);
        System.out.println(hashedPassword2);
        System.out.println(hashedPassword3);
        System.out.println(hashedPassword4);

        boolean match1 = passwordEncoder.matches(password, hashedPassword1);
        System.out.println(match1);
        boolean match2 = passwordEncoder.matches(password, hashedPassword2);
        System.out.println(match2);
        boolean match3 = passwordEncoder.matches(password, hashedPassword3);
        System.out.println(match3);
        boolean match4 = passwordEncoder.matches(password, hashedPassword4);
        System.out.println(match4);
    }
}

结果如下:

$2a$06$.PnAp0jsfeTND2yBjdfFcuV0VoNQ6.E5hHDxWRrmGDuGxV3bj/zUS
$2a$06$Juq9dexQqveFiJhcPqZ/SeuEGB8/BtLMyCpYBNEfnm4F2HG/FFK9y
$2a$06$yCT1TPwWOpWr5.OrAdis5OOvbCOSmBuadibbxO7zajSChsAwFC8R6
$2a$06$cCMPkgZc7s2lB06fWG6SnO8nsZvBwXr0eZZFkLo16pLjk8A/XN196
true
true
true
true

登录注册还是用https安全。在客户端 base64 或者 md5 什么的真心没用,直接监听到所谓的密文,然后用脚本发起一个 http 请求就可以登录上去了。http 在网络上是明文传输的,代理和网关都能看到所有的数据,在同一局域网内也可以被嗅探到,可以开个 wireshark 抓下局域网的包试试看。我认为加密也没有提高什么攻击难度,因为攻击者就没必要去解密原始密码,能登录上去就表示目标已经实现了,所以难度没有提高。另外,在客户端md5后,服务端怎么把原始密码还原出来,不能数据库直接存 md5 吧?所以要选择加密算法的话,还要让服务端能还原出来原始密码。然后,如果是简单的 base64 下,这种算法对安全性没什么提高,base64 又不是加密算法,它的作用就是编码而已。其他的诸如非对称加密之类的算法当然可以,但是这不是https提供的功能么?而且 https 提供的安全保障还可以应对其他的攻击。

你可能感兴趣的:(encryption,java)