bcrypt加密算法原理和應用
對於同一個密碼,每次生成的hash不一樣,但是hash中包含了salt(hash產生過程:先隨機生成salt,salt跟password進行hash);
在下次校驗時,從hash中取出salt,salt跟password進行hash;得到的結果跟保存在DB中的hash進行比對
簡單使用
package main
import (
"fmt"
"log"
"golang.org/x/crypto/bcrypt"
)
func main() {
hash := hashAndSalt([]byte("123"))
fmt.Println("hash過的密碼", hash)
pwdMatch := comparePasswords(hash, []byte("123"))
if pwdMatch {
fmt.Println("驗證密碼成功")
}
}
// 加密密碼
func hashAndSalt(pwd []byte) string {
hash, err := bcrypt.GenerateFromPassword(pwd, bcrypt.MinCost)
if err != nil {
log.Println(err)
}
return string(hash)
}
// 驗證密碼
func comparePasswords(hashedPwd string, plainPwd []byte) bool {
byteHash := []byte(hashedPwd)
err := bcrypt.CompareHashAndPassword(byteHash, plainPwd)
if err != nil {
log.Println(err)
return false
}
return true
}
運行之后結果
D:\go_test>go run hash.go
hash過的密碼 $2a$04$VQe0TCnCRWfB4O51Qflpses5l0udTyq74V3dNfibc1AT1JJck4S8u
驗證密碼成功
一起實現一個demo
對於這個例子,我將創建一個控制台應用程序,用於演示如何獲取用戶輸入的密碼並使用它生成 salt 哈希值。 完成此操作后,我將通過比較密碼與其散列版本來驗證密碼是否正確。
獲取用戶輸入的密碼
開始我們先創建一個可以在控制台讀取用戶輸入的的方法。
func getPwd() []byte {
fmt.Println("Enter a password")
var pwd string
// 讀取用戶輸入
_, err := fmt.Scan(&pwd)
if err != nil {
log.Println(err)
}
return []byte(pwd)
}
Hash & Salt 用戶的密碼
現在我們可以使用 Go 的 bcrypt 包提供的GenerateFromPassword(password []byte, cost int)([]byte, error)方法對用戶的密碼進行 hash 和 salt 加密了。
GenerateFromPassword 方法以給定 cost 值返回密碼的 Bcrypt 算法的 Hash 值,如果提供的 cost 值小於 Mincost 的話,將會默認使用 DefaultCost 代替使用GenerateFromPassword函數的一個優勢就是我們不需要自己來編寫函數來生成 Salt,因為它會為我們自動生成一個 Salt。
下面的函數使用GenerateFromPassword生成 salted 哈希值,該哈希值作為字節切片返回。 然后我們將字節切片作為字符串返回,以便我們可以將 salted 哈希存儲在數據庫中作為用戶密碼。
func hashAndSalt(pwd []byte) string {
hash, err := bcrypt.GenerateFromPassword(pwd, bcrypt.MinCost)
if err != nil {
log.Println(err)
}
return string(hash)
}
目前我們做了什么
到目前為止,我們已經創建了一個接受來自控制台的用戶輸入並將其作為字節切片返回的函數。 然后,
我們再創建一個可以接收用戶輸入並返回 salted 哈希值的函數。下面就是代碼事例。
package main
import (
"fmt"
"log"
"golang.org/x/crypto/bcrypt"
)
func main() {
for {
pwd := getPwd()
hash := hashAndSalt(pwd)
fmt.Println("Salted Hash", pwd)
}
}
如果你運行上面的代碼,將會得到下面的結果
> $ Enter a password
> $ foobar
> Salted Hash $2a$10$...........
這里需要的注意的是我使用 for 循環調用函數,直到我強制停止它。對於那些不熟悉 GO 的人來講,這個就和其他語言的while (true){}是一樣的效果。
驗證密碼
最后一件事兒就是需要驗證密碼的正確性來登陸我們的系統,我們可以使用bcrypt 包提供的CompareHashAndPassword(hashedPassword, password []byte) error函數
CompareHashAndPassword 將 bcrypt 哈希密碼與其純文本進行比較。 成功時返回 nil,失敗時返回錯誤 我們使用
CompareHashAndPassword函數來創建另一個返回 bool 值的函數讓我們知道密碼是否匹配。
func comparePasswords(hashedPwd string, plainPwd []byte) bool {
byteHash := []byte(hashedPwd)
err := bcrypt.CompareHashAndPassword(byteHash, plainPwd)\
if err != nil {
log.Println(err)
return false
}
return true
}
更新 Main 函數
我們現在可以更新我們的主要功能,以便我們能夠輸入密碼,獲取其鹽漬哈希,然后再次輸入密碼,並查明我們的第二個密碼是否與我們輸入的第一個密碼相匹配。
我們現在修改一個 main 函數,當我們輸入密碼的時候,獲取 salted 哈希值,然后再次輸入密碼,來檢查我們的密碼是否匹配。
func main() {
for {
pwd := getPwd()
hash := hashAndSalt(pwd)
pwd2 := getPwd()
pwdMatch := comparePasswords(hash, pwd2)
fmt.Println("Passwords Match?", pwd)
}
}
全部代碼
package main
import (
"fmt"
"log"
"golang.org/x/crypto/bcrypt"
)
func main() {
for {
// 輸入密碼 獲取 hash 值
pwd := getPwd()
hash := hashAndSalt(pwd)
// 再次輸入密碼驗證
pwd2 := getPwd()
pwdMatch := comparePasswords(hash, pwd2)
fmt.Println("Passwords Match?", pwd)
}
}
func getPwd() []byte {
fmt.Println("Enter a password")
var pwd string
_, err := fmt.Scan(&pwd)
if err != nil {
log.Println(err)
}
return []byte(pwd)
}
func hashAndSalt(pwd []byte) string {
hash, err := bcrypt.GenerateFromPassword(pwd, bcrypt.MinCost)
if err != nil {
log.Println(err)
}
return string(hash)
}
func comparePasswords(hashedPwd string, plainPwd []byte) bool {
byteHash := []byte(hashedPwd)
err := bcrypt.CompareHashAndPassword(byteHash, plainPwd)
if err != nil {
log.Println(err)
return false
}
return true
}
