View Source Cloak.Ecto.HMAC behaviour (cloak_ecto v1.3.0)

A custom Ecto.Type for hashing fields using :crypto.hmac/3.

Why

If you store a hash of a field's value, you can then query on it as a proxy for an encrypted field. This works because HMAC is deterministic and always results in the same value, while secure encryption does not. Be warned, however, that hashing will expose which fields have the same value, because they will contain the same hash.

Security

HMAC is more secure than Cloak.Ecto.SHA256, because it uses a secret to obfuscate the hash. This makes it harder to guess the value of the field.

Configuration

Create an HMAC field in your project:

defmodule MyApp.Hashed.HMAC do
  use Cloak.Ecto.HMAC, otp_app: :my_app
end

Then, configure it with a :secret and :algorithm, either using mix configuration:

config :my_app, MyApp.Hashed.HMAC,
  algorithm: :sha512,
  secret: "secret"

Or using the init/1 callback to fetch configuration at runtime:

defmodule MyApp.Hashed.HMAC do
  use Cloak.Ecto.HMAC, otp_app: :my_app

  @impl Cloak.Ecto.HMAC
  def init(config) do
    config = Keyword.merge(config, [
      algorithm: :sha512,
      secret: System.get_env("HMAC_SECRET")
    ])

    {:ok, config}
  end
end

Usage

Create the hash field with the type :binary. Add it to your schema definition like this:

schema "table" do
  field :field_name, MyApp.Encrypted.Binary
  field :field_name_hash, MyApp.Hashed.HMAC
end

Ensure that the hash is updated whenever the target field changes with the put_change/3 function:

def changeset(struct, attrs \\ %{}) do
  struct
  |> cast(attrs, [:field_name, :field_name_hash])
  |> put_hashed_fields()
end

defp put_hashed_fields(changeset) do
  changeset
  |> put_change(:field_name_hash, get_field(changeset, :field_name))
end

Query the Repo using the :field_name_hash in any place you would typically query by :field_name.

user = Repo.get_by(User, email_hash: "user@email.com")

Summary

Types

HMAC algorithms supported by Cloak.Field.HMAC

Callbacks

Configures the HMAC field using runtime information.

Types

@type algorithms() :: :md5 | :ripemd160 | :sha | :sha224 | :sha256 | :sha384 | :sha512

HMAC algorithms supported by Cloak.Field.HMAC

Callbacks

@callback init(config :: Keyword.t()) :: {:ok, Keyword.t()} | {:error, any()}

Configures the HMAC field using runtime information.

Example

@impl Cloak.Fields.HMAC
def init(config) do
  config = Keyword.merge(config, [
    algorithm: :sha512,
    secret: System.get_env("HMAC_SECRET")
  ])

  {:ok, config}
end