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) }