package auth import ( "crypto/rand" "crypto/subtle" "encoding/base64" "errors" "fmt" "strings" "golang.org/x/crypto/argon2" ) const ( // Argon2id parameters argon2Memory = 64 * 1024 // 64 MB argon2Iterations = 3 argon2Parallelism = 2 argon2SaltLength = 16 argon2KeyLength = 32 ) // HashPassword hashes a password using Argon2id func HashPassword(password string) (string, error) { // Generate a random salt salt := make([]byte, argon2SaltLength) if _, err := rand.Read(salt); err != nil { return "", err } // Hash the password hash := argon2.IDKey([]byte(password), salt, argon2Iterations, argon2Memory, argon2Parallelism, argon2KeyLength) // Encode the hash and salt b64Salt := base64.RawStdEncoding.EncodeToString(salt) b64Hash := base64.RawStdEncoding.EncodeToString(hash) // Return the encoded hash in the format: $argon2id$v=19$m=65536,t=3,p=2$salt$hash return fmt.Sprintf("$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s", argon2.Version, argon2Memory, argon2Iterations, argon2Parallelism, b64Salt, b64Hash), nil } // VerifyPassword verifies a password against a hash func VerifyPassword(password, encodedHash string) (bool, error) { // Parse the encoded hash parts := strings.Split(encodedHash, "$") if len(parts) != 6 { return false, errors.New("invalid hash format") } if parts[1] != "argon2id" { return false, errors.New("unsupported hash algorithm") } // Parse version var version int if _, err := fmt.Sscanf(parts[2], "v=%d", &version); err != nil { return false, err } if version != argon2.Version { return false, errors.New("incompatible version") } // Parse parameters var memory, iterations, parallelism int if _, err := fmt.Sscanf(parts[3], "m=%d,t=%d,p=%d", &memory, &iterations, ¶llelism); err != nil { return false, err } // Decode salt and hash salt, err := base64.RawStdEncoding.DecodeString(parts[4]) if err != nil { return false, err } hash, err := base64.RawStdEncoding.DecodeString(parts[5]) if err != nil { return false, err } // Compute the hash of the password otherHash := argon2.IDKey([]byte(password), salt, uint32(iterations), uint32(memory), uint8(parallelism), uint32(len(hash))) // Compare hashes in constant time if subtle.ConstantTimeCompare(hash, otherHash) == 1 { return true, nil } return false, nil }