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 :tag
s. 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.