很长一段时间我也是用 MD5 + 盐 来解决绝大多数密码的问题的,因为确实很方便。不过,从安全的角度来说,还是有风险,那就干脆直接上 Bcrypt 吧。

MD5 + salt

其实,在大多场景够用了,毕竟 hash 和 salt 同时被黑的概率太低了,不过其实 MD5 最大的问题不是到不是这个,而是算的太快了,随着计算能力的发展总会是有概率被破解的。

1
password_hash = md5(password+salt)

Bcrypt 的特点

  • hash 不可逆
  • 随机 salt
  • 可调整的计算 cost

上代码

不多说,直接上代码,看怎么用,然后再分析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
"fmt"

"golang.org/x/crypto/bcrypt"
)

func main() {
password := "123456"
fmt.Printf("第一次加密后的密码: %s\n", encryptPassword(password))
fmt.Printf("第二次加密后的密码: %s\n", encryptPassword(password))

fmt.Printf("密码比对结果: %v\n", comparePassword(password, encryptPassword(password)))
fmt.Printf("密码比对结果: %v\n", comparePassword("123", encryptPassword(password)))
}

func encryptPassword(password string) string {
hashPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
panic(err)
}
return string(hashPassword)
}

func comparePassword(password, hashPassword string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hashPassword), []byte(password))
return err == nil
}

1
2
3
4
5
6
# output

第一次加密后的密码: $2a$10$dFPckrZLstSKxX8zf3uUKurLw4Pes.G3APfrDIQfVHCFyGmUq4J7K
第二次加密后的密码: $2a$10$nYbAG/Om/bjEGq..x5TsVOy5VIVWudVaFxchrWLWPO5M7tMDIBDVO
密码比对结果: true
密码比对结果: false

golang.org/x/crypto/bcrypt 提供了 bcrypt 方法,所以使用起来非常简单的。

  • GenerateFromPassword 提供了加密(hash) 的方法,其中第二个参数是计算成本(工作因子),越大计算耗时越长 MaxCost 是 31
  • CompareHashAndPassword 提供了验证的方法,用于验证用户输入的密码是否正确

最让人安心的就是,它的每次 hash 结果都都是不一样的,原因就是每次的 salt 也是不一样的。我们知道,md5 使用相同的 字符串 前后两次 hash 是一样的,从而可以验证前后用的密码是不是一样的。那么,Bcrypt 每次的 hash 都不一样,如何它是如何做验证呢?

原理一瞥

hash 结构

首先我们看看 hash 之后的结果

1
2
3
$2a$10$nYbAG/Om/bjEGq..x5TsVOy5VIVWudVaFxchrWLWPO5M7tMDIBDVO
\__/\/ \____________________/\_____________________________/
A C Salt Hash
  • A:表示 hash 的方式,2a 代表 Bcrypt 加密版本号
  • C:表示迭代次方数
  • Salt:是盐
  • Hash:是最后的值

分析

其实看完了结构你就不难猜测到它的原理了,说白了验证的方式很简单,就是将 hash 后的结果中的 Salt 取出来,然后对用户输入的密码再次使用相同的方式和次数进行 hash,然后比较结果,看结果是否一致。也就是说,其实 Bcrypt 的 hash 结果并不仅仅只是包含了 hash 还包含了具体的 hash 计算方式和 Salt。

总结

所以,Bcrypt 相比于 MD5 来说,我认为最关键的还是有了 cost 这个选项,并且本身的计算就比 MD5 的时间要长,大大的提高了破解的难度,而且由于 salt 的不固定,彩虹表是别想了。最后,还有一个关键点要提醒你:Bcrypt 的加密长度是有限制的,比如 golang 这里的库限制长度最大为 72,超过就会报错。