很长一段时间我也是用 MD5 + 盐 来解决绝大多数密码的问题的,因为确实很方便。不过,从安全的角度来说,还是有风险,那就干脆直接上 Bcrypt 吧。
其实,在大多场景够用了,毕竟 hash 和 salt 同时被黑的概率太低了,不过其实 MD5 最大的问题不是到不是这个,而是算的太快了,随着计算能力的发展总会是有概率被破解的。
password_hash = md5(password+salt)
不多说,直接上代码,看怎么用,然后再分析。
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
}
# output
第一次加密后的密码: $2a$10$dFPckrZLstSKxX8zf3uUKurLw4Pes.G3APfrDIQfVHCFyGmUq4J7K
第二次加密后的密码: $2a$10$nYbAG/Om/bjEGq..x5TsVOy5VIVWudVaFxchrWLWPO5M7tMDIBDVO
密码比对结果: true
密码比对结果: false
golang.org/x/crypto/bcrypt
提供了 bcrypt 方法,所以使用起来非常简单的。
GenerateFromPassword
提供了加密(hash) 的方法,其中第二个参数是计算成本(工作因子),越大计算耗时越长 MaxCost 是 31CompareHashAndPassword
提供了验证的方法,用于验证用户输入的密码是否正确最让人安心的就是,它的每次 hash 结果都都是不一样的,原因就是每次的 salt 也是不一样的。我们知道,md5 使用相同的 字符串 前后两次 hash 是一样的,从而可以验证前后用的密码是不是一样的。那么,Bcrypt 每次的 hash 都不一样,如何它是如何做验证呢?
首先我们看看 hash 之后的结果
$2a$10$nYbAG/Om/bjEGq..x5TsVOy5VIVWudVaFxchrWLWPO5M7tMDIBDVO
\__/\/ \____________________/\_____________________________/
A C Salt Hash
其实看完了结构你就不难猜测到它的原理了,说白了验证的方式很简单,就是将 hash 后的结果中的 Salt 取出来,然后对用户输入的密码再次使用相同的方式和次数进行 hash,然后比较结果,看结果是否一致。也就是说,其实 Bcrypt 的 hash 结果并不仅仅只是包含了 hash 还包含了具体的 hash 计算方式和 Salt。
所以,Bcrypt 相比于 MD5 来说,我认为最关键的还是有了 cost 这个选项,并且本身的计算就比 MD5 的时间要长,大大的提高了破解的难度,而且由于 salt 的不固定,彩虹表是别想了。最后,还有一个关键点要提醒你:Bcrypt 的加密长度是有限制的,比如 golang 这里的库限制长度最大为 72,超过就会报错。