// Package crypto 密码哈希(argon2id)与对称加密(AES-GCM)。 package crypto import ( "crypto/aes" "crypto/cipher" "crypto/rand" "crypto/subtle" "encoding/base64" "errors" "fmt" "strings" "golang.org/x/crypto/argon2" ) // PasswordHasher argon2id 参数(来自配置)。 type PasswordHasher struct { Time uint32 Memory uint32 Threads uint8 KeyLen uint32 SaltLen int } func NewPasswordHasher(time, memory uint32, threads uint8, keyLen uint32, saltLen int) *PasswordHasher { return &PasswordHasher{Time: time, Memory: memory, Threads: threads, KeyLen: keyLen, SaltLen: saltLen} } // HashPassword 编码为 $argon2id$v=19$m=...,t=...,p=...$salt$hash func (h *PasswordHasher) HashPassword(password string) (string, error) { salt := make([]byte, h.SaltLen) if _, err := rand.Read(salt); err != nil { return "", err } key := argon2.IDKey([]byte(password), salt, h.Time, h.Memory, h.Threads, h.KeyLen) enc := base64.RawStdEncoding return fmt.Sprintf("$argon2id$v=19$m=%d,t=%d,p=%d$%s$%s", h.Memory, h.Time, h.Threads, enc.EncodeToString(salt), enc.EncodeToString(key)), nil } // VerifyPassword 校验密码,常数时间比较。 func (h *PasswordHasher) VerifyPassword(encoded, password string) (bool, error) { parts := strings.Split(encoded, "$") if len(parts) != 6 || parts[1] != "argon2id" { return false, errors.New("invalid hash format") } var memory, time uint32 var threads uint8 if _, err := fmt.Sscanf(parts[3], "m=%d,t=%d,p=%d", &memory, &time, &threads); err != nil { return false, err } enc := base64.RawStdEncoding salt, err := enc.DecodeString(parts[4]) if err != nil { return false, err } want, err := enc.DecodeString(parts[5]) if err != nil { return false, err } got := argon2.IDKey([]byte(password), salt, time, memory, threads, uint32(len(want))) return subtle.ConstantTimeCompare(got, want) == 1, nil } // --------------------------------------------------------------------------- // AES-GCM 渠道密钥加密 // Encryptor 用主密钥加解密渠道上游 key。 type Encryptor struct { key []byte } // NewEncryptor 主密钥必须为 16/24/32 字节;不足时用 SHA-256 派生固定 32 字节。 func NewEncryptor(master string) *Encryptor { key := []byte(master) switch len(key) { case 16, 24, 32: default: key = sha256Sum(master) } return &Encryptor{key: key} } // Encrypt 输出 base64(nonce || ciphertext) func (e *Encryptor) Encrypt(plain string) (string, error) { block, err := aes.NewCipher(e.key) if err != nil { return "", err } gcm, err := cipher.NewGCM(block) if err != nil { return "", err } nonce := make([]byte, gcm.NonceSize()) if _, err := rand.Read(nonce); err != nil { return "", err } ct := gcm.Seal(nil, nonce, []byte(plain), nil) return base64.StdEncoding.EncodeToString(append(nonce, ct...)), nil } // Decrypt 解析 Encrypt 的输出。 func (e *Encryptor) Decrypt(enc string) (string, error) { raw, err := base64.StdEncoding.DecodeString(enc) if err != nil { return "", err } block, err := aes.NewCipher(e.key) if err != nil { return "", err } gcm, err := cipher.NewGCM(block) if err != nil { return "", err } if len(raw) < gcm.NonceSize() { return "", errors.New("ciphertext too short") } nonce, ct := raw[:gcm.NonceSize()], raw[gcm.NonceSize():] plain, err := gcm.Open(nil, nonce, ct, nil) if err != nil { return "", err } return string(plain), nil }