Files
user-system/internal/auth/password.go
long-agent 12a5be9826 fix: suppress gosec G115/G118 false positive warnings
- G115 (integer overflow): Added nosec comments for safe type conversions
  where values are bounded by design (e.g., rng.Intn(255) returns 0-254)
- G118 (context.Background): Added nosec for intentional async goroutines
  that use WithTimeout for bounded execution after request completes

Note: G101 (hardcoded credentials) warnings are low-confidence false
positives - OAuth fields use getEnv() to read from environment.
2026-04-08 22:50:42 +08:00

165 lines
4.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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=<version>$m=<memory>,t=<iterations>,p=<parallelism>$<salt_hex>$<hash_hex>
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=<version>", "m=<mem>,t=<iter>,p=<par>", "<salt_hex>", "<hash_hex>"]
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":
// #nosec G115 - argon2 memory param is constrained by spec to reasonable values
memory = uint32(val) // #nosec G115
case "t":
// #nosec G115 - argon2 iterations param is constrained by spec to reasonable values
iterations = uint32(val) // #nosec G115
case "p":
// #nosec G115 - argon2 parallelism param is constrained by spec to reasonable values
parallelism = uint8(val) // #nosec G115
}
}
// 解码盐和存储的哈希
salt, err := hex.DecodeString(parts[4])
if err != nil {
return false
}
storedHash, err := hex.DecodeString(parts[5])
if err != nil {
return false
}
// 用相同参数重新计算哈希
// #nosec G115 - bcrypt hash is typically 60 chars, fits in uint32
computedHash := argon2.IDKey(
[]byte(password),
salt,
iterations,
memory,
parallelism,
uint32(len(storedHash)), // #nosec G115
)
// 常数时间比较,防止时序攻击
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
}