How to encrypt your data with Ecto

If your data is encrypted, even if it’s leaked, no one know what is the data. That’s great.

So in this post, I’m going to show you how to encrypt data in Ecto. Ecto allows developer to define their own type. And we will define a type call EncryptedTex…


This content originally appeared on DEV Community and was authored by Dung Nguyen

If your data is encrypted, even if it's leaked, no one know what is the data. That's great.

So in this post, I'm going to show you how to encrypt data in Ecto. Ecto allows developer to define their own type. And we will define a type call EncryptedText which encrypts data before persiting to database and decrypts data after loading.

1. Define encrypt/decrypt methods

This is a simple version of crypto module:

defmodule Crypto do
  @block_size 16

  def generate_secret do
    :crypto.strong_rand_bytes(@block_size)
    |> Base.encode64()
  end

  def encrypt(plaintext, secret_key) do
    with {:ok, secret_key} <- decode_key(secret_key) do
      iv = :crypto.strong_rand_bytes(@block_size)
      plaintext = pad(plaintext, @block_size)
      ciphertext = :crypto.crypto_one_time(:aes_128_cbc, secret_key, iv, plaintext, true)

      {:ok, Base.encode64(iv <> ciphertext)}
    end
  end

  def decrypt(ciphertext, secret_key) do
    with {:ok, secret_key} <- decode_key(secret_key),
         {:ok, <<iv::binary-@block_size, ciphertext::binary>>} <- Base.decode64(ciphertext) do
      plaintext =
        :crypto.crypto_one_time(:aes_128_cbc, secret_key, iv, ciphertext, false)
        |> unpad

      {:ok, plaintext}
    else
      {:error, _} = err -> err
      _ -> {:error, "Bad encrypted data"}
    end
  end

  defp pad(data, block_size) do
    to_add = block_size - rem(byte_size(data), block_size)
    data <> :binary.copy(<<to_add>>, to_add)
  end

  defp unpad(data) do
    to_remove = :binary.last(data)
    :binary.part(data, 0, byte_size(data) - to_remove)
  end
end

Let go throught the code

  def generate_secret do
    :crypto.strong_rand_bytes(@block_size)
    |> Base.encode64()
  end

This function generate a 16 bytes secret key and encode 64 so you can add it to config.

  • encrypt/2 function
 def encrypt(plaintext, secret_key) do
    # check the key size
    with {:ok, secret_key} <- decode_key(secret_key) do

      # random initial vector
      iv = :crypto.strong_rand_bytes(@block_size)
      # if length of text is not multiple of @block_size
      # we add more data until it meets condition
      plaintext = pad(plaintext, @block_size)
      # encrypt here
      ciphertext = :crypto.crypto_one_time(:aes_128_cbc, secret_key, iv, plaintext, true)

      {:ok, Base.encode64(iv <> ciphertext)}
    end
  end

This is the most important line

ciphertext = :crypto.crypto_one_time(:aes_128_cbc, secret_key, iv, plaintext, true)
  • iv is initial vector. We are encrypt data by block of 16 bytes, so we need intial vector to make sure that the output of blocks with same data are different.
  • The last parameter set to true to encrypt, set to false to decrypt data

And then we encode 64 to return a string. Here we concat iv and ciphertext so that we can extract iv to use for decryption

{:ok, Base.encode64(iv <> ciphertext)}
  • decrypt/2 function
def decrypt(ciphertext, secret_key) do
    # check the key
    with {:ok, secret_key} <- decode_key(secret_key),
         {:ok, <<iv::binary-@block_size, ciphertext::binary>>} <- Base.decode64(ciphertext) do
      plaintext =
        :crypto.crypto_one_time(:aes_128_cbc, secret_key, iv, ciphertext, false)
        |> unpad

      {:ok, plaintext}
    else
      {:error, _} = err -> err
      _ -> {:error, "Bad encrypted data"}
    end
  end

We extract iv and encrypted data from intput

{:ok, <<iv::binary-@block_size, ciphertext::binary>>} <- Base.decode64(ciphertext)

We use pattern matching to extract first 16 byte and assign to iv and remaining data to ciphertext. Then decrypt data

plaintext =
    :crypto.crypto_one_time(:aes_128_cbc, secret_key, iv, ciphertext, false)
    |> unpad

Similar to encrypt data, here we replace plaintext by ciphertext and last parameter is set to false instead. After data is decrypted, we need to remove padding data to get original data.

2. Build EncryptedText type

I define a type to store binary data, you can define a EncryptedMap to store map data. The most importants function are dump and load where we encrypt before persiting and decrypt after loading.

defmodule EncryptedText do
  use Ecto.Type

  # we store data as string
  def type, do: :string

  def cast(value) when is_binary(value) do
    {:ok, value}
  end
  def cast(_), do: :error

  def dump(nil), do: nil
  # encrypt data before persit to database
  def dump(data) when is_binary(data) do
    with {:ok, secret_key} <- Application.fetch_env(:myapp, :ecto_secret_key),
         {:ok, data} <- Crypto.encrypt(data, secret_key) do
      {:ok, data}
    else
      _ -> :error
    end
  end

  def dump(_), do: :error

  def load(nil), do: nil
  # decrypt data after loaded from database
  def load(data) when is_binary(data) do
    secret_key = Application.fetch_env!(:myapp, :ecto_secret_key)
    case Crypto.decrypt(data, secret_key) do
      {:error, _} -> :error
      ok -> ok
    end
  end

  def load(_), do: :error

  def embed_as(_), do: :dump
end

3. Use it in your schema

  • You must add secret key to your app config first.
config :myapp, :ecto_secret_key, "your key using Crypto.generate_secret"
  • Add to schema
schema "users" do
    field :name, :string
    ...
    field :secret, EncryptedText
    ...
end

Your data are safe now.

4. Conclusion

With Crypto data you can implement encrypted field for any type of data you want.

There is an issue when you want to change your secret key, you have to load your data row by row, decrypt and then encrypt with new key and update to database.

I found this article explain very well about crypto if you are interested https://www.thegreatcodeadventure.com/elixir-encryption-with-erlang-crypto/
Although she uses old crypto api so it will through some warning.

I implemented encrypted type for text and map for my company project here if you want to use it:

Github

Thanks for reading.


This content originally appeared on DEV Community and was authored by Dung Nguyen


Print Share Comment Cite Upload Translate Updates
APA

Dung Nguyen | Sciencx (2021-08-20T12:10:14+00:00) How to encrypt your data with Ecto. Retrieved from https://www.scien.cx/2021/08/20/how-to-encrypt-your-data-with-ecto/

MLA
" » How to encrypt your data with Ecto." Dung Nguyen | Sciencx - Friday August 20, 2021, https://www.scien.cx/2021/08/20/how-to-encrypt-your-data-with-ecto/
HARVARD
Dung Nguyen | Sciencx Friday August 20, 2021 » How to encrypt your data with Ecto., viewed ,<https://www.scien.cx/2021/08/20/how-to-encrypt-your-data-with-ecto/>
VANCOUVER
Dung Nguyen | Sciencx - » How to encrypt your data with Ecto. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2021/08/20/how-to-encrypt-your-data-with-ecto/
CHICAGO
" » How to encrypt your data with Ecto." Dung Nguyen | Sciencx - Accessed . https://www.scien.cx/2021/08/20/how-to-encrypt-your-data-with-ecto/
IEEE
" » How to encrypt your data with Ecto." Dung Nguyen | Sciencx [Online]. Available: https://www.scien.cx/2021/08/20/how-to-encrypt-your-data-with-ecto/. [Accessed: ]
rf:citation
» How to encrypt your data with Ecto | Dung Nguyen | Sciencx | https://www.scien.cx/2021/08/20/how-to-encrypt-your-data-with-ecto/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.