package auth import ( "crypto/rand" "crypto/subtle" "encoding/hex" "errors" "fmt" "strconv" "strings" "golang.org/x/crypto/argon2" "golang.org/x/crypto/bcrypt" ) var defaultPasswordManager = NewPassword() // Password 密码管理器(Argon2id) type Password struct { memory uint32 iterations uint32 parallelism uint8 saltLength uint32 keyLength uint32 } // NewPassword 创建密码管理器 func NewPassword() *Password { return &Password{ memory: 64 * 1024, // 64MB(符合 OWASP 建议) iterations: 5, // 5 次迭代(保守值,高于 OWASP 建议的 3) parallelism: 4, // 4 并行(符合 OWASP 建议,防御 GPU 破解) saltLength: 16, // 16 字节盐(符合 OWASP 最低要求) keyLength: 32, // 32 字节密钥 } } // Hash 哈希密码(使用Argon2id + 随机盐) func (p *Password) Hash(password string) (string, error) { // 使用 crypto/rand 生成真正随机的盐 salt := make([]byte, p.saltLength) if _, err := rand.Read(salt); err != nil { return "", fmt.Errorf("生成随机盐失败: %w", err) } // 使用Argon2id哈希密码 hash := argon2.IDKey( []byte(password), salt, p.iterations, p.memory, p.parallelism, p.keyLength, ) // 格式: $argon2id$v=$m=,t=,p=$$ encoded := fmt.Sprintf("$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s", argon2.Version, p.memory, p.iterations, p.parallelism, hex.EncodeToString(salt), hex.EncodeToString(hash), ) return encoded, nil } // Verify 验证密码 func (p *Password) Verify(hashedPassword, password string) bool { // 支持 bcrypt 格式(兼容旧数据) if strings.HasPrefix(hashedPassword, "$2a$") || strings.HasPrefix(hashedPassword, "$2b$") { err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) return err == nil } // 解析 Argon2id 格式 parts := strings.Split(hashedPassword, "$") // 格式: ["", "argon2id", "v=", "m=,t=,p=", "", ""] if len(parts) != 6 || parts[1] != "argon2id" { return false } // 解析参数 var memory, iterations uint32 var parallelism uint8 params := strings.Split(parts[3], ",") if len(params) != 3 { return false } for _, param := range params { kv := strings.SplitN(param, "=", 2) if len(kv) != 2 { return false } val, err := strconv.ParseUint(kv[1], 10, 64) if err != nil { return false } switch kv[0] { case "m": memory = uint32(val) case "t": iterations = uint32(val) case "p": parallelism = uint8(val) } } // 解码盐和存储的哈希 salt, err := hex.DecodeString(parts[4]) if err != nil { return false } storedHash, err := hex.DecodeString(parts[5]) if err != nil { return false } // 用相同参数重新计算哈希 computedHash := argon2.IDKey( []byte(password), salt, iterations, memory, parallelism, uint32(len(storedHash)), ) // 常数时间比较,防止时序攻击 return subtle.ConstantTimeCompare(storedHash, computedHash) == 1 } // HashPassword hashes passwords with Argon2id for new credentials. func HashPassword(password string) (string, error) { return defaultPasswordManager.Hash(password) } // VerifyPassword verifies both Argon2id and legacy bcrypt password hashes. func VerifyPassword(hashedPassword, password string) bool { return defaultPasswordManager.Verify(hashedPassword, password) } // ErrInvalidPassword 密码无效错误 var ErrInvalidPassword = errors.New("密码无效") // BcryptHash 使用bcrypt哈希密码(兼容性支持) func BcryptHash(password string) (string, error) { hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { return "", fmt.Errorf("bcrypt加密失败: %w", err) } return string(hash), nil } // BcryptVerify 使用bcrypt验证密码 func BcryptVerify(hashedPassword, password string) bool { err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) return err == nil }