golang 使用 bcrypt 实现密码加密
这是我参与「第三届青训营 -后端场」笔记创作活动的第1篇笔记
01 简介
在完成许多的项目当中,注册和登录这两个功能几乎是必不可少的。
将用户的密码加密后再存入数据库的操作是必要的。我们通常对密码进行加密,然后存放在数据库中,在用户进行登录的时候,将其输入的密码与数据库中存放的密文进行比较,以验证用户密码是否正确。目前,MD5和Bcrypt比较流行。
- BCrypt加密: 一种加盐的单向Hash,不可逆的加密算法,同一种明文(plaintext),每次加密后的密文都不一样,而且不可反向破解生成明文,破解难度很大。
- MD5加密: 是不加盐的单向Hash,不可逆的加密算法,同一个密码经过hash的时候生成的是同一个hash值,在大多数的情况下,有些经过md5加密的方法将会被破解。
Bcrypt生成的密文是60位的。而MD5的是32位的。相对来说,Bcrypt比MD5更安全,但加密更慢。
02 实现
其中使用的数据库、框架:
- mysql
- gorm
- gin
- bcrypt
主要分三步:
1、go get 一下这个算法的包
go get golang.org/x/crypto/bcrypt
2、生成、比对
实现GetPwd()
函数给密码加密、ComparePwd()
函数比对密码。
func GetPwd(pwd string) ([]byte, error)
func ComparePwd(pwd1 string, pwd2 string) bool
主要使用了这两个函数: bcrypt.GenerateFromPassword() 和 bcrypt.CompareHashAndPassword()
注意:分清 pwd1 、 pwd2分别代表什么,pwd1 是数据库的已经加密的密码, pwd2 是用户实际的密码。
3、在 controller/user.go
实现登录、注册逻辑
在这次实现极简版抖音为例,主要有以下代码实现:
controller/user.go
package controller
import (
"douyin/dao"
"douyin/model"
"fmt"
"github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin"
"golang.org/x/crypto/bcrypt"
"log"
"net/http"
)
type UserLoginResponse struct {
model.Response
UserId int64 `json:"user_id,omitempty"`
Token string `json:"token"`
}
type UserResponse struct {
model.Response
User model.Userinfo `json:"user"`
}
func Register(c *gin.Context) {
username := c.Query("username")
password := c.Query("password")
token, _ := GenToken(username, password)
// 查找用户是否存在
user, err := dao.Mgr.IsExist(username)
if err != nil {
log.Println(err)
}
if user.Name != "" {
fmt.Println("已存在!")
c.JSON(http.StatusOK, UserLoginResponse{
Response: model.Response{StatusCode: 1, StatusMsg: "User already exist"},
})
return
}
// encrypted : 已加密的密码
encrypted, _ := GetPwd(password)
userinfo := model.Userinfo{
Name: username,
Password: string(encrypted),
}
// 将加密的密码写入数据库
err = dao.Mgr.Register(userinfo)
if err != nil {
log.Println(err)
}
c.JSON(http.StatusOK, UserLoginResponse{
Response: model.Response{StatusCode: 0},
UserId: userinfo.Id,
Token: token,
})
}
func Login(c *gin.Context) {
username := c.Query("username")
password := c.Query("password")
token, _ := GenToken(username, password)
// 查找用户是否存在
user, err := dao.Mgr.IsExist(username)
if err != nil {
log.Println(err)
}
if user.Name == "" {
fmt.Println("用户不存在!")
c.JSON(http.StatusOK, UserLoginResponse{
Response: model.Response{StatusCode: 1, StatusMsg: "User doesn't exist"},
})
return
}
if ComparePwd(user.Password, password) {
fmt.Println("登陆成功!")
c.JSON(http.StatusOK, UserLoginResponse{
Response: model.Response{StatusCode: 0},
UserId: user.Id,
Token: token,
})
} else {
c.JSON(http.StatusOK, UserLoginResponse{
Response: model.Response{StatusCode: 1, StatusMsg: "Password is not correct"},
})
}
}
// GetPwd 给密码加密
func GetPwd(pwd string) ([]byte, error) {
hash, err := bcrypt.GenerateFromPassword([]byte(pwd), bcrypt.DefaultCost)
return hash, err
}
// ComparePwd 比对密码
func ComparePwd(pwd1 string, pwd2 string) bool {
// Returns true on success, pwd1 is for the database.
err := bcrypt.CompareHashAndPassword([]byte(pwd1), []byte(pwd2))
if err != nil {
return false
} else {
return true
}
}
dao/db_init.go
该文件的功能是:做数据库连接、操作数据库函数的声明。
package dao
import (
"douyin/model"
"douyin/pkg/constrant"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/schema"
"log"
)
type manager struct {
db *gorm.DB
}
type Manager interface {
// Register 注册
Register(user model.Userinfo) error
// IsExist 判断是否用户是否已存在
IsExist(username string) (model.Userinfo, error)
}
var Mgr Manager
func init() {
db, err := gorm.Open(mysql.Open(root:xxxxxx@tcp(xxx.xx.xxx.xxx)/douyin?charset=utf8&parseTime=True&loc=Local), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
TablePrefix: "t_",
SingularTable: true,
},
})
if err != nil {
log.Fatal("Failed to init db:", err)
}
Mgr = &manager{db: db}
}
dao/user.go
实际操作数据库的函数:
package dao
import (
"douyin/model"
)
func (mgr manager) Register(userinfo model.Userinfo) error {
result := mgr.db.Create(&userinfo)
return result.Error
}
func (mgr manager) IsExist(username string) (model.Userinfo, error) {
var userinfo model.Userinfo
result := mgr.db.Model(&model.Userinfo{}).Where("name=?", username).Find(&userinfo)
return userinfo, result.Error
}
03 总结
目前,MD5 和 Bcrypt 比较流行。相对来说,Bcrypt 比 MD5 更安全,但加密更慢。
实现 bcrypt 加密分三个步骤:
1、go get 一下这个算法的包
go get golang.org/x/crypto/bcrypt
2、生成、比对
实现GetPwd()
函数给密码加密、ComparePwd()
函数比对密码。
func GetPwd(pwd string) ([]byte, error)
func ComparePwd(pwd1 string, pwd2 string) bool
3、在 controller
实现登录、注册逻辑