Files
lab/go/testing/crypto/bcrypt_test.go
T

150 lines
2.9 KiB
Go

package crypto
import (
"errors"
"testing"
"time"
"golang.org/x/crypto/bcrypt"
)
var (
ErrInvalidPassword = errors.New("invalid password")
ErrInvalidHash = errors.New("invalid password hash")
)
type Hasher struct {
cost int // cost 可以理解成 bcrypt 的“计算难度”参数,cost 越高,计算 bcrypt hash 就越慢,攻击者暴力破解密码也越贵
}
func NewHasher(cost int) (*Hasher, error) {
if cost < bcrypt.MinCost || cost > bcrypt.MaxCost {
return nil, errors.New("invalid bcrypt cost")
}
return &Hasher{
cost: cost,
}, nil
}
// Hash 用于注册、重置密码、修改密码。
func (h *Hasher) Hash(password string) (string, error) {
if len(password) == 0 {
return "", ErrInvalidPassword
}
// bcrypt 只使用密码前 72 bytes。
// 生产环境建议显式拒绝,而不是静默截断。
if len([]byte(password)) > 72 {
return "", ErrInvalidPassword
}
hash, err := bcrypt.GenerateFromPassword(
[]byte(password),
h.cost,
)
if err != nil {
return "", err
}
return string(hash), nil
}
// Verify 用于登录时验证密码。
func (h *Hasher) Verify(password, encodedHash string) (bool, error) {
if len(password) == 0 || len([]byte(password)) > 72 {
return false, ErrInvalidPassword
}
if encodedHash == "" {
return false, ErrInvalidHash
}
err := bcrypt.CompareHashAndPassword(
[]byte(encodedHash),
[]byte(password),
)
switch {
case err == nil:
return true, nil
case errors.Is(err, bcrypt.ErrMismatchedHashAndPassword):
return false, nil
default:
// 例如数据库里的 hash 已损坏或格式不合法。
return false, ErrInvalidHash
}
}
// NeedsRehash 判断数据库中的 hash 是否使用了较旧的 cost。
func (h *Hasher) NeedsRehash(encodedHash string) bool {
cost, err := bcrypt.Cost([]byte(encodedHash))
if err != nil {
return true
}
return cost != h.cost
}
func TestBcrypt(t *testing.T) {
oldHasher, err := NewHasher(10)
if err != nil {
t.Fatal(err)
}
newHasher, err := NewHasher(12)
if err != nil {
t.Fatal(err)
}
password := "password"
oldHash, err := oldHasher.Hash(password)
if err != nil {
t.Fatal(err)
}
t.Log(oldHash)
newHash, err := newHasher.Hash(password)
if err != nil {
t.Fatal(err)
}
t.Log(newHash)
err = bcrypt.CompareHashAndPassword([]byte(oldHash), []byte(password))
if err != nil {
t.Fatal(err)
}
}
func TestHashPassword(t *testing.T) {
hasher, err := NewHasher(12)
if err != nil {
t.Fatal(err)
}
password := "password"
hash, err := hasher.Hash(password)
if err != nil {
t.Fatal(err)
}
t.Log(hash)
if ok, err := hasher.Verify(password, hash); err != nil || !ok {
t.Fatal("verify failed")
}
if ok := hasher.NeedsRehash(hash); !ok {
t.Fatal("needs rehash failed")
}
}
func TestGenerateTime(t *testing.T) {
start := time.Now()
_, err := bcrypt.GenerateFromPassword(
[]byte("TestPassword123!"),
14,
)
if err != nil {
t.Fatal(err)
}
t.Log(time.Since(start), err)
}