// 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$$$". 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] }