一、引入
Gin是一個(gè)用Go語(yǔ)言編寫的Web框架,而用戶密碼的加密通常是在應(yīng)用程序中處理用戶身份驗(yàn)證時(shí)的一個(gè)重要問題。
通常敏感信息你要防兩類人:
- 研發(fā)人員:包括你自己和你的同事。作為研發(fā)人員,你可能會(huì)接觸到公司的敏感信息,如用戶數(shù)據(jù)、商業(yè)機(jī)密等。
- 攻擊者:指那些有意獲取或破壞敏感信息的人。他們可能是黑客、競(jìng)爭(zhēng)對(duì)手、內(nèi)部威脅等。
所以為了增加安全性,密碼通常不應(yīng)以明文形式存儲(chǔ)在數(shù)據(jù)庫(kù)中,而是應(yīng)該經(jīng)過適當(dāng)?shù)募用芴幚怼?/p>
二、密碼加密位置
實(shí)際上,你選擇 service、repository、dao,包括 domain 都可以:
-
service加密:加密是一個(gè)業(yè)務(wù)概念,不是一個(gè)存儲(chǔ)概念。 -
repository加密:加密是一個(gè)存儲(chǔ)概念,畢竟我們說的是“加密存儲(chǔ)”。 -
dao加密:加密是一個(gè)數(shù)據(jù)庫(kù)概念,因?yàn)槲彝耆梢赃x擇利用數(shù)據(jù)庫(kù)本身的加密功能來實(shí)現(xiàn)。 -
domain加密:加密是一個(gè)業(yè)務(wù)概念,但是應(yīng)該是“用戶(User)”自己才知道怎么加密。

三、如何加密
加密算法的選擇會(huì)直接影響你整個(gè)系統(tǒng)的安全性,因?yàn)楣粽咭坏┠玫搅嗣艽a,差不多就可以為所欲為了。
選擇加密算法的標(biāo)準(zhǔn)就一個(gè),難破解。你要考慮以下問題:
- 相同的密碼,加密后的結(jié)果應(yīng)該不同。你可以預(yù)期,很多用戶習(xí)慣用
123456這種密碼,但是我們希望數(shù)據(jù)庫(kù)存儲(chǔ)的值還是不一樣。 - 難以通過碰撞、彩虹表來破解。
常見的加密算法無非就是下面這些,安全性逐步提高:
md5之類的哈希算法。在 1 的基礎(chǔ)上,引入了鹽值(
salt),或者進(jìn)行多次哈希等。PBKDF2、BCrypt這一類隨機(jī)鹽值的加密算法,同樣的文本加密后的結(jié)果都不同。
四、bcrypt 庫(kù)加密
4.1 介紹
在Go語(yǔ)言中,可以使用bcrypt庫(kù)來對(duì)密碼進(jìn)行安全加密,號(hào)稱最安全的加密算法。
4.2 優(yōu)點(diǎn):
不需要你自己去生成鹽值。
不需要額外存儲(chǔ)鹽值。
可以通過控制
cost來控制加密性能。同樣的文本,加密后的結(jié)果不同。
4.3 使用
首先,你需要在Go中安裝bcrypt庫(kù):
go get golang.org/x/crypto/bcrypt
下面是一個(gè)使用bcrypt庫(kù)在對(duì)用戶密碼進(jìn)行加密的示例:
package main
import (
"fmt"
"golang.org/x/crypto/bcrypt"
)
func main() {
// 用戶注冊(cè)時(shí)使用的密碼
password := "user_password"
// 使用bcrypt庫(kù)對(duì)密碼進(jìn)行哈希處理
hashedPassword, err := hashPassword(password)
if err != nil {
fmt.Println("Error hashing password:", err)
return
}
fmt.Println("Original Password:", password)
fmt.Println("Hashed Password:", hashedPassword)
// 模擬用戶登錄時(shí)的密碼驗(yàn)證
err = comparePasswords(hashedPassword, "wrong_password")
if err != nil {
fmt.Println("Password does not match:", err)
} else {
fmt.Println("Password matches!")
}
err = comparePasswords(hashedPassword, "user_password")
if err != nil {
fmt.Println("Password does not match:", err)
} else {
fmt.Println("Password matches!")
}
}
func hashPassword(password string) (string, error) {
// 使用bcrypt庫(kù)的GenerateFromPassword函數(shù)進(jìn)行哈希處理
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return "", err
}
return string(hashedPassword), nil
}
func comparePasswords(hashedPassword, inputPassword string) error {
// 使用bcrypt庫(kù)的CompareHashAndPassword函數(shù)比較密碼
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(inputPassword))
return err
}
加密后的結(jié)果如下:

五、小黃書密碼加密實(shí)踐
webook/internal/service/user.go:
func (svc *UserService) SignUp(ctx context.Context, u domain.User) error {
// 先加密密碼
hash, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
if err != nil {
return err
}
u.Password = string(hash)
// 然后存起來
return svc.repo.Create(ctx, u)
}
func (svc *UserService) Login(ctx context.Context, email, password string) (domain.User, error) {
// 先找用戶
u, err := svc.repo.FindByEmail(ctx, email)
if err == repository.ErrUserNotFound {
return domain.User{}, ErrInvalidUserOrPassword
}
if err != nil {
return domain.User{}, err
}
// 比較密碼了
err = bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))
if err != nil {
return domain.User{}, ErrInvalidUserOrPassword
}
return u, nil
}