Files
ggjuku/internal/app/account_password.go
2026-09-30 10:18:32 +08:00

70 lines
2.1 KiB
Go

package app
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"errors"
"strings"
"unicode"
"unicode/utf8"
)
const accountPasswordIterations = 600000
func normalizeAccountUsername(name string) (string, string, error) {
name = strings.TrimSpace(name)
if !utf8.ValidString(name) || utf8.RuneCountInString(name) < 2 || utf8.RuneCountInString(name) > 32 {
return "", "", errors.New("用户名需为 2–32 个字,支持中文、字母、数字、下划线和短横线")
}
for _, char := range name {
if !unicode.IsLetter(char) && !unicode.IsDigit(char) && char != '_' && char != '-' {
return "", "", errors.New("用户名只能包含中文、字母、数字、下划线和短横线")
}
}
return strings.ToLower(name), name, nil
}
func validateAccountPassword(password string) error {
count := utf8.RuneCountInString(password)
if !utf8.ValidString(password) || count < 10 || count > 128 {
return errors.New("密码需为 10–128 个字符,可使用长短语")
}
return nil
}
func accountPasswordKey(password string, salt []byte, iterations int) []byte {
mac := hmac.New(sha256.New, []byte(password))
mac.Write(salt)
mac.Write([]byte{0, 0, 0, 1})
block := mac.Sum(nil)
key := append([]byte(nil), block...)
for remaining := iterations - 1; remaining > 0; remaining-- {
mac.Reset()
mac.Write(block)
block = mac.Sum(block[:0])
for index, value := range block {
key[index] ^= value
}
}
return key
}
func newAccountPassword(password string) (string, string, error) {
if err := validateAccountPassword(password); err != nil {
return "", "", err
}
salt := randomHex(16)
if salt == "" {
return "", "", errors.New("无法生成密码盐,请重试")
}
decoded, _ := hex.DecodeString(salt)
return salt, hex.EncodeToString(accountPasswordKey(password, decoded, accountPasswordIterations)), nil
}
func verifyAccountPassword(account accountRecord, password string) bool {
salt, _ := hex.DecodeString(account.Salt)
expected, _ := hex.DecodeString(account.PasswordHash)
return hmac.Equal(expected, accountPasswordKey(password, salt, account.Iterations))
}