authority_ecto v0.1.2 Authority.Ecto.HMAC

An Ecto.Type to hash secrets (like tokens) using HMAC to reduce the chance of leaking, even if your database is compromised. If reversible encryption is desired, see the Cloak library instead.

Relies on :crypto.hmac/3, using the hash algorithm :sha256.

Configuration

Define an HMAC module in your project:

defmodule MyApp.HMAC do
  use Authority.Ecto.HMAC, secret: "secret here"
end

The :secret is required and can be specified any of the following ways:

# A simple string
secret: "string"

# An environment variable
secret: {:system, "VARIABLE_NAME"}

# An application configuration value
secret: {:app_env, :my_app, :hmac_secret}

# An application module configuration value
secret: {:app_env, :my_app_web, MyAppWeb.Endpoint, :secret}

If fetching from the :app_env, :system tuples are still supported.

config :my_app, hmac_secret: {:system, "HMAC_SECRET"}

Once your HMAC field module is configured, change your secret field’s type from :string to MyApp.HMAC.

schema "tokens" do
  field :token, MyApp.HMAC
end

Next, update your changeset function to generate random token strings using Authority.Ecto.Changeset.put_token/2.

import Authority.Ecto.Changeset

def changeset(struct, attrs \ %{}) do
  struct
  |> cast(attrs, [...])
  |> put_token(:token)
end

Usage

The :token field will now automatically generate and hash using the specified secret.

This means that the original token value is only available immediately after inserting the record. You should immediately store the original on the client-side. When the token is fetched from the database later, only the irreversible hashed value will be returned.

# The original, raw value is available after insert
token =
  %Token{}
  |> Token.changeset()
  |> Repo.insert!
# => %Token{token: "original-value"}

# Subsequent loads will only return a hashed value
Repo.get!(Token, token.id)
# => %Token{token: "4F5410A9D48AD80826A027F98DC7B9E1D20E5C42D3E7F341549954C28B5ABA89"}

However, if you preserved the original value on the client-side, (such as in a session cookie) you can still query using it. Ecto will transparently hash the value before using it to query the database.

Token
|> where(token: "original-value")
|> Repo.one()
# => %Token{token: "4F5410A9D48AD80826A027F98DC7B9E1D20E5C42D3E7F341549954C28B5ABA89"}

Link to this section Summary

Functions

Hashes a given string using the given secret, and returns the value as a Base.encode16/1 string. Relies on :crypto.hmac/3 with hash algorithm :sha256

Link to this section Functions

Link to this function hash(plaintext, secret)
hash(plaintext :: String.t(), secret :: String.t()) :: String.t()

Hashes a given string using the given secret, and returns the value as a Base.encode16/1 string. Relies on :crypto.hmac/3 with hash algorithm :sha256.

Examples

iex> hash("hello world", "authority")
"0695772E021A6880A6EF0D26C4898E733A1923A7CDBD5150F5B0C38B788A76E8"