defmodule Argon2.Base do @moduledoc """ Lower-level api for Argon2. These functions can be useful if you want more control over some of the options. In most cases, you will not need to call these functions directly. """ @compile {:autoload, false} @on_load {:init, 0} def init do case load_nif() do :ok -> :ok _ -> raise """ An error occurred when loading Argon2. Make sure you have a C compiler and Erlang 20 installed. If you are not using Erlang 20, either upgrade to Erlang 20 or use bcrypt_elixir (version 0.12) or pbkdf2_elixir. See the Comeonin wiki for more information. """ end end @doc """ Hash a password using Argon2. """ def hash_nif( t_cost, m_cost, parallelism, password, salt, raw, hashlen, encodedlen, argon2_type, argon2_version ) def hash_nif(_, _, _, _, _, _, _, _, _, _), do: :erlang.nif_error(:not_loaded) @doc """ Verify a password using Argon2. """ def verify_nif(stored_hash, password, argon2_type) def verify_nif(_, _, _), do: :erlang.nif_error(:not_loaded) @doc """ Translate the error code to an error message. """ def error_nif(error_code) def error_nif(_), do: :erlang.nif_error(:not_loaded) @doc """ Calculate the length of the encoded hash. """ def encodedlen_nif(t_cost, m_cost, parallelism, saltlen, hashlen, argon2_type) def encodedlen_nif(_, _, _, _, _, _), do: :erlang.nif_error(:not_loaded) @doc """ Generate a random salt. The default length for the salt is 16 bytes. We do not recommend using a salt shorter than the default. """ def gen_salt(salt_len \\ 16), do: :crypto.strong_rand_bytes(salt_len) @doc """ Hash a password using Argon2. ## Options There are six options: * `:t_cost` - time cost * the default is 8 * `:m_cost` - memory usage * the default is 16 - this will produce a memory usage of 64 MiB (2 ^ 16 KiB) * `:parallelism` - number of parallel threads * the default is 2 * `:format` - output format * this value can be * `:encoded` - encoded with Argon2 crypt format * `:raw_hash` - raw hash output in hexadecimal format * `:report` - raw hash and encoded hash, together with the options used * `:encoded` is the default * `:hashlen` - length of the hash (in bytes) * the default is 32 * `:argon2_type` - Argon2 type * this value should be 0 (Argon2d), 1 (Argon2i) or 2 (Argon2id) * the default is 2 (Argon2id) The `t_cost`, `m_cost`, `parallelism` and `argon2_type` can also be set in the config. See the documentation for Argon2.Stats for more information about choosing these values. ## Examples The following example changes the default `t_cost` and `m_cost`: Argon2.Base.hash_password("password", "somesaltSOMESALT", [t_cost: 4, m_cost: 18]) In the example below, the Argon2 type is changed to Argon2d: Argon2.Base.hash_password("password", "somesaltSOMESALT", [argon2_type: 0]) To use Argon2i, use `argon2_type: 1`. """ def hash_password(password, salt, opts \\ []) do {t, m, p, hashlen, argon2_type} = options = hash_opts(opts) format = output_opts(opts[:format]) encodedlen = (format == 1 and 0) || encodedlen_nif(t, m, p, byte_size(salt), hashlen, argon2_type) hash_nif(t, m, p, password, salt, format, hashlen, encodedlen, argon2_type, 0) |> handle_result(options, format) end def handle_error(error) do raise ArgumentError, error |> error_nif() |> :binary.list_to_bin() end defp load_nif do path = :filename.join(:code.priv_dir(:argon2_elixir), 'argon2_nif') :erlang.load_nif(path, 0) end defp hash_opts(opts) do { Keyword.get(opts, :t_cost, Application.get_env(:argon2_elixir, :t_cost, 8)), Keyword.get(opts, :m_cost, Application.get_env(:argon2_elixir, :m_cost, 16)), Keyword.get(opts, :parallelism, Application.get_env(:argon2_elixir, :parallelism, 2)), Keyword.get(opts, :hashlen, 32), Keyword.get(opts, :argon2_type, Application.get_env(:argon2_elixir, :argon2_type, 2)) } end defp output_opts(:raw_hash), do: 1 defp output_opts(:report), do: 2 defp output_opts(_), do: 0 defp handle_result(result, _, _) when is_integer(result) do handle_error(result) end defp handle_result({_, encoded}, _, 0), do: :binary.list_to_bin(encoded) defp handle_result({raw, _}, _, 1), do: :binary.list_to_bin(raw) defp handle_result({raw, encoded}, options, 2) do {:binary.list_to_bin(raw), :binary.list_to_bin(encoded), options} end end