package domain import ( "crypto/hmac" "crypto/rand" "crypto/sha256" "crypto/subtle" "encoding/base64" "errors" "strconv" "strings" ) const creatorPasswordIterations = 120000 var ErrInvalidCreatorPassword = errors.New("invalid creator password") func HashCreatorPassword(password string) (string, error) { if len(password) < 6 || len(password) > 4096 { return "", ErrInvalidCreatorPassword } salt := make([]byte, 16) if _, err := rand.Read(salt); err != nil { return "", err } derived := pbkdf2SHA256([]byte(password), salt, creatorPasswordIterations, 32) return "pbkdf2-sha256$" + strconv.Itoa(creatorPasswordIterations) + "$" + base64.RawStdEncoding.EncodeToString(salt) + "$" + base64.RawStdEncoding.EncodeToString(derived), nil } func VerifyCreatorPassword(encoded, password string) bool { parts := strings.Split(encoded, "$") if len(parts) != 4 || parts[0] != "pbkdf2-sha256" || password == "" { return false } iterations, err := strconv.Atoi(parts[1]) if err != nil || iterations < 100000 || iterations > 1000000 { return false } salt, err := base64.RawStdEncoding.DecodeString(parts[2]) if err != nil || len(salt) < 16 { return false } expected, err := base64.RawStdEncoding.DecodeString(parts[3]) if err != nil || len(expected) != 32 { return false } actual := pbkdf2SHA256([]byte(password), salt, iterations, len(expected)) return subtle.ConstantTimeCompare(actual, expected) == 1 } func pbkdf2SHA256(password, salt []byte, iterations, keyLength int) []byte { key := make([]byte, 0, keyLength) for block := 1; len(key) < keyLength; block++ { mac := hmac.New(sha256.New, password) mac.Write(salt) mac.Write([]byte{byte(block >> 24), byte(block >> 16), byte(block >> 8), byte(block)}) u := mac.Sum(nil) t := append([]byte(nil), u...) for i := 1; i < iterations; i++ { mac = hmac.New(sha256.New, password) mac.Write(u) u = mac.Sum(nil) for j := range t { t[j] ^= u[j] } } key = append(key, t...) } return key[:keyLength] }