package auth import ( "crypto/rand" "crypto/subtle" "encoding/base64" "errors" "fmt" "strings" "golang.org/x/crypto/argon2" ) // Argon2 参数(DEVELOPMENT 第 12 节 / OWASP 最低配置)。 const ( ArgonMemoryKiB = 19 * 1024 // 19 MiB ArgonTime = 2 ArgonThreads = 1 ArgonKeyLen = 32 ArgonSaltLen = 16 ) // ErrInvalidPHC 表示 PHC 字符串无法解析或参数不支持。 var ErrInvalidPHC = errors.New("auth: invalid phc") // HashPassword 用 argon2id 生成 PHC 字符串(不含并发池;池见 HashPool)。 func HashPassword(password string) (string, error) { salt := make([]byte, ArgonSaltLen) if _, err := rand.Read(salt); err != nil { return "", err } hash := argon2.IDKey([]byte(password), salt, ArgonTime, ArgonMemoryKiB, ArgonThreads, ArgonKeyLen) return encodePHC(salt, hash), nil } // VerifyPassword 常量时间比较密码与 PHC;不匹配时 ok=false 且 err=nil。 func VerifyPassword(password, phc string) (bool, error) { salt, hash, err := decodePHC(phc) if err != nil { return false, err } got := argon2.IDKey([]byte(password), salt, ArgonTime, ArgonMemoryKiB, ArgonThreads, ArgonKeyLen) if subtle.ConstantTimeCompare(got, hash) == 1 { return true, nil } return false, nil } func encodePHC(salt, hash []byte) string { return fmt.Sprintf( "$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s", argon2.Version, ArgonMemoryKiB, ArgonTime, ArgonThreads, base64.RawStdEncoding.EncodeToString(salt), base64.RawStdEncoding.EncodeToString(hash), ) } func decodePHC(phc string) (salt, hash []byte, err error) { // $argon2id$v=19$m=19456,t=2,p=1$salt$hash parts := strings.Split(phc, "$") if len(parts) != 6 || parts[1] != "argon2id" { return nil, nil, ErrInvalidPHC } var version int if _, scanErr := fmt.Sscanf(parts[2], "v=%d", &version); scanErr != nil || version != argon2.Version { return nil, nil, ErrInvalidPHC } var m, t, p int if _, scanErr := fmt.Sscanf(parts[3], "m=%d,t=%d,p=%d", &m, &t, &p); scanErr != nil { return nil, nil, ErrInvalidPHC } if m != ArgonMemoryKiB || t != ArgonTime || p != ArgonThreads { return nil, nil, ErrInvalidPHC } salt, err = base64.RawStdEncoding.DecodeString(parts[4]) if err != nil { return nil, nil, ErrInvalidPHC } hash, err = base64.RawStdEncoding.DecodeString(parts[5]) if err != nil { return nil, nil, ErrInvalidPHC } if len(salt) == 0 || len(hash) == 0 { return nil, nil, ErrInvalidPHC } return salt, hash, nil }