more customer
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
// Package customerauth adds password-based signup/login to the profile service.
|
||||
//
|
||||
// It deliberately uses only the standard library: PBKDF2-HMAC-SHA256 for
|
||||
// password hashing (crypto/pbkdf2, Go 1.24+) and HMAC-SHA256-signed cookies for
|
||||
// sessions (crypto/hmac). No password ever leaves the server in plaintext and
|
||||
// the session token is opaque + tamper-evident.
|
||||
package customerauth
|
||||
|
||||
import (
|
||||
"crypto/pbkdf2"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// pbkdf2Iterations is the work factor for PBKDF2-HMAC-SHA256. 210_000 matches
|
||||
// the current OWASP recommendation for PBKDF2-SHA256.
|
||||
const pbkdf2Iterations = 210_000
|
||||
|
||||
const (
|
||||
saltLen = 16
|
||||
keyLen = 32
|
||||
)
|
||||
|
||||
// HashPassword returns an encoded PBKDF2 hash of the form
|
||||
//
|
||||
// pbkdf2-sha256$<iterations>$<saltB64>$<hashB64>
|
||||
//
|
||||
// with a fresh random salt. The encoded string is self-describing so Verify can
|
||||
// read the parameters back without external configuration.
|
||||
func HashPassword(password string) (string, error) {
|
||||
salt := make([]byte, saltLen)
|
||||
if _, err := rand.Read(salt); err != nil {
|
||||
return "", fmt.Errorf("customerauth: read salt: %w", err)
|
||||
}
|
||||
dk, err := pbkdf2.Key(sha256.New, password, salt, pbkdf2Iterations, keyLen)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("customerauth: pbkdf2: %w", err)
|
||||
}
|
||||
return fmt.Sprintf("pbkdf2-sha256$%d$%s$%s",
|
||||
pbkdf2Iterations,
|
||||
base64.RawStdEncoding.EncodeToString(salt),
|
||||
base64.RawStdEncoding.EncodeToString(dk),
|
||||
), nil
|
||||
}
|
||||
|
||||
// VerifyPassword reports whether password matches the encoded hash. It is
|
||||
// constant-time in the hash comparison and returns false for any malformed
|
||||
// encoding rather than erroring, so callers can treat it as a simple predicate.
|
||||
func VerifyPassword(password, encoded string) bool {
|
||||
parts := strings.Split(encoded, "$")
|
||||
if len(parts) != 4 || parts[0] != "pbkdf2-sha256" {
|
||||
return false
|
||||
}
|
||||
iter, err := strconv.Atoi(parts[1])
|
||||
if err != nil || iter <= 0 {
|
||||
return false
|
||||
}
|
||||
salt, err := base64.RawStdEncoding.DecodeString(parts[2])
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
want, err := base64.RawStdEncoding.DecodeString(parts[3])
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
got, err := pbkdf2.Key(sha256.New, password, salt, iter, len(want))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return subtle.ConstantTimeCompare(got, want) == 1
|
||||
}
|
||||
Reference in New Issue
Block a user