package utils import ( "crypto/rand" "crypto/subtle" "encoding/base64" "fmt" "strings" "golang.org/x/crypto/argon2" ) type Argon2Params struct { Memory uint32 Iterations uint32 Parallelism uint8 SaltLength uint32 KeyLength uint32 } // Default secure parameters var DefaultParams = &Argon2Params{ Memory: 64 * 1024, // 64 MB Iterations: 3, Parallelism: 2, SaltLength: 16, KeyLength: 32, } // HashPassword creates a secure Argon2id hash func HashPassword(password string, p *Argon2Params) (string, error) { // Generate salt salt := make([]byte, p.SaltLength) _, err := rand.Read(salt) if err != nil { return "", fmt.Errorf("failed to generate salt: %w", err) } // Calculate hash hash := argon2.IDKey([]byte(password), salt, p.Iterations, p.Memory, p.Parallelism, p.KeyLength) // Format: $argon2id$v=19$m=65536,t=3,p=2$$ encoded := fmt.Sprintf("$argon2id$v=19$m=%d,t=%d,p=%d$%s$%s", p.Memory, p.Iterations, p.Parallelism, base64.RawStdEncoding.EncodeToString(salt), base64.RawStdEncoding.EncodeToString(hash), ) return encoded, nil } // VerifyPassword checks if the password matches the hash func VerifyPassword(password, encodedHash string) (bool, error) { parts := strings.Split(encodedHash, "$") if len(parts) != 6 { return false, fmt.Errorf("invalid hash format") } var memory uint32 var iterations uint32 var parallelism uint8 _, err := fmt.Sscanf(parts[3], "m=%d,t=%d,p=%d", &memory, &iterations, ¶llelism) if err != nil { return false, fmt.Errorf("invalid hash parameters: %w", err) } // Decode salt and hash salt, err := base64.RawStdEncoding.DecodeString(parts[4]) if err != nil { return false, fmt.Errorf("invalid salt encoding: %w", err) } expectedHash, err := base64.RawStdEncoding.DecodeString(parts[5]) if err != nil { return false, fmt.Errorf("invalid hash encoding: %w", err) } // Recalculate hash with the same parameters hash := argon2.IDKey([]byte(password), salt, iterations, memory, parallelism, uint32(len(expectedHash))) // Constant time comparison if subtle.ConstantTimeCompare(hash, expectedHash) == 1 { return true, nil } return false, nil }