这是我参与「第三届青训营 -后端场」笔记创作活动的第4篇笔记
本文描述了在项目中使用的密码加盐和加密操作,以及token的生成方式。
-
对接收到的用户密码进行加密操作再存入数据库而不是明文存入,减少了用户密码泄漏风险。
-
使用jwt-go生成token并设置token的有效期,使用token验证用户。
密码加密
- 本项目初期使用sha256散列加密,不加盐,接收到密码之后进行加密存入数据库,对登录用户的验证采用接收密码使用sha256散列,与数据库所存密码对比,符合则视为密码正确。但是,发现使用解密工具能很容易的破解密码,例如在线sha解密网站www.ttmd5.com/hash.php?ty…
- 进行调研之后发现pbkdf2加密算法可以一定程度上减缓暴力破解的速度。
这里是初期使用的加密版本,可以看出仅仅接收了passwd进行sha256散列,并转化为十六进制使用string保存在数据库中
func Encryption(passwd string) string {
h := sha256.New()
h.Write([]byte(passwd))
return fmt.Sprintf("%x", h.Sum(nil))
}
这是第二版使用pbkdf2进行加密,接收密码,盐以及迭代次数,在本程序中,salt = username,iter = 10
func Encryption_PBKDF2(passwd, salt string, iter int) string {
return fmt.Sprintf("%x", pbkdf2.Key([]byte(passwd), []byte(salt), iter, 32, sha256.New))
}
修改之后发现一个问题,像这样中途更改加密算法,之前的用户岂不是不能再次登录了?
所以在这里在user里边添加了一个Encryption字段和迭代次数Iter,用于标记加密手段,对于之前的用户来讲,这个字段是null,对于之后注册的用户来讲,这个字段应该填入"pbkdf2"。
这里也可以新建立一个表,用于存储用户密码的加密手段。
在用户登录界面增加了一个switch判断,如果Encrption字段为空,就使用default,也就是普通不加盐的Encryption算法,如果Encrption == "pbkdf2",那就使用PBKDF2算法。
var password string
switch user.Encryption {
case "pbkdf2":
password = service.Encryption_PBKDF2(c.Query("password"), username, user.Iter)
default:
password = service.Encryption(c.Query("password"))
}
其中Iter参数可以根据当前网络环境和硬件的更新,进行更新,增加或减少次数,由于同样存入数据库中,所以之后的更新不影响之前的参数。
测试
对两种算法的速度进行测试,两种算法的速度差距还是挺大的,在iter=10的时候能够达到十二倍的差距,但是iter=1时候还是有接近五倍的差距。
iter = 1
BenchmarkEncruption-4 2490416 481.6 ns/op
BenchmarkEncruptionPBKDF2-4 662444 1876 ns/op
iter = 10
BenchmarkEncruption-4 2576559 465.2 ns/op
BenchmarkEncruptionPBKDF2-4 189534 5645 ns/op
iter = 20
BenchmarkEncruption-4 2614942 466.3 ns/op
BenchmarkEncruptionPBKDF2-4 116610 9871 ns/op
iter = 100
BenchmarkEncruption-4 2566798 465.6 ns/op
BenchmarkEncruptionPBKDF2-4 26781 44085 ns/op
Token生成
本项目使用用户名和ID生成了token,token不保存,设置token的过期时间是七天,每次接收到token都会进行有效性验证,并解析出对应得用户信息。
package service
import (
"time"
"github.com/dgrijalva/jwt-go"
)
var jwtKey = []byte("a_secret_crect")
type Claims struct {
Username string
Id int64
jwt.StandardClaims
}
//根据用户名和Id生成token
func GetToken(username string, user_id int64) (token string, err error) {
token, err = ReleaseToken(username, user_id)
return
}
//验证token的有效性
func TokenValidity(tokenString string) (int64, bool) {
token, claims, err := ParseToken(tokenString)
if err == nil && token.Valid {
return claims.Id, true
} else {
return 0, false
}
}
//token分发
func ReleaseToken(username string, user_id int64) (string, error) {
expirationTime := time.Now().Add(7 * 24 * time.Hour) //token截至有效时间
claims := &Claims{
Username: username,
Id: user_id,
StandardClaims: jwt.StandardClaims{
ExpiresAt: expirationTime.Unix(),
IssuedAt: time.Now().Unix(), //发放的时间
Issuer: "DouShen", //谁发放的
Subject: "user token",
},
}
//token分三部分,加密协议.储存的信息.前面两部分加上key再哈希得到的值
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString(jwtKey)
if err != nil {
return "", err
}
return tokenString, nil
}
//token解析
func ParseToken(tokenString string) (*jwt.Token, *Claims, error) {
claims := &Claims{}
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (i interface{}, err error) {
return jwtKey, nil
})
return token, claims, err
}