密码存储与传输的那些事儿(二)bcrypt密码哈希

      上一节讲到了密码哈希算法,首先我们从bcrypt入手,之前说到bcrypt算法不需要再生成盐,其实这么说是错误的,应该说bcrypt算法生成的结果自身就包含了盐,而且可以进行算法复杂度参数调整,从而加长了哈希算法执行时间,有效的防止了彩虹表攻击等手段。

       本次以Java为例,其他javascript、python等主流语言都有相应的库,相信并不难找,本次用的Java库如下:

 
      org.mindrot
      jbcrypt
      0.4
 

      代码示例如下所示:

String password = "123456";
        
String hashed = BCrypt.hashpw(password, BCrypt.gensalt(10));
if(BCrypt.checkpw(password, hashed)) {
     // 校验成功       
}

     首先让我们看一下生成的密码哈希结果

$2a$10$C5nirnEy0TgpB6UjtZebcOf7EanmJhFi2g0t8Vek3kZTEeer.BaR6

      上述的哈希结果准确来说并不全是哈希的结果,它是一种Shadow密码格式字符串,可以分为四个部分:类型、复杂度、盐和哈希结果。

1. 类型:$2a$表示bcrypt(UTF-8)类型,其余常见类型还包括:

  • $1$: MD5

  • $2$: Bcrypt

  • $sha1$: SHA-1

  • $5$: SHA-256

  • $6$: SHA-512

     其中$2$仅支持ASCII字符,而$2a$则规定密码需为UTF-8编码格式,所以目前Bcrypt主要为$2a$

2. 复杂度:$10$即表示Bcrypt算法的COST,总的来说COST越大计算越复杂,耗时越长,几乎是呈指数增长的。

3. 随机盐:Base64_encode(byte[16]),长度固定为22

4. 哈希结果:剩下的字符串才是真正的哈希结果


       有关bcrypt哈希的COST(log_rounds)值,其取值范围为[4, 30]。考虑到用户体验,登陆的时候算法时长最好不要超过300ms,为此个人认为10或11应为密码存储的最佳取值。

       此外, 对于登陆时的密码也可使用bcrypt进行哈希,增强安全性。为此,密码存储时可进行两次哈希,bcrypt(SALT + bcrypt(C-SALT + PASSWORD)),C-SALT可理解为服务器固定的盐,客户端使用bcrypt(C-SALT + PASSWORD)进行哈希后,将值传递给服务器,服务器再次调用bcypt和用户随机盐进行密码哈希,再和数据库中存储哈希密码结果进行比较即可。考虑到哈希算法时长的问题,本方法进行了两次密码哈希,为此COST取10,控制哈希时长不超过300ms,代码示例如下:

String C_SALT = "$2a$10$C5nirnEy0TgpB6UjtZebcO";
String password = "12345678";
// 客户端也通过同样方法利用服务器常量盐对密码进行哈希
String hashed = BCrypt.hashpw(password, C_SALT);
// 只取bcrypt哈希结果
hashed = hashed.substring(hashed.lastIndexOf('$') + 23);

String storeResult = BCrypt.hashpw(hashed, BCrypt.gensalt(10));
if(BCrypt.checkpw(hashed, storeResult)) {
    // 校验成功
}

 

你可能感兴趣的:(安全)