Cloak Your Ecto Data

September 22, 2015

I’ve written previously about how to encrypt your data when you are using Ecto, here:

Encrypting Data With Ecto
Changing Your Ecto Encryption Key

After deciding to use encryption for one of my personal projects, I decided that the techniques I wrote about should be built into a Hex package. With that in mind…

Presenting Cloak

I’m pleased to present Cloak, a simple encryption package designed to be used with Ecto. It implements all the ideas I’ve written about, and then some. It supports:

  • Seamless encryption/decryption of model fields
  • Zero-downtime migrations to new keys and cipher suites
  • Multiple ciphers in use at once (though only one will be used for encryption)

Configuration

It’s simple to use. Select a cipher (there’s only one currently, AES CTR), and then configure it in your config/config.exs:

config :cloak, Cloak.AES.CTR,
  tag: "AES",
  default: true,
  keys: [
    %{tag: <<1>>, key: :base64.decode("your key here"), default: true}
  ]

The :tag for the given cipher module will be prepended to any ciphertext generated by that module. :default controls whether this cipher should be the default for encrypting new values, and the :keys array holds a list of keys to use.

Each key also has a :tag, which will become part of each ciphertext, and a :default setting to control whether it should be used for new encryption.

You can then use the Cloak.encrypt/1 and Cloak.decrypt/1 functions directly to encrypt and decrypt values using the default cipher module:

# Delegates encryption work to Cloak.AES.CTR.encrypt/1
Cloak.encrypt("Hello, World!")
# => <<65, 69, 83, 1, 235, 128, 59, ...>>

Cloak.encrypt("Hello, World!")
|> Cloak.decrypt
# => "Hello, World!"

Cloak is able to distinguish between ciphertext based on the :tags. For example, when using the Cloak.AES.CTR cipher module, Cloak will generate ciphertext in this format:

Cipher Tag
(n bytes)
Key Tag
(1 byte)
IV
(16 bytes)
Ciphertext
(n bytes)
“AES” 1 <<235, 128, 59, 167, 97, ...>> <<89, 228, 1, 111, 197, 201, ...>>

When you call Cloak.decrypt/1, Cloak will use the cipher tag to determine which module created the ciphertext, and will then pass the rest of the binary to that module’s decrypt/1 function for decryption.

This allows Cloak to continue to decrypt old values seamlessly, even if you’ve set up a new :default encryption module or key.

Ecto Integration

It’s easy to integrate Cloak with Ecto. Add the field you intend to encrypt to your database; it should be a :binary field.

defmodule User do
  use Ecto.Model

  schema "users" do
    field :name, :binary
  end
end

Then, replace the :binary type with Cloak.EncryptedBinaryField:

defmodule User do
  use Ecto.Model

  schema "users" do
    field :name, Cloak.EncryptedBinaryField
  end
end

And that’s it! The :name field will be transparently encrypted and decrypted with the cipher module you configured.

Cloak supports string fields, map fields, integer fields, float fields, and SHA256 hash fields as of this writing. I’m definitely open to contributions!

Migrating to a New Key or Cipher

Because Cloak tags every piece of ciphertext with metadata, you don’t have to do anything when you switch to a new key. Old data will be gradually converted to the new key or cipher as your app is used.

At the very least, this means that your app can stay up during the transition. However, you usually will want to proactively migrate rows to the new key. To do so, you will need to track which encryption configuration was used to encrypt each row, so that you can know which rows need to be migrated.

Add an :encryption_version field to your module, with a :binary type, and use the Cloak.Model module.

defmodule User do
  use Ecto.Model
+ use Cloak.Model, :encryption_version

  schema "users" do
    field :name, Cloak.EncryptedBinaryField
+   field :encryption_version, :binary
  end
end

This will set up before_insert and before_update hooks on your model to save metadata about the encryption that was used to encrypt that module in the :encryption_version field.

Next, configure your :migration settings in config/config.exs:

config :cloak, :migration,
  repo: Repo,
  models: [User] # A list of modules that you want to migrate

Then, simply run the following mix task, and all the rows in your database will be proactively migrated to the new :default encryption configuration!

$ mix cloak.migrate

Conclusion

I hope this package will be useful to others out there. If you have any suggestions or ideas, please let me know over on Github.


More Documentation?

See the Hex documentation for Cloak for more in-depth information.

Contributing

I realize that only supporting AES CTR encryption is somewhat limiting. If you’d like to contribute more encryptors, I’m accepting PRs at Cloak’s Github repo.

comments powered by Disqus