赞
踩
bcrypt,是一个跨平台的文件加密工具。由它加密的文件可在所有支持的操作系统和处理器上进行转移。它的口令必须是8至56个字符,并将在内部被转化为448位的密钥;bcrypt 使用的是布鲁斯·施内尔在1993年发布的 Blowfish 加密算法。具体来说,bcrypt 使用保罗·柯切尔的算法实现。随 bcrypt 一起发布的源代码对原始版本作了略微改动。
在Spring Secutity框架中会使用bcrypt算法对用户输入的密码进行加密,然后对比;由BCryptPasswordEncoder提供了相关的方法;
bcrypt有两个特点:1.每一次Hash出来的值不一样 2.计算非常缓慢
- import org.mindrot.jbcrypt.BCrypt;
-
-
- String pw = "abc123456";
- String hashpw = BCrypt.hashpw(pw, BCrypt.gensalt());
- System.out.println("第一:"+hashpw);
- String hashpw1 = BCrypt.hashpw(pw, BCrypt.gensalt());
- System.out.println("第二: "+hashpw1);
- String hashpw2 = BCrypt.hashpw(pw, BCrypt.gensalt());
- System.out.println("第三:"+hashpw2);
-
-
- 输出:
- 第一:$2a$10$8fVlbQI/sEueD58hWyATxOQEn8XUCXiU/gU6hFQMLoTsRNOpLInGC
- 第二: $2a$10$aCf7FxEvIkKm7UebXHHfqu9hBq5qrzMliBZy.uJWRcY1P1EDX6bse
- 第三:$2a$10$02UwB4ODFQLfwNbQtB3Fs.NecXUy4ssiUYKZzeP7ftW4tdn5FO4nK

看源码前先去看一下gensalt的底层实现::
- public static String gensalt(int log_rounds, SecureRandom random) {
- StringBuffer rs = new StringBuffer();
- byte[] rnd = new byte[16];
- random.nextBytes(rnd);
- rs.append("$2a$");
- if (log_rounds < 10) {
- rs.append("0");
- }
-
- if (log_rounds > 30) {
- throw new IllegalArgumentException("log_rounds exceeds maximum (30)");
- } else {
- rs.append(Integer.toString(log_rounds));
- rs.append("$");
- rs.append(encode_base64(rnd, rnd.length));
- return rs.toString();
- }
- }

这个底层组成三个部分,第一个固定($2a$),第二个其实也是固定的值为10,第三个使用了base64对刚才随机拿到的byte数组遍历进行转码,再来看看这个encode_base64方法:
- private static String encode_base64(byte[] d, int len) throws IllegalArgumentException {
- int off = 0;
- StringBuffer rs = new StringBuffer();
- if (len > 0 && len <= d.length) {
- while(off < len) {
- int c1 = d[off++] & 255;
- rs.append(base64_code[c1 >> 2 & 63]);
- c1 = (c1 & 3) << 4;
- if (off >= len) {
- rs.append(base64_code[c1 & 63]);
- break;
- }
-
- int c2 = d[off++] & 255;
- c1 |= c2 >> 4 & 15;
- rs.append(base64_code[c1 & 63]);
- c1 = (c2 & 15) << 2;
- if (off >= len) {
- rs.append(base64_code[c1 & 63]);
- break;
- }
-
- c2 = d[off++] & 255;
- c1 |= c2 >> 6 & 3;
- rs.append(base64_code[c1 & 63]);
- rs.append(base64_code[c2 & 63]);
- }
-
- return rs.toString();
- } else {
- throw new IllegalArgumentException("Invalid len");
- }
- }

这里出现了&,&即是运算符也是逻辑运算符,&的两侧可以是int也可以是boolean表达式,当&两侧是int时,先要把运算符两侧的数字转为二进制在进行运算; 这里遍历第一个byte然后和固定的255进行运算,然后出现>>符号,相当于num/2n,算数右移,(这里的<<和>>自行百度,不做过多的阐述)在和固定的63进行运算,得出的数对应base64_code中的char字符,添加进StringBuffer中,然后再次进入运算c1,其目的是判断当前数组是否结束,然后再拿下一个索引的值进行相关的计算,并添加进StringBuffer中,每次循环完索引数组,off++,第一遍循环完off是等于3的,而添加进StringBuffer中是四次,注意末尾端的c1和c2添加;
private static final char[] base64_code = new char[]{'.', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
继续往下走 看一下hashpw的底层实现:
- public static String hashpw(String password, String salt) {
- char minor = 0;
- int off = false;
- StringBuffer rs = new StringBuffer();
- if (salt.charAt(0) == '$' && salt.charAt(1) == '2') {
- byte off;
- 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 (salt.charAt(off + 2) > '$') {
- throw new IllegalArgumentException("Missing salt rounds");
- } else {
- int rounds = Integer.parseInt(salt.substring(off, off + 2));
- String real_salt = salt.substring(off + 3, off + 25);
-
- byte[] passwordb;
- try {
- passwordb = (password + (minor >= 'a' ? "\u0000" : "")).getBytes("UTF-8");
- } catch (UnsupportedEncodingException var12) {
- throw new AssertionError("UTF-8 is not supported");
- }
-
- byte[] saltb = decode_base64(real_salt, 16);
- BCrypt B = new BCrypt();
- byte[] hashed = B.crypt_raw(passwordb, saltb, rounds, (int[])((int[])bf_crypt_ciphertext.clone()));
- rs.append("$2");
- if (minor >= 'a') {
- rs.append(minor);
- }
-
- rs.append("$");
- if (rounds < 10) {
- rs.append("0");
- }
-
- if (rounds > 30) {
- throw new IllegalArgumentException("rounds exceeds maximum (30)");
- } else {
- rs.append(Integer.toString(rounds));
- rs.append("$");
- rs.append(encode_base64(saltb, saltb.length));
- rs.append(encode_base64(hashed, bf_crypt_ciphertext.length * 4 - 1));
- return rs.toString();
- }
- }
- } else {
- throw new IllegalArgumentException("Invalid salt version");
- }
- }

private static final int[] bf_crypt_ciphertext = new int[]{1332899944, 1700884034, 1701343084, 1684370003, 1668446532, 1869963892};
进入会先判断相对应的salt是否符合,不符合报错,然后在进行判断salt的第二位,拿到第二位,赋值给上面的minor,并且判断是否符合,如果不符合报错,符合对off赋值为4,然后用off的值进行相加判断该位,不符合报错,符合的话进行位置的提取,分盐轮和真盐。然后将密码字节化,组成新的passwordb,然后将刚才的passwordb和拿到的盐的16个byte数组等进入crypt_raw()这个方法内;
- public byte[] crypt_raw(byte[] password, byte[] salt, int log_rounds, int[] cdata) {
- int clen = cdata.length;
- if (log_rounds >= 4 && log_rounds <= 30) {
- int rounds = 1 << log_rounds;
- if (salt.length != 16) {
- throw new IllegalArgumentException("Bad salt length");
- } else {
- this.init_key();
- this.ekskey(salt, password);
-
- int i;
- for(i = 0; i != rounds; ++i) {
- this.key(password);
- this.key(salt);
- }
-
- int j;
- for(i = 0; i < 64; ++i) {
- for(j = 0; j < clen >> 1; ++j) {
- this.encipher(cdata, j << 1);
- }
- }
-
- byte[] ret = new byte[clen * 4];
- i = 0;
-
- for(j = 0; i < clen; ++i) {
- ret[j++] = (byte)(cdata[i] >> 24 & 255);
- ret[j++] = (byte)(cdata[i] >> 16 & 255);
- ret[j++] = (byte)(cdata[i] >> 8 & 255);
- ret[j++] = (byte)(cdata[i] & 255);
- }
-
- return ret;
- }
- } else {
- throw new IllegalArgumentException("Bad number of rounds");
- }
- }

这个过程比较繁琐,一些具体的循环运算,不做过多阐述,最终返回这个ret;再最后把这个返回的字节数组通过encode_base64这个方法得出后的结果存入StringBuffer中返回给用户;
即使黑客得到了bcrypt密码,他也无法转换明文,因为bcrypt是单向hash算法
;
有文章指出bcrypt一个密码出来的时间比较长,需要0.3秒,而MD5只需要一微秒(百万分之一秒),一个40秒可以穷举得到明文的MD5,在bcrypt需要12年,时间成本太高。
在下次校验时,从myHash中取出salt,salt跟password进行hash;得到的结果跟保存在DB中的hash进行比对,如Spring Security框架中实现的Bcrypt密码验证Bcrypet。checkpw(candidatePassword,dpPassword);
总体来说,以上分析很拉垮,所以没有以下;
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。