golang 使用 bcrypt 实现密码加密 | 青训营笔记

4,278 阅读3分钟

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 实现登录、注册逻辑