refactor(go)!: 分离练习区与独立项目区
- 将现有练习及 module 文件迁移至 go/practice,保持代码和依赖不变 - 新增 go/projects 项目区说明,更新目录约定与开发命令 BREAKING CHANGE: Go 练习命令需改在 go/practice 目录下执行
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user