上一期的文章《我们应该如何保护用户的密码》里我们介绍了bcrypt
相较于MD5
,SHA-1
...SHA-256
等哈希算法更适合用于做密码的哈希,原因就是bcrypt
算法哈希字符串的速度远远慢于上面列举的那些算法。这样即使整个用户密码库被用户盗用后想要通过彩虹表和暴力破解的方法猜测出用户的密码代价会非常高昂。今天的文章里就主要来看一下bcrypt
哈希的组成部分以及在Go
语言里如何使用bcrypt
对密码字符串进行哈希。
bcrypt哈希字符串的组成
bcrypt
哈希由多个部分组成。这些部分用于确定创建哈希的设置,从而可以在不需要任何其他信息的情况下对其进行验证。
上图是一个bcrypt
哈希的示例图,其由四部分组成:
-
Prefix
说明了使用的bcrypt
的版本 -
Cost
是进行哈希的次数-数字越大生成bcrypt
的速度越慢,成本越大。同样也意味着如果密码库被盗,攻击者想通过暴力破解的方法猜测出用户密码的成本变得越昂贵。 -
Salt
是添加到要进行哈希的字符串中的随机字符(21.25个字符),所以使用bcrypt
时不需要我们在表里单独存储Salt
。 -
Hashed Text
是明文字符串最终被bcrypt
应用这些设置哈希后的哈希文本。
另外无论什么方法:每个密码加单独的盐进行哈希,使用bcrypt
进行哈希等等,如果用户使用非常简单的密码例如password
或123456
,还是能被猜测出来的,所以在用户设置密码时应该禁止他们输入简单的密码。
Go语言使用bcrypt
bcrypt
的原理和实现都非常复杂,不过常用的编程语言都有实现bcrypt
的包让我们直接使用,在Go
语言里是通过golang.org/x/crypto/bcrypt
包提供bcrypt
相关功能给开发者使用的。
接下来我们在http_demo
项目里演示一下使用bcrypt
做密码哈希和验证的方法,首先我们需要安装一下bcrypt
包
$ go get golang.org/x/crypto/bcrypt
bcrypt
包只提供了三个函数:
-
CompareHashAndPassword
用于比对bcrypt
哈希字符串和提供的密码明文文本是否匹配。 -
GenerateFromPassword
以给定的Cost
返回密码的bcrypt
哈希。如果给定的成本小于MinCost
,则将成本设置为DefaultCost
(10)。 -
Cost
返回用于创建给定bcrypt
哈希的哈希成本。将来密码系统为了应对更大的计算能力而增加哈希成本时,该功能可以用于确定哪些密码需要更新。
我们创建一个处理请求的Handler
程序,演示bcrypt
库三个函数的功能
// ./handler/password_hashing.go
package handler
import (
"fmt"
"golang.org/x/crypto/bcrypt"
"net/http"
)
func HashPassword(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
return string(bytes), err
}
func CheckPasswordHash(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}
func GetHashingCost(hashedPassword []byte) int {
cost, _ := bcrypt.Cost(hashedPassword) // 为了简单忽略错误处理
return cost
}
func PassWordHashingHandler(w http.ResponseWriter, r *http.Request) {
password := "secret"
hash, _ := HashPassword(password) // 为了简单忽略错误处理
fmt.Fprintln(w,"Password:", password)
fmt.Fprintln(w, "Hash: ", hash)
match := CheckPasswordHash(password, hash)
fmt.Fprintln(w,"Match: ", match)
cost := GetHashingCost([]byte(hash))
fmt.Fprintln(w,"Cost: ", cost)
}
增加Handler
程序的路由:
func RegisterRoutes(r *mux.Router) {
...
indexRouter := r.PathPrefix("/index").Subrouter()
indexRouter.HandleFunc("/password_hashing", handler.PassWordHashingHandler)
...
}
重启http_demo
服务器后访问http://localhost:8000/index/password_hashing
即可得到如下结果:
Password: secret
Hash: $2a$14$Ael8nW7UF/En/iI7LGdyBuaIO8VREbL2CAShRN0EUQHqtmOHXh.XK
Match: true
Cost: 14
本文源代码已经打包上传,公众号回复gohttp13
即可获得下载链接。如果觉得我的文章有收获,请帮忙点"在看"分享给更多人。
有疑问加站长微信联系(非本文作者)