defmodule Argon2 do @moduledoc """ Argon2 password hashing for Elixir using Rust NIFs. This module provides a secure way to hash passwords using the Argon2i algorithm with configuration presets following security best practices. ## Security Presets * `:owasp` (default) - OWASP recommended settings (m=19456, t=2, p=1) * `:strong` - Higher security settings (m=65540, t=3, p=4) * `:test_unsafe` - Fast settings for testing only (m=1024, t=1, p=1) ## Examples # Hash with default OWASP settings iex> hash = Argon2.hash_password("secure_password123") iex> String.starts_with?(hash, "$argon2i$v=19$m=19456,t=2,p=1$") true # Hash with strong settings iex> hash = Argon2.hash_password("secure_password123", "strong") iex> String.starts_with?(hash, "$argon2i$v=19$m=65540,t=3,p=4$") true # Verify password iex> hash = Argon2.hash_password("secure_password123") iex> Argon2.verify_password("secure_password123", hash) true iex> Argon2.verify_password("wrong_password", hash) false ## Security Notes * Passwords must be at least 8 characters long * Each hash uses a unique random salt * The `:test_unsafe` preset should never be used in production """ @type password :: String.t() @type hash :: String.t() @type config :: String.t() @doc """ Hashes a password using Argon2i. ## Options * `config` - One of `"owasp"` (default), `"strong"`, or `"test_unsafe"` ## Examples iex> hash = Argon2.hash_password("secure_password123") iex> is_binary(hash) true ## Security Notes * Passwords must be at least 8 characters * A unique random salt is used for each hash * The default OWASP preset is recommended for most use cases Raises `ArgumentError` if the password is less than 8 characters long. """ @spec hash_password(password :: password, config :: config | nil) :: hash def hash_password(password, config \\ nil) do case Argon2.Native.hash_password(password, config) do {:error, message} -> raise ArgumentError, message result -> result end end @doc """ Verifies a password against a hash. Takes constant time regardless of whether the password matches or not. ## Examples iex> hash = Argon2.hash_password("secure_password123") iex> Argon2.verify_password("secure_password123", hash) true Raises `ArgumentError` if: * The password is less than 8 characters long * The hash format is invalid """ @spec verify_password(password :: password, hash :: hash) :: boolean def verify_password(password, hash) do case Argon2.Native.verify_password(password, hash) do {:error, message} -> raise ArgumentError, message result -> result end end end