70 lines
2.1 KiB
Go
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))
|
|
}
|