AI Gateway Go 0.10.0 源码快照 + 旗舰版需求规划报告
M0-M7 已完成:核心网关(身份/RBAC/TOTP/OIDC/SAML/Provider/配额/路由/内容策略/审计/定价)+ 资源市场(MCP/Skills/数字员工)。 含 22 个 PostgreSQL 迁移、管理端/门户端前端源码、OpenAPI 契约、部署 compose。 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
package identity
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
currentPBKDF2Iterations = 600_000
|
||||
legacyPBKDF2Iterations = 120_000
|
||||
maximumPBKDF2Iterations = 2_000_000
|
||||
passwordDigestBytes = 32
|
||||
)
|
||||
|
||||
var ErrInvalidPasswordHash = errors.New("invalid password hash")
|
||||
|
||||
type PasswordHasher struct{}
|
||||
|
||||
func (PasswordHasher) Hash(password string) (string, error) {
|
||||
saltBytes := make([]byte, 16)
|
||||
if _, err := rand.Read(saltBytes); err != nil {
|
||||
return "", fmt.Errorf("generate password salt: %w", err)
|
||||
}
|
||||
return hashWithSalt(password, hex.EncodeToString(saltBytes), currentPBKDF2Iterations), nil
|
||||
}
|
||||
|
||||
func (PasswordHasher) Verify(password, stored string) bool {
|
||||
iterations, salt, expected, err := parsePasswordHash(stored)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
actual := pbkdf2SHA256([]byte(password), []byte(salt), iterations, len(expected))
|
||||
return subtle.ConstantTimeCompare(actual, expected) == 1
|
||||
}
|
||||
|
||||
func (PasswordHasher) NeedsUpgrade(stored string) bool {
|
||||
iterations, _, _, err := parsePasswordHash(stored)
|
||||
return err == nil && iterations < currentPBKDF2Iterations
|
||||
}
|
||||
|
||||
func hashWithSalt(password, salt string, iterations int) string {
|
||||
digest := pbkdf2SHA256([]byte(password), []byte(salt), iterations, passwordDigestBytes)
|
||||
return fmt.Sprintf("pbkdf2_sha256$%d$%s$%s", iterations, salt, hex.EncodeToString(digest))
|
||||
}
|
||||
|
||||
func parsePasswordHash(stored string) (int, string, []byte, error) {
|
||||
parts := strings.Split(stored, "$")
|
||||
iterations := legacyPBKDF2Iterations
|
||||
var salt, encodedDigest string
|
||||
switch {
|
||||
case len(parts) == 4 && parts[0] == "pbkdf2_sha256":
|
||||
parsed, err := strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
return 0, "", nil, ErrInvalidPasswordHash
|
||||
}
|
||||
iterations, salt, encodedDigest = parsed, parts[2], parts[3]
|
||||
case len(parts) == 2:
|
||||
salt, encodedDigest = parts[0], parts[1]
|
||||
default:
|
||||
return 0, "", nil, ErrInvalidPasswordHash
|
||||
}
|
||||
if iterations < 1 || iterations > maximumPBKDF2Iterations || salt == "" {
|
||||
return 0, "", nil, ErrInvalidPasswordHash
|
||||
}
|
||||
digest, err := hex.DecodeString(encodedDigest)
|
||||
if err != nil || len(digest) != passwordDigestBytes {
|
||||
return 0, "", nil, ErrInvalidPasswordHash
|
||||
}
|
||||
return iterations, salt, digest, nil
|
||||
}
|
||||
|
||||
func pbkdf2SHA256(password, salt []byte, iterations, keyLength int) []byte {
|
||||
const hashLength = sha256.Size
|
||||
blocks := (keyLength + hashLength - 1) / hashLength
|
||||
result := make([]byte, 0, blocks*hashLength)
|
||||
buffer := make([]byte, len(salt)+4)
|
||||
copy(buffer, salt)
|
||||
for block := 1; block <= blocks; block++ {
|
||||
binary.BigEndian.PutUint32(buffer[len(salt):], uint32(block))
|
||||
mac := hmac.New(sha256.New, password)
|
||||
_, _ = mac.Write(buffer)
|
||||
u := mac.Sum(nil)
|
||||
t := append([]byte(nil), u...)
|
||||
for round := 1; round < iterations; round++ {
|
||||
mac.Reset()
|
||||
_, _ = mac.Write(u)
|
||||
u = mac.Sum(nil)
|
||||
for index := range t {
|
||||
t[index] ^= u[index]
|
||||
}
|
||||
}
|
||||
result = append(result, t...)
|
||||
}
|
||||
return result[:keyLength]
|
||||
}
|
||||
Reference in New Issue
Block a user