How to Encrypt Existing Data

When you adopt Cloak in an existing project, you often need to encrypt fields that were not previously encrypted. This guide will help you through the process, step by step.

Create a Vault

Make sure you’ve set up (and configured!) a vault as described in the Cloak.Vault documentation and the installation guide.

Create Local Ecto Types

Create local Ecto types for each kind of field you want to encrypt, as described in the Cloak.Vault documentation and the installation guide.

Add encrypted_* Fields

You’ll need to create a duplicate version of each field you want to encrypt, perhaps with the encrypted_ prefix.

alter table(:users) do
  add :encrypted_name, :binary
  add :encrypted_metadata, :binary
end

Then, add the new fields to your schema. Make sure to keep them up to date with changes using a temporary put_encrypted_fields function in your changeset.

defmodule MyApp.Accounts.User do
  use Ecto.Schema

  import Ecto.Changeset

  schema "users" do
    field :name, :string
    field :encrypted_name, MyApp.Encrypted.Binary
    field :metadata, :map
    field :encrypted_metadata, MyApp.Encrypted.Map
  end

  @doc false
  def changeset(struct, attrs \\ %{}) do
    struct
    |> cast(attrs, [:name, :metadata])
    |> put_encrypted_fields()
  end

  # Temporary function during the migration process, to ensure
  # that all changes are copied over to the new fields
  defp put_encrypted_fields(changeset) do
    changeset
    |> put_change(:encrypted_name, get_field(changeset, :name))
    |> put_change(:encrypted_metadata, get_field(changeset, :metadata))
  end
end

Copy Data to New Fields

In a custom mix task or the IEx console, run the following:

MyApp.Accounts.User
|> MyApp.Repo.all()
|> Enum.map(fn user ->
  user
  |> Ecto.Changeset.change(%{
       encrypted_name: user.name,
       encrypted_metadata: user.metadata
     })
  |> MyApp.Repo.update!()
end)

Remove Original Fields

Once you’re confident that all the data has migrated successfully to the new fields, you can write a migration to remove the old, unencrypted fields.

alter table(:users) do
  remove :name
  remove :metadata
end

rename table(:users), :encrypted_name, to: :name
rename table(:users), :encrypted_metadata, to: :metadata

You can also remove the temporary put_encrypted_fields/1 function, leaving your schema like this:

defmodule MyApp.Accounts.User do
  use Ecto.Schema

  import Ecto.Changeset

  schema "users" do
    field :name, MyApp.Encrypted.Binary
    field :metadata, MyApp.Encrypted.Map
  end

  @doc false
  def changeset(struct, attrs \\ %{}) do
    struct
    |> cast(attrs, [:name, :metadata])
  end
end