当前位置:   article > 正文

浅谈Bcrypt_qt bcrypt

qt bcrypt

bcrypt,是一个跨平台文件加密工具。由它加密的文件可在所有支持的操作系统和处理器上进行转移。它的口令必须是8至56个字符,并将在内部被转化为448位的密钥;bcrypt 使用的是布鲁斯·施内尔在1993年发布的 Blowfish 加密算法。具体来说,bcrypt 使用保罗·柯切尔的算法实现。随 bcrypt 一起发布的源代码对原始版本作了略微改动。

在Spring Secutity框架中会使用bcrypt算法对用户输入的密码进行加密,然后对比;由BCryptPasswordEncoder提供了相关的方法;

bcrypt有两个特点:1.每一次Hash出来的值不一样 2.计算非常缓慢

  1. import org.mindrot.jbcrypt.BCrypt;
  2. String pw = "abc123456";
  3. String hashpw = BCrypt.hashpw(pw, BCrypt.gensalt());
  4. System.out.println("第一:"+hashpw);
  5. String hashpw1 = BCrypt.hashpw(pw, BCrypt.gensalt());
  6. System.out.println("第二: "+hashpw1);
  7. String hashpw2 = BCrypt.hashpw(pw, BCrypt.gensalt());
  8. System.out.println("第三:"+hashpw2);
  9. 输出:
  10. 第一:$2a$10$8fVlbQI/sEueD58hWyATxOQEn8XUCXiU/gU6hFQMLoTsRNOpLInGC
  11. 第二: $2a$10$aCf7FxEvIkKm7UebXHHfqu9hBq5qrzMliBZy.uJWRcY1P1EDX6bse
  12. 第三:$2a$10$02UwB4ODFQLfwNbQtB3Fs.NecXUy4ssiUYKZzeP7ftW4tdn5FO4nK

看源码前先去看一下gensalt的底层实现::

  1. public static String gensalt(int log_rounds, SecureRandom random) {
  2. StringBuffer rs = new StringBuffer();
  3. byte[] rnd = new byte[16];
  4. random.nextBytes(rnd);
  5. rs.append("$2a$");
  6. if (log_rounds < 10) {
  7. rs.append("0");
  8. }
  9. if (log_rounds > 30) {
  10. throw new IllegalArgumentException("log_rounds exceeds maximum (30)");
  11. } else {
  12. rs.append(Integer.toString(log_rounds));
  13. rs.append("$");
  14. rs.append(encode_base64(rnd, rnd.length));
  15. return rs.toString();
  16. }
  17. }

这个底层组成三个部分,第一个固定($2a$),第二个其实也是固定的值为10,第三个使用了base64对刚才随机拿到的byte数组遍历进行转码,再来看看这个encode_base64方法:

  1. private static String encode_base64(byte[] d, int len) throws IllegalArgumentException {
  2. int off = 0;
  3. StringBuffer rs = new StringBuffer();
  4. if (len > 0 && len <= d.length) {
  5. while(off < len) {
  6. int c1 = d[off++] & 255;
  7. rs.append(base64_code[c1 >> 2 & 63]);
  8. c1 = (c1 & 3) << 4;
  9. if (off >= len) {
  10. rs.append(base64_code[c1 & 63]);
  11. break;
  12. }
  13. int c2 = d[off++] & 255;
  14. c1 |= c2 >> 4 & 15;
  15. rs.append(base64_code[c1 & 63]);
  16. c1 = (c2 & 15) << 2;
  17. if (off >= len) {
  18. rs.append(base64_code[c1 & 63]);
  19. break;
  20. }
  21. c2 = d[off++] & 255;
  22. c1 |= c2 >> 6 & 3;
  23. rs.append(base64_code[c1 & 63]);
  24. rs.append(base64_code[c2 & 63]);
  25. }
  26. return rs.toString();
  27. } else {
  28. throw new IllegalArgumentException("Invalid len");
  29. }
  30. }

这里出现了&,&即是运算符也是逻辑运算符,&的两侧可以是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的底层实现:

  1. public static String hashpw(String password, String salt) {
  2. char minor = 0;
  3. int off = false;
  4. StringBuffer rs = new StringBuffer();
  5. if (salt.charAt(0) == '$' && salt.charAt(1) == '2') {
  6. byte off;
  7. if (salt.charAt(2) == '$') {
  8. off = 3;
  9. } else {
  10. minor = salt.charAt(2);
  11. if (minor != 'a' || salt.charAt(3) != '$') {
  12. throw new IllegalArgumentException("Invalid salt revision");
  13. }
  14. off = 4;
  15. }
  16. if (salt.charAt(off + 2) > '$') {
  17. throw new IllegalArgumentException("Missing salt rounds");
  18. } else {
  19. int rounds = Integer.parseInt(salt.substring(off, off + 2));
  20. String real_salt = salt.substring(off + 3, off + 25);
  21. byte[] passwordb;
  22. try {
  23. passwordb = (password + (minor >= 'a' ? "\u0000" : "")).getBytes("UTF-8");
  24. } catch (UnsupportedEncodingException var12) {
  25. throw new AssertionError("UTF-8 is not supported");
  26. }
  27. byte[] saltb = decode_base64(real_salt, 16);
  28. BCrypt B = new BCrypt();
  29. byte[] hashed = B.crypt_raw(passwordb, saltb, rounds, (int[])((int[])bf_crypt_ciphertext.clone()));
  30. rs.append("$2");
  31. if (minor >= 'a') {
  32. rs.append(minor);
  33. }
  34. rs.append("$");
  35. if (rounds < 10) {
  36. rs.append("0");
  37. }
  38. if (rounds > 30) {
  39. throw new IllegalArgumentException("rounds exceeds maximum (30)");
  40. } else {
  41. rs.append(Integer.toString(rounds));
  42. rs.append("$");
  43. rs.append(encode_base64(saltb, saltb.length));
  44. rs.append(encode_base64(hashed, bf_crypt_ciphertext.length * 4 - 1));
  45. return rs.toString();
  46. }
  47. }
  48. } else {
  49. throw new IllegalArgumentException("Invalid salt version");
  50. }
  51. }
 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()这个方法内;

  1. public byte[] crypt_raw(byte[] password, byte[] salt, int log_rounds, int[] cdata) {
  2. int clen = cdata.length;
  3. if (log_rounds >= 4 && log_rounds <= 30) {
  4. int rounds = 1 << log_rounds;
  5. if (salt.length != 16) {
  6. throw new IllegalArgumentException("Bad salt length");
  7. } else {
  8. this.init_key();
  9. this.ekskey(salt, password);
  10. int i;
  11. for(i = 0; i != rounds; ++i) {
  12. this.key(password);
  13. this.key(salt);
  14. }
  15. int j;
  16. for(i = 0; i < 64; ++i) {
  17. for(j = 0; j < clen >> 1; ++j) {
  18. this.encipher(cdata, j << 1);
  19. }
  20. }
  21. byte[] ret = new byte[clen * 4];
  22. i = 0;
  23. for(j = 0; i < clen; ++i) {
  24. ret[j++] = (byte)(cdata[i] >> 24 & 255);
  25. ret[j++] = (byte)(cdata[i] >> 16 & 255);
  26. ret[j++] = (byte)(cdata[i] >> 8 & 255);
  27. ret[j++] = (byte)(cdata[i] & 255);
  28. }
  29. return ret;
  30. }
  31. } else {
  32. throw new IllegalArgumentException("Bad number of rounds");
  33. }
  34. }

这个过程比较繁琐,一些具体的循环运算,不做过多阐述,最终返回这个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);

总体来说,以上分析很拉垮,所以没有以下;

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/神奇cpp/article/detail/984420
推荐阅读
相关标签
  

闽ICP备14008679号