// Package crypto 提供口令哈希原语(argon2id,与 opsx-loki 同参数)。 package crypto import ( "crypto/rand" "crypto/subtle" "encoding/base64" "math/big" "golang.org/x/crypto/argon2" ) // HashPassword 生成 argon2id 口令哈希(salt+hash base64 编码) func HashPassword(password string) (string, error) { salt := make([]byte, 16) if _, err := rand.Read(salt); err != nil { return "", err } hash := argon2.IDKey([]byte(password), salt, 3, 64*1024, 4, 32) combined := append(salt, hash...) return base64.StdEncoding.EncodeToString(combined), nil } // VerifyPassword 常数时间比对口令哈希 func VerifyPassword(password, hashStr string) bool { combined, err := base64.StdEncoding.DecodeString(hashStr) if err != nil || len(combined) < 16 { return false } salt := combined[:16] hash := combined[16:] newHash := argon2.IDKey([]byte(password), salt, 3, 64*1024, 4, 32) return subtle.ConstantTimeCompare(hash, newHash) == 1 } // passwordAlphabet 去掉易混淆字符(0O1lI)的密码字母表 const passwordAlphabet = "23456789abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ" // RandomPassword 生成 n 位随机密码(crypto/rand,无易混淆字符) func RandomPassword(n int) (string, error) { max := big.NewInt(int64(len(passwordAlphabet))) buf := make([]byte, n) for i := range buf { idx, err := rand.Int(rand.Reader, max) if err != nil { return "", err } buf[i] = passwordAlphabet[idx.Int64()] } return string(buf), nil }