matrixcloud / internal /auth /password.go
github-actions[bot]
Deploy from 4dfd54f2
857a91b
Raw
History Blame Contribute Delete
2.86 kB
// 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]
}