Spaces:
Runtime error
Runtime error
| // Package auth provides password hashing and session-token helpers for the | |
| // multitenant user store. Hashing uses PBKDF2-HMAC-SHA256 from the standard | |
| // library only (no external crypto dependency). | |
| package auth | |
| import ( | |
| "crypto/hmac" | |
| "crypto/rand" | |
| "crypto/sha256" | |
| "crypto/subtle" | |
| "encoding/hex" | |
| "fmt" | |
| "strconv" | |
| "strings" | |
| ) | |
| const ( | |
| pbkdfIterations = 120_000 | |
| saltLen = 16 | |
| keyLen = 32 | |
| ) | |
| // HashPassword returns an encoded PBKDF2 hash of the form | |
| // "pbkdf2$<iter>$<salt-hex>$<key-hex>". | |
| func HashPassword(password string) (string, error) { | |
| salt := make([]byte, saltLen) | |
| if _, err := rand.Read(salt); err != nil { | |
| return "", err | |
| } | |
| key := pbkdf2SHA256([]byte(password), salt, pbkdfIterations, keyLen) | |
| return fmt.Sprintf("pbkdf2$%d$%s$%s", pbkdfIterations, hex.EncodeToString(salt), hex.EncodeToString(key)), nil | |
| } | |
| // VerifyPassword reports whether password matches the encoded hash. | |
| func VerifyPassword(password, encoded string) bool { | |
| parts := strings.Split(encoded, "$") | |
| if len(parts) != 4 || parts[0] != "pbkdf2" { | |
| return false | |
| } | |
| iter, err := strconv.Atoi(parts[1]) | |
| if err != nil || iter <= 0 { | |
| return false | |
| } | |
| salt, err := hex.DecodeString(parts[2]) | |
| if err != nil { | |
| return false | |
| } | |
| want, err := hex.DecodeString(parts[3]) | |
| if err != nil { | |
| return false | |
| } | |
| got := pbkdf2SHA256([]byte(password), salt, iter, len(want)) | |
| return subtle.ConstantTimeCompare(got, want) == 1 | |
| } | |
| // FastHash returns a hex SHA-256 of s. Suitable for indexing/looking up | |
| // high-entropy random tokens (not for low-entropy passwords — use HashPassword). | |
| func FastHash(s string) string { | |
| sum := sha256.Sum256([]byte(s)) | |
| return hex.EncodeToString(sum[:]) | |
| } | |
| // NewToken returns a random 32-byte hex session token. | |
| func NewToken() (string, error) { | |
| b := make([]byte, 32) | |
| if _, err := rand.Read(b); err != nil { | |
| return "", err | |
| } | |
| return hex.EncodeToString(b), nil | |
| } | |
| // NewID returns a random identifier with the given prefix. | |
| func NewID(prefix string) string { | |
| b := make([]byte, 8) | |
| _, _ = rand.Read(b) | |
| return prefix + hex.EncodeToString(b) | |
| } | |
| // pbkdf2SHA256 is a minimal PBKDF2-HMAC-SHA256 implementation (RFC 2898). | |
| func pbkdf2SHA256(password, salt []byte, iter, keyLen int) []byte { | |
| hLen := sha256.Size | |
| numBlocks := (keyLen + hLen - 1) / hLen | |
| out := make([]byte, 0, numBlocks*hLen) | |
| block := make([]byte, len(salt)+4) | |
| copy(block, salt) | |
| for i := 1; i <= numBlocks; i++ { | |
| block[len(salt)+0] = byte(i >> 24) | |
| block[len(salt)+1] = byte(i >> 16) | |
| block[len(salt)+2] = byte(i >> 8) | |
| block[len(salt)+3] = byte(i) | |
| mac := hmac.New(sha256.New, password) | |
| mac.Write(block) | |
| u := mac.Sum(nil) | |
| t := make([]byte, len(u)) | |
| copy(t, u) | |
| for n := 2; n <= iter; n++ { | |
| mac.Reset() | |
| mac.Write(u) | |
| u = mac.Sum(nil) | |
| for j := range t { | |
| t[j] ^= u[j] | |
| } | |
| } | |
| out = append(out, t...) | |
| } | |
| return out[:keyLen] | |
| } | |