{"id":13507891,"url":"https://github.com/dwyl/phoenix-ecto-encryption-example","last_synced_at":"2025-03-30T09:33:09.419Z","repository":{"id":38206250,"uuid":"118827536","full_name":"dwyl/phoenix-ecto-encryption-example","owner":"dwyl","description":"🔐 A detailed example for how to encrypt data in an Elixir (Phoenix v1.7) App before inserting into a database using Ecto Types","archived":false,"fork":false,"pushed_at":"2025-03-09T09:08:20.000Z","size":534,"stargazers_count":282,"open_issues_count":8,"forks_count":20,"subscribers_count":17,"default_branch":"main","last_synced_at":"2025-03-09T09:27:21.854Z","etag":null,"topics":["aes","aes-256","aes-gcm","argon2","custom","database","ecto","elixir","encryption","learn","phoenix","tutorial","types"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dwyl.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-01-24T21:59:28.000Z","updated_at":"2025-03-09T09:08:17.000Z","dependencies_parsed_at":"2023-11-20T23:31:11.503Z","dependency_job_id":"ab8039a9-40c1-4c3f-b3a3-a378434c6da1","html_url":"https://github.com/dwyl/phoenix-ecto-encryption-example","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dwyl%2Fphoenix-ecto-encryption-example","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dwyl%2Fphoenix-ecto-encryption-example/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dwyl%2Fphoenix-ecto-encryption-example/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dwyl%2Fphoenix-ecto-encryption-example/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dwyl","download_url":"https://codeload.github.com/dwyl/phoenix-ecto-encryption-example/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246301963,"owners_count":20755512,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aes","aes-256","aes-gcm","argon2","custom","database","ecto","elixir","encryption","learn","phoenix","tutorial","types"],"created_at":"2024-08-01T02:00:42.148Z","updated_at":"2025-03-30T09:33:09.134Z","avatar_url":"https://github.com/dwyl.png","language":"Elixir","readme":"\n\u003cdiv align=\"center\"\u003e\n\n# Phoenix Ecto Encryption Example\n\n\u003cimg src=\"https://user-images.githubusercontent.com/194400/36345569-f60382de-1424-11e8-93e9-74ed7eaceb71.jpg\" \n  width=\"700\" alt=\"data encrypted\" /\u003e\n\n[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/dwyl/phoenix-ecto-encryption-example/ci.yml?label=build\u0026style=flat-square\u0026branch=main)](https://github.com/dwyl/phoenix-ecto-encryption-example/actions)\n[![codecov.io](https://img.shields.io/codecov/c/github/dwyl/phoenix-ecto-encryption-example/master.svg?style=flat-square)](https://codecov.io/github/dwyl/phoenix-ecto-encryption-example?branch=master)\n[![Hex.pm](https://img.shields.io/hexpm/v/phoenix?color=brightgreen\u0026style=flat-square)](https://hex.pm/packages/phoenix)\n[![docs](https://img.shields.io/badge/docs-maintained-brightgreen?style=flat-square)](https://hexdocs.pm/fields/api-reference.html)\n[![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat-square)](https://github.com/dwyl/phoenix-ecto-encryption-example/issues)\n[![HitCount](https://hits.dwyl.com/dwyl/phoenix-ecto-encryption-example.svg)](https://hits.dwyl.com/dwyl/phoenix-ecto-encryption-example)\n\n\u003c/div\u003e\n\n\u003cbr /\u003e\n\n💡 **Note**: we wrote this example/tutorial\nto _understand_ how to do field-level encryption\nfrom **first principals**. \u003cbr /\u003e\nOnce we solved the problem,\nwe built a library to streamline it:\n[**`fields`**](https://github.com/dwyl/fields).\u003cbr /\u003e\nWe still recommend going through this example,\nbut if you just want \nto get on with building your **`Phoenix` App**,\nuse\n[**`fields`**](https://github.com/dwyl/fields).\n\n\u003cbr /\u003e\n\n- [Phoenix Ecto Encryption Example](#phoenix-ecto-encryption-example)\n- [Why?](#why)\n- [What?](#what)\n  - [Technical Overview](#technical-overview)\n    - [OWASP Cryptographic Rules?](#owasp-cryptographic-rules)\n- [Who?](#who)\n  - [Prerequisites?](#prerequisites)\n    - [Crypto Knowledge?](#crypto-knowledge)\n    - [Time Requirement?](#time-requirement)\n- [How?](#how)\n  - [1. Create the `encryption` App](#1-create-the-encryption-app)\n  - [2. Create the `user` Schema (_Database Table_)](#2-create-the-user-schema-database-table)\n  - [3. Define The 6 Functions](#3-define-the-6-functions)\n    - [3.1 Encrypt](#31-encrypt)\n      - [Test the `encrypt/1` Function](#test-the-encrypt1-function)\n    - [3.2 Decrypt](#32-decrypt)\n      - [Test the `decrypt/1` Function](#test-the-decrypt1-function)\n    - [3.3 Key rotation](#33-key-rotation)\n    - [3.4  `ENCRYPTION_KEYS` Environment Variable](#34--encryption_keys-environment-variable)\n      - [Test the `get_key/0` and `get_key/1` Functions?](#test-the-get_key0-and-get_key1-functions)\n  - [4. Hash _Email Address_](#4-hash-email-address)\n    - [4.1 Generate the `SECRET_KEY_BASE`](#41-generate-the-secret_key_base)\n  - [5. _Create_ and use `HashField` Custom Ecto Type](#5-create-and-use-hashfield-custom-ecto-type)\n    - [`type/0`](#type0)\n    - [`cast/1`](#cast1)\n    - [`dump/1`](#dump1)\n    - [`load/1`](#load1)\n    - [`embed_as/1`](#embed_as1)\n    - [`equal?/2`](#equal2)\n  - [6. Create and user Hash _Password_ Custom Ecto type](#6-create-and-user-hash-password-custom-ecto-type)\n    - [Add the `argon2` Dependency](#add-the-argon2-dependency)\n    - [6.1 Define the `hash_password/1` Function](#61-define-the-hash_password1-function)\n      - [6.1.1 Test the `hash_password/1` Function?](#611-test-the-hash_password1-function)\n    - [6.2 _Verify_ Password](#62-verify-password)\n        - [Test for `verify_password/2`](#test-for-verify_password2)\n  - [7. _Create_ and use `EncryptedField` Custom Ecto Type](#7-create-and-use-encryptedfield-custom-ecto-type)\n    - [`type/0`](#type0-1)\n    - [`cast/1`](#cast1-1)\n    - [`dump/1`](#dump1-1)\n    - [`load/1`](#load1-1)\n    - [`embed_as/1`](#embed_as1-1)\n    - [`equal?/2`](#equal2-1)\n  - [8. Ensure All Tests Pass](#8-ensure-all-tests-pass)\n- [Conclusion](#conclusion)\n  - [How To Generate AES Encryption Keys?](#how-to-generate-aes-encryption-keys)\n- [Useful Links, FAQ \\\u0026 Background Reading](#useful-links-faq--background-reading)\n  - [Understanding Advanced Encryption Standard (AES)](#understanding-advanced-encryption-standard-aes)\n  - [Running a Single Test](#running-a-single-test)\n  - [Ecto Validation Error format](#ecto-validation-error-format)\n- [Stuck / Need Help?](#stuck--need-help)\n- [Credits](#credits)\n\n# Why?\n\n**Encrypting User/Personal data** stored by your Web App is ***essential***\nfor security/privacy.\n\n\u003e If your app offers any **personalised content** or interaction\nthat depends on \"**login**\", it is **_storing_ personal data**\n(_by definition_).\nYou might be tempted to think that\ndata is \"safe\" in a database, but it's _not_.\nThere is an entire (\"dark\") army/industry of people\n([_cybercriminals_](https://en.wikipedia.org/wiki/Cybercrime))\nwho target websites/apps attempting to \"steal\" data by compromising databases.\nAll the time you spend _building_ your app,\nthey spend trying to \"_break_\" apps like yours.\nDon't let the people using your app\nbe the victims of identity theft,\nprotect their personal data!\n(_it's both the \"**right**\" **thing to do** and the\n [**law**](https://github.com/dwyl/learn-security/#gdpr) ..._)\n\n# What?\n\nThis example/tutorial is intended as a _comprehensive_ answer\nto the question:\n\n\u003e [\"_**How to Encrypt/Decrypt Sensitive Data** in `Elixir` Apps\n**Before** Inserting (Saving) it into the\nDatabase?_\"](https://github.com/dwyl/learn-elixir/issues/80)\n\n## Technical Overview\n\nWe are _not_ \"re-inventing encryption\"\nor using our \"own algorithm\"\n_everyone_ knows that's a \"_**bad** idea_\":\nhttps://security.stackexchange.com/questions/18197/why-shouldnt-we-roll-our-own\n\u003cbr /\u003e\nWe are _following_ a **_battle-tested_ industry-standard** approach\nand applying it to our Elixir/Phoenix App. \u003cbr /\u003e\nWe are using:\n\n+ **Advanced Encryption Standard** (**AES**) to encrypt sensitive data.\n  + **Galois/Counter Mode**\nfor _symmetric_ key cryptographic block ciphers:\nhttps://en.wikipedia.org/wiki/Galois/Counter_Mode\nrecommended by many security and cryptography practitioners including\n [Matthew Green](https://blog.cryptographyengineering.com/),\n [Niels Ferguson](https://en.wikipedia.org/wiki/Niels_Ferguson)\n and [Bruce Schneier](https://www.schneier.com/blog/about/)\n  + \"Under the hood\" we are using Erlang's\n[crypto](https://erlang.org/doc/man/crypto.html) library\n_specifically_ AES with **256 bit keys** \u003cbr /\u003e\n(_the same as AWS or Google's KMS service_)\nsee: https://erlang.org/doc/man/crypto.html#block_encrypt-4\n+ Password \"hashing\" using the **Argon2**\nkey derivation function (KDF): https://en.wikipedia.org/wiki/Argon2 \u003cbr /\u003e\n_specifically_ the Elixir implementation of `argon2`\nwritten by David Whitlock: https://github.com/riverrun/argon2_elixir\nwhich in turn uses the **C** \"_reference implementation_\"\nas a \"Git Submodule\".\n\n\u003e `¯\\_(ツ)_/¯...?` Don't be \"put off\" if any of these terms/algorithms\nare _unfamiliar_ to you; \u003cbr /\u003e\nthis example is \"***step-by-step***\"\nand we are _happy_ to answer/clarify\n_any_ (_relevant and specific_) questions you have!\n\n### OWASP Cryptographic Rules?\n\nThis example/tutorial follows\nthe Open Web Application Security Project (**OWASP**)\nCryptographic and Password rules:\n+ [x] Use \"***strong approved Authenticated Encryption***\"\nbased on an ***AES algorithm***.\n  + [x] Use GCM mode of operation for symmetric key cryptographic block ciphers.\n  + [x] Keys used for encryption must be rotated at least annually.\n+ [x] Only use approved public algorithm **SHA-256** or better for hashing.\n+ [x] **Argon2** is the winner of the password hashing competition\nand should be your ***first choice*** for **_new_ applications**.\n\nSee:\n+ [https://www.owasp.org/index.php/Cryptographic_Storage_Cheat_Sheet](https://www.owasp.org/index.php/Cryptographic_Storage_Cheat_Sheet#Rule_-_Use_strong_approved_Authenticated_Encryption)\n+ https://www.owasp.org/index.php/Password_Storage_Cheat_Sheet\n\n\n# Who?\n\nThis example/tutorial is for _any_ developer\n(_or technical decision maker / \"application architect\"_) \u003cbr /\u003e\nwho takes personal data protection _seriously_\nand wants a robust/reliable and \"transparent\" way \u003cbr /\u003e\nof _encrypting data_ `before` storing it,\nand _decrypting_ when it is queried.\n\n## Prerequisites?\n\n+ Basic **`Elixir`** syntax knowledge: https://github.com/dwyl/learn-elixir\n+ Familiarity with the **`Phoenix`** framework: https://github.com/dwyl/learn-phoenix-framework\n+ Basic understanding of **`Ecto`**\n(_the module used to interface with databases in elixir/phoenix_)\n\n\u003e If you are totally `new` to (_or \"rusty\" on_)\nElixir, Phoenix or Ecto,\nwe recommend going through our **Phoenix Chat Example**\n(_Beginner's Tutorial_) _first_:\nhttps://github.com/dwyl/phoenix-chat-example\n\n### Crypto Knowledge?\n\nYou will _not_ need any \"advanced\" mathematical knowledge;\nwe are _not_ \"inventing\" our own encryption or\ngoing into the \"internals\" of any cyphers/algorithms/schemes. \u003cbr /\u003e\n\n**You do _not_ need to _understand_\nhow the encryption/hashing algorithms _work_,** \u003cbr /\u003e\nbut it is _useful_ to **know the _difference_** between\n[encryption](https://en.wikipedia.org/wiki/Encryption)\nvs.\n[hashing](https://en.wikipedia.org/wiki/Hash_function)\nand\n[plaintext](https://en.wikipedia.org/wiki/Plaintext)\nvs.\n[ciphertext](https://en.wikipedia.org/wiki/Ciphertext).\n\nThe fact that the example/tutorial follows _all_ OWASP crypto/hashing rules\n(_see:_\n[\"OWASP Cryptographic Rules?\"](https://github.com/dwyl/phoenix-ecto-encryption-example#owasp-cryptographic-rules)\n_section above_),\nshould be \"enough\" for _most_ people who just want to focus\non building their app and don't want to\n[\"_go down the rabbit hole_\"](https://youtu.be/6IDT3MpSCKI?t=1m2s). \u003cbr /\u003e\n\n_However_ ... We have included 30+ links in the\n[\"Useful Links\"](https://github.com/dwyl/phoenix-ecto-encryption-example#useful-links-faq--background-reading)\nsection at the _end_ of this readme.\nThe list includes several common questions (_and **answers**_)\nso if you are _curious_, you can [learn](https://youtu.be/hOZnP4dZYK0).\n\n\u003e _**Note**: in the @dwyl Library we have_\nhttps://www.schneier.com/books/applied_cryptography\n_So, if you're **really curious** let us know!_\n\n### Time Requirement?\n\nSimply _reading_ (\"_skimming_\") through this example will\nonly take **15 minutes**. \u003cbr /\u003e\n_Following_ the examples on your computer (_to fully understand it_)\nwill take around **1 hour** \u003cbr /\u003e\n(_including reading a few of the links_).\n\n\u003e _**Invest** the time **up-front** to **avoid** on the **embarrassment**\nand [**fines**](https://www.itgovernance.co.uk/dpa-and-gdpr-penalties)\nof a data breach_.\n\n\n# How?\n\nThese are \"step-by-step\" instructions,\ndon't skip any step(s).\n\n## 1. Create the `encryption` App\n\nIn your Terminal,\n***create*** a `new` Phoenix application called \"encryption\":\n```sh\nmix phx.new encryption\n```\n\nWhen you see `Fetch and install dependencies? [Yn]`, \u003cbr /\u003e\ntype `y` and press the `[Enter]` key\nto download and install the dependencies. \u003cbr /\u003e\nYou should see following in your terminal:\n\n```sh\n* running mix deps.get\n* running mix deps.compile\n* running cd assets \u0026\u0026 npm install \u0026\u0026 node node_modules/webpack/bin/webpack.js --mode development\n\nWe are almost there! The following steps are missing:\n\n    $ cd encryption\n\nThen configure your database in config/dev.exs and run:\n\n    $ mix ecto.create\n\nStart your Phoenix app with:\n\n    $ mix phx.server\n\nYou can also run your app inside IEx (Interactive Elixir) as:\n\n    $ iex -S mix phx.server\n```\nFollow the _first_ instruction\n**change** into the `encryption` directory: \u003cbr /\u003e\n```sh\ncd encryption\n```\n\nNext ***create*** the database for the App using the command:\n```sh\nmix ecto.create\n```\n\nYou should see the following output:\n```sh\nCompiling 13 files (.ex)\nGenerated encryption app\nThe database for Encryption.Repo has been created\n```\n\n\n\n## 2. Create the `user` Schema (_Database Table_)\n\nIn our _example_ `user` database table,\nwe are going to store 3 (_primary_) pieces of data.\n+ `name`: the person's name (_encrypted_)\n+ `email`: their email address (_encrypted_)\n+ `password_hash`: the hashed password (_so the person can login_)\n\nIn _addition_ to the 3 \"_primary_\" fields,\nwe need _**one** more field_ to store \"metadata\":\n+ `email_hash`: so we can check (\"lookup\")\nif an email address is in the database\n_without_ having to _decrypt_ the email(s) stored in the DB.\n\nCreate the `user` schema using the following generator command:\n```sh\nmix phx.gen.schema User users email:binary email_hash:binary name:binary password_hash:binary\n```\n\n![phx.gen.schema](https://user-images.githubusercontent.com/194400/35360796-dc4507cc-0156-11e8-9cf1-7f4005e5ed34.png)\n\n\nThe _reason_ we are creating the encrypted/hashed fields as `:binary`\nis that the _data_ stored in them will be _encrypted_\nand `:binary` is the _most efficient_ Ecto/SQL data type\nfor storing encrypted data;\nstoring it as a `String` would take up more bytes\nfor the _same_ data.\ni.e. _wasteful_ without any _benefit_ to security or performance. \u003cbr /\u003e\nsee:\nhttps://dba.stackexchange.com/questions/56934/what-is-the-best-way-to-store-a-lot-of-user-encrypted-data\n\u003cbr /\u003e\nand: https://elixir-lang.org/getting-started/binaries-strings-and-char-lists.html\n\nNext we need to update our newly created migration file. Open\n`priv/repo/migrations/{timestamp}_create_users.exs`.\n\n\u003e Your migration file will\nhave a slightly different name to ours as migration files are named with a\ntimestamp when they are created but it will be in the same location.\n\nUpdate the file ***from***:\n```elixir\ndefmodule Encryption.Repo.Migrations.CreateUsers do\n  use Ecto.Migration\n\n  def change do\n    create table(:users) do\n      add(:email, :binary)\n      add(:email_hash, :binary)\n      add(:name, :binary)\n      add(:password_hash, :binary)\n\n      timestamps()\n    end\n  end\nend\n```\n\n**To**\n\n```elixir\ndefmodule Encryption.Repo.Migrations.CreateUsers do\n  use Ecto.Migration\n\n  def change do\n    create table(:users) do\n      add(:email, :binary)\n      add(:email_hash, :binary)\n      add(:name, :binary)\n      add(:password_hash, :binary)\n\n      timestamps()\n    end\n\n    create(unique_index(:users, [:email_hash]))\n  end\nend\n```\n\nThe newly added line ensures that we will never be allowed to enter duplicate\n`email_hash` values into our database.\n\nRun the \"migration\" task to create the tables in the Database:\n```sh\nmix ecto.migrate\n```\n\nRunning the `mix ecto.migrate` command will create the\n`users` table in your `encryption_dev` database. \u003cbr /\u003e\nYou can _view_ this (_empty_) table in a PostgreSQL GUI. Here is a screenshot\nfrom **pgAdmin**: \u003cbr /\u003e\n![elixir-encryption-pgadmin-user-table](https://user-images.githubusercontent.com/1466/82065993-1b619c80-96cf-11ea-898e-8cfd0160346e.png)\n\n\n## 3. Define The 6 Functions\n\nWe need 6 functions for encrypting, decrypting, hashing and verifying\nthe data we will be storing:\n\n1. **Encrypt** - to encrypt any personal data we want to store in the database.\n2. **Decrypt** - decrypt any data that needs to be viewed.\n3. **Get Key** - get the _latest_ encryption/decryption key\n(_or a **specific** older key where data was encrypted with a different key_)\n4. **Hash Email** (_deterministic \u0026 **fast**_) - so that we can \"lookup\"\nan email without \"decrypting\".\nThe hash of an email address should _always_ be the ***same***.\n5. **Hash Password** (_pseudorandom \u0026 **slow**_) - the output of the hash\nshould _always_ be ***different*** and relatively **slow** to compute.\n6. **Verify Password** - check a password against the stored `password_hash`\nto confirm that the person \"logging-in\" has the _correct_ password.\n\nThe next 6 sections of the example/tutorial will walk through\nthe creation of (_and testing_) these functions.\n\n\u003e _**Note**: If you have **any questions** on these functions_,\n***please ask***: \u003cbr /\u003e\n[github.com/dwyl/**phoenix-ecto-encryption-example/issues**](https://github.com/nelsonic/phoenix-ecto-encryption-example/issues)\n\n\n### 3.1 Encrypt\n\nCreate a file called `lib/encryption/aes.ex` and copy-paste (_or hand-write_)\nthe following code:\n\n```elixir\ndefmodule Encryption.AES do\n  @aad \"AES256GCM\" # Use AES 256 Bit Keys for Encryption.\n\n  def encrypt(plaintext) do\n    iv = :crypto.strong_rand_bytes(16) # create random Initialisation Vector\n    key = get_key()    # get the *latest* key in the list of encryption keys\n    {ciphertext, tag} =\n      :crypto.crypto_one_time_aead(:aes_256_gcm, key, iv, to_string(plaintext), @aad, true)\n    iv \u003c\u003e tag \u003c\u003e ciphertext # \"return\" iv with the cipher tag \u0026 ciphertext\n  end\n\n  defp get_key do # this is a \"dummy function\" we will update it in step 3.3\n    \u003c\u003c109, 182, 30, 109, 203, 207, 35, 144, 228, 164, 106, 244, 38, 242,\n    106, 19, 58, 59, 238, 69, 2, 20, 34, 252, 122, 232, 110, 145, 54,\n    241, 65, 16\u003e\u003e # return a random 32 Byte / 128 bit binary to use as key.\n  end\nend\n```\n\nThe `encrypt/1` function for encrypting `plaintext` into `ciphertext`\nis quite simple; (_the \"body\" is only 4 lines_).\u003cbr /\u003e\n\nLet's \"step through\" these lines one at a time:\n\n+ `encrypt/1` accepts one argument; the `plaintext` to be encrypted.\n+ First we create a \"**strong**\" _random_\n[***initialization vector***](https://en.wikipedia.org/wiki/Initialization_vector)\n(IV) of **16 bytes** (***128 bits***)\nusing the Erlang's crypto library `strong_rand_bytes` function:\nhttps://erlang.org/doc/man/crypto.html#strong_rand_bytes-1\nThe \"IV\" ensures that each time a string/block of text/data is encrypted,\nthe `ciphertext` is _different_.\n\n\u003e Having **different** `ciphertext` each time `plaintext` is encrypted\nis _essential_ for\n[\"semantic security\"](https://en.wikipedia.org/wiki/Semantic_security)\nwhereby repeated use of the _same encryption key and algorithm_\ndoes not allow an \"attacker\" to infer relationships\nbetween segments of the encrypted message.\n\u003e [Cryptanalysis](https://en.wikipedia.org/wiki/Cryptanalysis) techniques\nare _well_ \"beyond scope\" for this example/tutorial,\nbut we _highly_ encourage to check-out the \"Background Reading\" links\nat the end and read up on the subject for deeper understanding.\n\n+ Next we use the `get_key/0` function\nto retrieve the _latest_ encryption key\nso we can use it to `encrypt` the `plaintext`\n(_the \"real\"_ `get_key/0` _is defined below in section 3.3_).\n\n+ Then we use the Erlang `block_encrypt` function\nto encrypt the `plaintext`. \u003cbr /\u003e\nUsing `:aes_gcm` (\"_Advanced Encryption Standard Galois Counter Mode_\"):\n  + `@aad` is a \"module attribute\" (_Elixir's equivalent of a \"constant\"_)\n  is defined in `aes.ex` as `@aad \"AES256GCM\"` \u003cbr /\u003e\n  this simply defines the encryption mode we are using which,\n  if you break down the code into 3 parts:\n    + AES = Advanced Encryption Standard.\n    + 256 = \"256 Bit Key\"\n    + GCM = \"Galois Counter Mode\"\n\n+ Finally we \"return\" the `iv` with the `ciphertag` \u0026 `ciphertext`,\nthis is what we store in the database.\nIncluding the IV and ciphertag is _essential_ for allowing decryption,\nwithout these two pieces of data, we would not be able to \"reverse\" the process.\n\n\u003e _**Note**: in addition to this_ `encrypt/1` _function,\nwe have defined an_ `encrypt/2` _\"sister\" function which accepts\na **specific** (encryption)_ `key_id` _so that we can use the desired\nencryption key for encrypting a block of text.\nFor the purposes of this example/tutorial,\nit's **not strictly necessary**,\nbut it is included for \"completeness\"_.\n\n#### Test the `encrypt/1` Function\n\n\nCreate a file called `test/lib/aes_test.exs` and _copy-paste_\nthe following code into it:\n\n```elixir\ndefmodule Encryption.AESTest do\n  use ExUnit.Case\n  alias Encryption.AES\n\n  test \".encrypt includes the random IV in the value\" do\n    \u003c\u003civ::binary-16, ciphertext::binary\u003e\u003e = AES.encrypt(\"hello\")\n\n    assert String.length(iv) != 0\n    assert String.length(ciphertext) != 0\n    assert is_binary(ciphertext)\n  end\n\n  test \".encrypt does not produce the same ciphertext twice\" do\n    assert AES.encrypt(\"hello\") != AES.encrypt(\"hello\")\n  end\nend\n```\n\nRun these two tests by running the following command:\n```sh\nmix test test/lib/aes_test.exs\n```\n\n\n\u003e The full function definitions for AES `encrypt/1` \u0026 `encrypt/2` are in:\n[`lib/encryption/aes.ex`](https://github.com/dwyl/phoenix-ecto-encryption-example/blob/master/lib/encryption/aes.ex) \u003cbr /\u003e\n\u003e And tests are in:\n[`test/lib/aes_test.exs`](https://github.com/dwyl/phoenix-ecto-encryption-example/blob/master/test/lib/aes_test.exs)\n\n\n### 3.2 Decrypt\n\nThe `decrypt` function _reverses_ the work done by `encrypt`;\nit accepts a \"blob\" of `ciphertext` (_which as you may recall_),\nhas the IV and cypher tag prepended to it, and returns the original `plaintext`.\n\nIn the `lib/encryption/aes.ex` file, copy-paste (_or hand-write_)\nthe following `decrypt/1` function definition:\n\n```elixir\ndef decrypt(ciphertext) do\n  \u003c\u003civ::binary-16, tag::binary-16, ciphertext::binary\u003e\u003e =\n    ciphertext\n  :crypto.crypto_one_time_aead(:aes_256_gcm, get_key(), iv, ciphertext, @aad, tag, false)\nend\n```\n\nThe fist step (line) is to \"split\" the IV from the `ciphertext`\nusing Elixir's binary pattern matching.\n\n\u003e If you are unfamiliar with Elixir binary pattern matching syntax:\n`\u003c\u003civ::binary-16, tag::binary-16, ciphertext::binary\u003e\u003e`\nread the following guide:\nhttps://elixir-lang.org/getting-started/binaries-strings-and-char-lists.html\n\nThe \n`:crypto.crypto_one_time_aead(:aes_256_gcm, get_key(key_id), iv, ciphertext, @aad, tag, false)`\nline is the _very similar_ to the `encrypt` function.\n\nThe `ciphertext` is decrypted using\n[`block_decrypt/4`](https://erlang.org/doc/man/crypto.html#block_decrypt-4)\npassing in the following parameters:\n+ `:aes_256_gcm` = encyrption algorithm\n+ `get_key(key_id)` =  get the encryption key used to `encrypt` the `plaintext`\n+ `iv` = the original Initialisation Vector used to `encrypt` the `plaintext`\n+ `{@aad, ciphertext, tag}` = a Tuple with the encryption \"mode\",\n`ciphertext` and the `tag` that was originally used to encrypt the `ciphertext`.\n\nFinally return _just_ the original `plaintext`.\n\n\u003e _**Note**: as above with the_ `encrypt/2` _function,\nwe have defined an_ `decrypt/2` _\"sister\" function which accepts\na **specific** (encryption)_ `key_id` _so that we can use the desired\nencryption key for decrypting the_ `ciphertext`.\n_For the purposes of this example/tutorial,\nit's **not strictly necessary**,\nbut it is included for \"completeness\"_.\n\n#### Test the `decrypt/1` Function\n\nIn the `test/lib/aes_test.exs` add the following test:\n\n```elixir\ntest \"decrypt/1 ciphertext that was encrypted with default key\" do\n  plaintext = \"hello\" |\u003e AES.encrypt |\u003e AES.decrypt()\n  assert plaintext == \"hello\"\nend\n```\nRe-run the tests `mix test test/lib/aes_test.exs` and confirm they pass.\n\n\u003e The full `encrypt` \u0026 `decrypt` function definitions with `@doc` comments\nare in:\n[`lib/encryption/aes.ex`](https://github.com/dwyl/phoenix-ecto-encryption-example/blob/master/lib/encryption/aes.ex)\n\u003cbr /\u003e\n\u003e And tests are in:\n[`test/lib/aes_test.exs`](https://github.com/dwyl/phoenix-ecto-encryption-example/blob/master/test/lib/aes_test.exs)\n\n### 3.3 Key rotation\n\nKey rotation is a \"**best practice**\"\nthat **limits** the amount of data an \"attacker\" can decrypt if the database were ever \"compromised\"\n_(provided we keep the encryption keys safe that is!)_ A really good guide to this is: https://cloud.google.com/kms/docs/key-rotation.\n\nFor this reason we want to 'store' a `key_id`. The `key_id` indicates which **encryption key** was used to encrypt the data. Besides the IV and ciphertag, the key_id is also _essential_ for allowing decryption, so we change the encrypt/1 function to preserve the key_id as well\n\n\n```elixir\ndefmodule Encryption.AES do\n  @aad \"AES256GCM\" # Use AES 256 Bit Keys for Encryption.\n\n  def encrypt(plaintext) do\n    iv = :crypto.strong_rand_bytes(16)\n    # get latest key\n    key = get_key()\n    # get latest ID;\n    key_id = get_key_id()\n    # {ciphertext, tag} = :crypto.block_encrypt(:aes_gcm, key, iv, {@aad, plaintext, 16})\n    {ciphertext, tag} = :crypto.block_encrypt(:aes_gcm, key, iv, {@aad, to_string(plaintext), 16})\n    iv \u003c\u003e tag \u003c\u003e \u003c\u003ckey_id::unsigned-big-integer-32\u003e\u003e \u003c\u003e ciphertext\n  end\n\n  defp get_key do\n    get_key_id() |\u003e get_key\n  end\n\n  defp get_key(key_id) do\n    encryption_keys() |\u003e Enum.at(key_id)\n  end\n\n  defp get_key_id do\n    Enum.count(encryption_keys()) - 1\n  end\n\n  defp encryption_keys do\n    Application.get_env(:encryption, Encryption.AES)[:keys]\n  end\nend\n```\n\n\u003e _For the **complete** file containing these functions see_:\n[`lib/encryption/aes.ex`](https://github.com/dwyl/phoenix-ecto-encryption-example/blob/master/lib/encryption/aes.ex)\n\n\nFor this example/demo we are using **two** encryption keys which are kept as an application environment variable. The values of the encryptions keys are associated with the key Encryption.AES. During the encryption we are by default always using the latest (most recent) encryption key (get_key/0) and the corresponding key_id is fetched by get_key_id/0 which becomes part of the ciphertext.\n\nWith decrypting we now pattern match the associated key_id from the ciphertext in order to be able to decrypt with the correct encryption key.\n\n```elixir\n  def decrypt(ciphertext) do\n    \u003c\u003civ::binary-16, tag::binary-16, key_id::unsigned-big-integer-32, ciphertext::binary\u003e\u003e =\n      ciphertext\n\n    :crypto.block_decrypt(:aes_gcm, get_key(key_id), iv, {@aad, ciphertext, tag})\n  end\n```\n\nSo we defined the `get_key` _twice_ in `lib/encryption/aes.ex`\nas per Erlang/Elixir standard,\nonce for each [\"arity\"](https://en.wikipedia.org/wiki/Arity)\nor number of \"arguments\".\nIn the first case `get_key/0` _assumes_ you want the _latest_ Encryption Key.\nThe second case `get_key/1` lets you supply the `key_id` to be \"looked up\":\n\n\nBoth versions of `get_key` use encryption_keys/0 function to call the `Application.get_env` function:\n`Application.get_env(:encryption, Encryption.AES)[:keys]` _specifically_.\nFor this to work we need to define the keys as an Environment Variable\nand make it available to our App in `config.exs`.\n\n\n### 3.4  `ENCRYPTION_KEYS` Environment Variable\n\nIn order for our `get_key/0` and `get_key/1` functions to _work_,\nit needs to be able to \"read\" the encryption keys.\n\nWe need to \"export\" an Environment Variable\ncontaining a (_comma-separated_) list of (_one or more_)\nencryption key(s).\n\n_Copy-paste_ (_and run_) the following command in your terminal:\n\n```elixir\necho \"export ENCRYPTION_KEYS='nMdayQpR0aoasLaq1g94FLba+A+wB44JLko47sVQXMg=,L+ZVX8iheoqgqb22mUpATmMDsvVGtafoAeb0KN5uWf0='\" \u003e\u003e .env \u0026\u0026 echo \".env\" \u003e\u003e .gitignore\n```\n\n\u003e For _now_, copy paste this command exactly as it is.\u003cbr /\u003e\n\u003e When you are deploying your own App,\n\u003e generate your own AES encryption key(s)\n\u003e see:\n[How To Generate AES Encryption Keys?](https://github.com/dwyl/phoenix-ecto-encryption-example#how-to-generate-aes-encryption-keys)\n\u003e section below for how to do this. \u003cbr /\u003e\n\n\u003e _**Note**: there are **two** encryption keys separated by a comma.\nThis is to **demonstrate** that it's **possible** to use **multiple keys**._\n\n\u003e _We prefer to store our Encryption Keys as\n**Environment Variables** this is consistent with the \"12 Factor App\"\nbest practice:_ https://en.wikipedia.org/wiki/Twelve-Factor_App_methodology\n\nUpdate the `config/config.exs` to load the environment variables from the `.env`\n file into the application. Add the following code your config file just above\n `import_config \"#{Mix.env()}.exs\"`:\n\n```elixir\n# run shell command to \"source .env\" to load the environment variables.\ntry do                                     # wrap in \"try do\"\n  File.stream!(\"./.env\")                   # in case .env file does not exist.\n    |\u003e Stream.map(\u0026String.trim_trailing/1) # remove excess whitespace\n    |\u003e Enum.each(fn line -\u003e line           # loop through each line\n      |\u003e String.replace(\"export \", \"\")     # remove \"export\" from line\n      |\u003e String.split(\"=\", parts: 2)       # split on *first* \"=\" (equals sign)\n      |\u003e Enum.reduce(fn(value, key) -\u003e     # stackoverflow.com/q/33055834/1148249\n        System.put_env(key, value)         # set each environment variable\n      end)\n    end)\nrescue\n  _ -\u003e IO.puts \"no .env file found!\"\nend\n\n# Set the Encryption Keys as an \"Application Variable\" accessible in aes.ex\nconfig :encryption, Encryption.AES,\n  keys: System.get_env(\"ENCRYPTION_KEYS\") # get the ENCRYPTION_KEYS env variable\n    |\u003e String.replace(\"'\", \"\")  # remove single-quotes around key list in .env\n    |\u003e String.split(\",\")        # split the CSV list of keys\n    |\u003e Enum.map(fn key -\u003e :base64.decode(key) end) # decode the key.\n```\n\n#### Test the `get_key/0` and `get_key/1` Functions?\n\nGiven that `get_key/0` and `get_key/1` are _both_ `defp` (_i.e. \"private\"_)\nthey are not \"exported\" with the AES module and therefore cannot be _invoked_\noutside of the AES module.\n\nThe `get_key/0` and `get_key/1` are _invoked_ by `encrypt/1` and `decrypt/1`\nand thus provided these (public) latter functions\nare tested adequately, the \"private\" functions will be too.\n\nRe-run the tests `mix test test/lib/aes_test.exs` and confirm they _still_ pass.\n\nWe also define a test in order to verify the working of key rotation. We add a new encryption key and assert (and make sure) that an encrypted value with an older encryption key will still be decrypted correctly.\n\n```elixir\n  test \"can still decrypt the value after adding a new encryption key\" do\n    encrypted_value = \"hello\" |\u003e AES.encrypt()\n\n    original_keys = Application.get_env(:encryption, Encryption.AES)[:keys]\n\n    # add a new key\n    Application.put_env(:encryption, Encryption.AES,\n      keys: original_keys ++ [:crypto.strong_rand_bytes(32)]\n    )\n\n    assert \"hello\" == encrypted_value |\u003e AES.decrypt()\n\n    # rollback to the original keys\n    Application.put_env(:encryption, Encryption.AES, keys: original_keys)\n  end\n\n```\n\n\u003e The full `encrypt` \u0026 `decrypt` function definitions with `@doc` comments\nare in:\n[`lib/encryption/aes.ex`](https://github.com/dwyl/phoenix-ecto-encryption-example/blob/master/lib/encryption/aes.ex)\n\u003e And tests are in:\n[`test/lib/aes_test.exs`](https://github.com/dwyl/phoenix-ecto-encryption-example/blob/master/test/lib/aes_test.exs)\n\n\n\n## 4. Hash _Email Address_\n\n\nThe idea behind _hashing_ email addresses is to\nallow us to perform a _lookup_ (_in the database_) to check if the\nemail has _already_ been registered/used for app/system.\n\nImagine that `alex@example.com` has previously used your app.\nThe `SHA256` hash (_encoded as [base64](https://hexdocs.pm/elixir/Base.html)_)\nis: `\"bbYebcvPI5DkpGr0JvJqEzo77kUCFCL8euhukTbxQRA=\"`\n\n`try` it for _yourself_ in `iex`:\n```elixir\niex(1)\u003e email = \"alex@example.com\"\n\"alex@example.com\"\niex(2)\u003e email_hash = :crypto.hash(:sha256, email) |\u003e Base.encode64\n\"bbYebcvPI5DkpGr0JvJqEzo77kUCFCL8euhukTbxQRA=\"\n```\n\nIf we store the `email_hash` in the database,\nwhen `Alex` wants to _log-in_ to the App/System,\nwe simply perform a \"lookup\" in the `users` table:\n\n```elixir\nhash  = :crypto.hash(:sha256, email) |\u003e Base.encode64\nquery = \"SELECT * FROM users WHERE email_hash = $1\"\nuser  = Ecto.Adapters.SQL.query!(Encryption.Repo, query, [hash])\n```\n\n\u003e _**Note**: there's a \"**built-in**\" Ecto_\n[`get_by`](https://hexdocs.pm/ecto/Ecto.Repo.html#c:get_by/3) _function\nto perform this type of_ \u003cbr /\u003e\n``\"SELECT ... WHERE field = value\"`` _query **effortlessly**_\n\n### 4.1 Generate the `SECRET_KEY_BASE`\n\nAll Phoenix apps have a `secret_key_base` for sessions.\nsee: https://hexdocs.pm/plug/1.13.6/Plug.Session.COOKIE.html\n\nRun the following command to generate a new phoenix secret key:\n\n```sh\nmix phx.gen.secret\n```\n_copy-paste_ the _output_ (64bit `String`)\ninto your `.env` file after the \"equals sign\" on the line for `SECRET_KEY_BASE`:\n```yml\nexport SECRET_KEY_BASE={YourSecreteKeyBaseGeneratedUsing-mix_phx.gen.secret}\n```\n\nYour `.env` file should look _similar_ to:\n[`.env_sample`](https://github.com/dwyl/phoenix-ecto-encryption-example/blob/master/.env_sample)\n\nLoad the secret key into your environment by typing into your terminal:\n\n```sh\nsource .env\n```\n\n\u003c!--\n#### _Alternatively_ Copy The `.env_sample` File\n\nThe _easy_ way manage your Environment Variables _locally_\nis to have a `.env` file in the _root_ of the project.\n\n_Copy_ the _sample_ one:\n\n```sh\ncp .env_sample .env\n```\n\u003e _**before** doing anything `else`,\nensure that `.env` is in your [`.gitignore`](https://github.com/nelsonic/phoenix-ecto-encryption-example/blob/0bc9481ab5f063e431244d915691d52103e103a6/.gitignore#L28) file._\n\nNow update the _values_ in your `.env` file the _real_ ones for your App. \u003cbr /\u003e\n--\u003e\n\n\u003e _**Note**: We are using an_ `.env` _file,\nbut if you are using a \"Cloud Platform\" to deploy your app, \u003cbr /\u003e\nyou could consider using their \"Key Management Service\"\nfor managing encryption keys. eg_: \u003cbr /\u003e\n+ Heroku:\nhttps://github.com/dwyl/learn-environment-variables#environment-variables-on-heroku\n+ AWS: https://aws.amazon.com/kms/\n+ Google Cloud: https://cloud.google.com/kms/\n\nWe now need to update our config files again. Open your `config.exs` file and\nchange the the following:\n***from***\n```elixir\n  secret_key_base: \"3PXN/6k6qoxqQjWFskGew4r74yp7oJ1UNF6wjvJSHjC5Y5LLIrDpWxrJ84UBphJn\",\n  # your secret_key_base will be different but that is fine.\n```\n\n**To**\n```elixir\n  secret_key_base: System.get_env(\"SECRET_KEY_BASE\"),\n```\n\nAs mentioned above, all Phoenix applications come with a `secret_key_base`.\nInstead of using this default one, we have told our application to use the new\none that we added to our `.env` file.\n\nNow we need to edit our `config/test.exs` file. Change the following:\n***from***\n```elixir\nconfig :encryption, EncryptionWeb.Endpoint,\n  http: [port: 4001],\n  server: false\n```\n\n**To**\n```elixir\nconfig :encryption, EncryptionWeb.Endpoint,\n  http: [port: 4001],\n  server: false,\n  secret_key_base: System.get_env(\"SECRET_KEY_BASE\")\n```\n\nBy adding the previous code block we will now have a `secret_key_base` which\nwe will be able to use for testing.\n\n## 5. _Create_ and use `HashField` Custom Ecto Type\n\nWhen we first created the Ecto Schema for our \"user\", in\n[Step 2](https://github.com/dwyl/phoenix-ecto-encryption-example#2-create-the-user-schema-database-table)\n(_above_)\nThis created the\n[`lib/encryption/user.ex`](https://raw.githubusercontent.com/dwyl/phoenix-ecto-encryption-example/36da851f30670967dd3493f055fb9f7b2649c188/lib/encryption/user.ex)\nfile with the following schema:\n\n```elixir\nschema \"users\" do\n  field :email, :binary\n  field :email_hash, :binary\n  field :name, :binary\n  field :password_hash, :binary\n\n  timestamps()\nend\n```\n\nThe _default_ Ecto field types (`:binary`) are a good start.\nBut we can do _so much_ better if we define _custom_ Ecto Types!\n\nEcto Custom Types are a way of automatically \"_pre-processing_\" data\nbefore inserting it into (_and reading from_) a database.\nExamples of \"pre-processing\" include:\n+ Custom Validation e.g: phone number or address format.\n+ Encrypting / Decrypting\n+ Hashing\n\nA custom type expects [6 callback functions](https://hexdocs.pm/ecto/Ecto.Type.html#callbacks)\nto be implemented in the file:\n+ [`type/0`](https://hexdocs.pm/ecto/Ecto.Type.html#c:type/0) - define\nthe Ecto Type we want Ecto to use to _store_ the data\nfor our Custom Type. e.g: `:integer` or `:binary`\n+ [`cast/1`](https://hexdocs.pm/ecto/Ecto.Type.html#c:cast/1) - \"typecasts\" (_converts_)\nthe given data to the desired type e.g: Integer to String.\n+ [`dump/1`](https://hexdocs.pm/ecto/Ecto.Type.html#c:dump/1) - performs the \"processing\"\non the raw data before it get's \"dumped\" into the Ecto Native Type.\n+ [`load/1`](https://hexdocs.pm/ecto/Ecto.Type.html#c:load/1) - called when\nloading data from the database and receive an Ecto native type.\n+ [`embed_as/1`](https://hexdocs.pm/ecto/Ecto.Type.html#c:embed_as/1) - the return value\n(`:self` or `:dump`) determines how the type is treated inside embeds (not used here).\n+ [`equal?/2`](https://hexdocs.pm/ecto/Ecto.Type.html#c:equal?/2) - invoked to determine\nif changing a type's field value changes the corresponding database record.\n\nCreate a file called `lib/encryption/hash_field.ex` and add the following:\n\n```elixir\ndefmodule Encryption.HashField do\n  @behaviour Ecto.Type\n\n  def type, do: :binary\n\n  def cast(value) do\n    {:ok, to_string(value)}\n  end\n\n  def dump(value) do\n    {:ok, hash(value)}\n  end\n\n  def load(value) do\n    {:ok, value}\n  end\n\n  def embed_as(_), do: :self\n\n  def equal?(value1, value2), do: value1 == value2\n\n  def hash(value) do\n    :crypto.hash(:sha256, value \u003c\u003e get_salt(value))\n  end\n\n  # Get/use Phoenix secret_key_base as \"salt\" for one-way hashing Email address\n  # use the *value* to create a *unique* \"salt\" for each value that is hashed:\n  defp get_salt(value) do\n    secret_key_base =\n      Application.get_env(:encryption, EncryptionWeb.Endpoint)[:secret_key_base]\n    :crypto.hash(:sha256, value \u003c\u003e secret_key_base)\n  end\nend\n\n```\n\nLet's step through each of these\n\n### `type/0`\n\nThe best data type for storing encrypted data is `:binary`\n(_it uses **half** the \"space\" of a `:string` for the **same** ciphertext_).\n\n### `cast/1`\n\nCast any data type `to_string` before encrypting it.\n(_the encrypted data \"ciphertext\" will be of_ `:binary` _type_)\n\n### `dump/1`\n\nThe `hash/1` function use Erlang's `crypto` library\n[`hash/2`](https://erlang.org/doc/man/crypto.html#hash-2) function.\n+ First we tell the `hash/2` function that we want to use `:sha256`\n\"SHA 256\" is the most widely used/recommended hash; it's both fast and \"secure\".\n+ We then hash the `value` passed in to the `hash/1` function (_we defined_)\nand _concatenate_ it with \"salt\" using the `get_salt/1` function\nwhich retrieves the `secret_key_base` environment variable\nand computes a ***unique*** \"salt\" using the value.\n\nWe use the `SHA256` one-way hash for _speed_.\nWe \"salt\" the email address so that\nthe hash has _some_ level of \"obfuscation\",\nin case the DB is ever \"compromised\"\nthe \"attacker\" still has to \"compute\"\na [\"rainbow table\"](https://en.wikipedia.org/wiki/Rainbow_table) from _scratch_.\n\n### `load/1`\n\nReturn the hash value as it is _read_ from the database.\n\n### `embed_as/1`\n\nThis callback is only of importance when the type is part of an [embed](https://hexdocs.pm/ecto/Ecto.Changeset.html#module-associations-embeds-and-on-replace). It's not used here,\nbut required for modules adopting the `Ecto.Type` behaviour as of Ecto 3.2.\n\n### `equal?/2`\n\nThis callback is invoked when we cast changes into a changeset and want to\ndetermine whether the database record needs to be updated. We use a simple\nequality comparison (`==`) to compare the current value to the requested\nupdate. If both values are equal, there's no need to update the record.\n\n\n\u003e _**Note**: Don't forget to export your_ `SECRET_KEY_BASE`\n_environment variable_ (_see instructions above_)\n\n\u003e The full file containing these two functions is:\n[`lib/encryption/hash_field.ex`](https://github.com/dwyl/phoenix-ecto-encryption-example/blob/master/lib/encryption/hash_field.ex) \u003cbr /\u003e\n\u003e And the tests for the functions are:\n[`test/lib/hash_field_test.exs`](https://github.com/dwyl/phoenix-ecto-encryption-example/blob/master/test/lib/hash_field_test.exs)\n\n\n_First_ add the `alias` for `HashField` near the top\nof the `lib/encryption/user.ex` file. e.g:\n```elixir\nalias Encryption.HashField\n```\n\n_Next_, in the `lib/encryption/user.ex` file,\n***update*** the lines for `email_hash` in the users schema\u003cbr /\u003e\n***from***:\n```elixir\nschema \"users\" do\n  field :email, :binary\n  field :email_hash, :binary\n  field :name, :binary\n  field :password_hash, :binary\n  timestamps()\nend\n```\n\n**To**:\n```elixir\nschema \"users\" do\n  field :email, :binary\n  field :email_hash, HashField\n  field :name, :binary\n  field :password_hash, :binary\n\n  timestamps()\nend\n```\n\n```Elixir\n  def changeset(%User{} = user, attrs \\\\ %{}) do\n    user\n    |\u003e cast(attrs, [:name, :email])\n    |\u003e validate_required([:email])\n    |\u003e add_email_hash\n    |\u003e unique_constraint(:email_hash)\n  end\n\n  defp add_email_hash(changeset) do\n    if Map.has_key?(changeset.changes, :email) do\n      changeset |\u003e put_change(:email_hash, changeset.changes.email)\n    else\n      changeset\n    end\n  end\n```\nWe should _test_ this new functionality. Create the file\n`test/lib/user_test.exs` and add the following:\n\n```elixir\ndefmodule Encryption.UserTest do\n  use Encryption.DataCase\n  alias Encryption.User\n\n  @valid_attrs %{\n    name: \"Max\",\n    email: \"max@example.com\",\n    password: \"NoCarbsBeforeMarbs\"\n  }\n\n  @invalid_attrs %{}\n\n  describe \"Verify correct working of hashing\" do\n    setup do\n      user = Repo.insert!(User.changeset(%User{}, @valid_attrs))\n      {:ok, user: user, email: @valid_attrs.email}\n    end\n\n    test \"inserting a user sets the :email_hash field\", %{user: user} do\n      assert user.email_hash == user.email\n    end\n\n    test \":email_hash field is the encrypted hash of the email\", %{user: user} do\n      user_from_db = User |\u003e Repo.one()\n      assert user_from_db.email_hash == Encryption.HashField.hash(user.email)\n    end\n  end\nend\n```\n\nFor the _full_ user tests please see:\n[`test/user/user_test.exs`](https://github.com/dwyl/phoenix-ecto-encryption-example/blob/master/test/user/user_test.exs)\n\n\n## 6. Create and user Hash _Password_ Custom Ecto type\n\nWhen hashing **passwords**, we want to use the **_strongest_ hashing algorithm**\nand we also want the hashed value (_or \"digest\"_) to be ***different***\neach time the _same_ `plaintext` is hashed\n(_unlike when hashing the email address\n  where we want a deterministic digest_).\n\nUsing `argon2` makes \"cracking\" a password\n(_in the event of the database being \"compromised\")\nfar less likely_ as it uses _both_ a CPU-bound \"work-factor\"\n_and_ a \"Memory-hard\" algorithm which will _significantly_\n\"slow down\" the attacker.\n\n### Add the `argon2` Dependency\n\nIn order to use `argon2` we must add it to our `mix.exs` file:\nin the `defp deps do` (_dependencies_) section, add the following line:\n\n```elixir\n{:argon2_elixir, \"~\u003e 1.3\"},  # securely hashing \u0026 verifying passwords\n```\n\nYou will need to run `mix deps.get` to install the dependency.\n\n\n### 6.1 Define the `hash_password/1` Function\n\nCreate a file called `lib/encryption/password_field.ex` in your project.\nThe first function we need is `hash_password/1`:\n\n```elixir\ndefmodule Encryption.PasswordField do\n\n  def hash_password(value) do\n    Argon2.Base.hash_password(to_string(value),\n      Argon2.Base.gen_salt(), [{:argon2_type, 2}])\n  end\n\nend\n```\n\n`hash_password/1` accepts a password to be hashed and invokes\n[`Argon2.Base.hash_password/3`](https://hexdocs.pm/argon2_elixir/Argon2.Base.html#hash_password/3)\npassing in 3 arguments:\n+ `value` - the value (_password_) to be hashed.\n+ [`Argon2.Base.gen_salt/1`](https://hexdocs.pm/argon2_elixir/Argon2.Base.html#gen_salt/1) - the salt used to initialise the hash function\nnote: \"behind the scenes\" just\n[`:crypto.strong_rand_bytes(16)`](https://github.com/riverrun/argon2_elixir/blob/d283a4f316a2a26e61f032a826ff992a480018c2/lib/argon2.ex#L76)\nas we saw before in the `encrypt` function; again,\n**128 bits** is considered \"secure\" as a hash salt or initialization vector.\n+ `[{:argon2_type, 2}]` - this corresponds to `argon2id` see:\n  + https://github.com/riverrun/argon2_elixir/issues/17\n  + https://crypto.stackexchange.com/questions/48935/why-use-argon2i-or-argon2d-if-argon2id-exists\n\n#### 6.1.1 Test the `hash_password/1` Function?\n\nIn order to _test_ the `PasswordField.hash_password/1` function\nwe use the `Argon2.verify_pass` function to _verify_ a password hash.\n\nCreate a file called `test/lib/password_field_test.exs`\nand _copy-paste_ (_or hand-type_) the following test:\n\n```elixir\ndefmodule Encryption.PasswordFieldTest do\n  use ExUnit.Case\n  alias Encryption.PasswordField, as: Field\n\n  test \".verify_password checks the password against the Argon2id Hash\" do\n    password = \"EverythingisAwesome\"\n    hash = Field.hash_password(password)\n    verified = Argon2.verify_pass(password, hash)\n    assert verified\n  end\n\nend\n```\n\nRun the test using the command:\n\n```sh\nmix test test/lib/password_field_test.exs\n```\n\nThe test should _pass_;\nif _not_, please re-trace the steps.\n\n\n### 6.2 _Verify_ Password\n\nThe _corresponding_ function to _check_ (_or \"verify\"_)\nthe password is `verify_password/2`.\nWe need to supply both the `password` and `stored_hash`\n(_the hash that was previously stored in the database\nwhen the person registered or updated their password_)\nIt then runs `Argon2.verify_pass` which does the checking.\n\n```elixir\ndef verify_password(password, stored_hash) do\n  Argon2.verify_pass(password, stored_hash)\nend\n```\n\n`hash_password/1` and `verify_password/2` functions are defined in:\n[`lib/encryption/password_field.ex`](https://github.com/dwyl/phoenix-ecto-encryption-example/blob/master/lib/encryption/password_field.ex)\n\n\n##### Test for `verify_password/2`\n\nTo test that our `verify_password/2` function works as _expected_,\nopen the file: `test/lib/password_field_test.exs` \u003cbr /\u003e\nand add the following code to it:\n\n```elixir\ntest \".verify_password fails if password does NOT match hash\" do\n  password = \"EverythingisAwesome\"\n  hash = Field.hash_password(password)\n  verified = Field.verify_password(\"LordBusiness\", hash)\n  assert !verified\nend\n```\n\nRun the tests: `mix test test/lib/password_field_test.exs`\nand confirm they pass.\n\nIf you get stuck, see:\n[`test/lib/password_field_test.exs`](https://github.com/dwyl/phoenix-ecto-encryption-example/blob/master/test/lib/password_field_test.exs)\n\n\nDefine the other Ecto.Type behaviour functions:\n\n```Elixir\ndefmodule Encryption.PasswordField do\n  @behaviour Ecto.Type\n\n  def type, do: :binary\n\n  def cast(value) do\n    {:ok, to_string(value)}\n  end\n\n  def dump(value) do\n    {:ok, hash_password(value)}\n  end\n\n  def load(value) do\n    {:ok, value}\n  end\n\n  def embed_as(_), do: :self\n\n  def equal?(value1, value2), do: value1 == value2\n\n  def hash_password(value) do\n    Argon2.Base.hash_password(to_string(value),\n      Argon2.Base.gen_salt(), [{:argon2_type, 2}])\n  end\n\n  def verify_password(password, stored_hash) do\n    Argon2.verify_pass(password, stored_hash)\n  end\nend\n\n```\n\n\n```elixir\nalias Encryption.{HashField, PasswordField, User}\n```\n\nUpdate the lines for `:email` and `:name` in the schema \u003cbr /\u003e\n***from***:\n```elixir\nschema \"users\" do\n  field :email, :binary\n  field :email_hash, HashField\n  field :name, :binary\n  field :password_hash, :binary\n\n  timestamps()\nend\n```\n\n**To**:\n```elixir\nschema \"users\" do\n  field :email, :binary\n  field :email_hash, HashField\n  field :name, :binary\n  field :password_hash, PasswordField\n\n  timestamps()\nend\n```\n\n\n\n## 7. _Create_ and use `EncryptedField` Custom Ecto Type\n\n\nCreate a file called `lib/encryption/encrypted_field.ex` and add the following:\n\n```elixir\ndefmodule Encryption.EncryptedField do\n  alias Encryption.AES  # alias our AES encrypt \u0026 decrypt functions (3.1 \u0026 3.2)\n\n  @behaviour Ecto.Type  # Check this module conforms to Ecto.type behavior.\n  def type, do: :binary # :binary is the data type ecto uses internally\n\n  # cast/1 simply calls to_string on the value and returns a \"success\" tuple\n  def cast(value) do\n    {:ok, to_string(value)}\n  end\n\n  # dump/1 is called when the field value is about to be written to the database\n  def dump(value) do\n    ciphertext = value |\u003e to_string |\u003e AES.encrypt\n    {:ok, ciphertext} # ciphertext is :binary data\n  end\n\n  # load/1 is called when the field is loaded from the database\n  def load(value) do\n    {:ok, AES.decrypt(value)} # decrypted data is :string type.\n  end\n\n  # embed_as/1 dictates how the type behaves when embedded (:self or :dump)\n  def embed_as(_), do: :self # preserve the type's higher level representation\n\n  # equal?/2 is called to determine if two field values are semantically equal\n  def equal?(value1, value2), do: value1 == value2\nend\n```\n\nLet's step through each of these\n\n### `type/0`\n\nThe best data type for storing encrypted data is `:binary`\n(_it uses **half** the \"space\" of a `:string` for the **same** ciphertext_).\n\n### `cast/1`\n\nCast any data type `to_string` before encrypting it.\n(_the encrypted data \"ciphertext\" will be of_ `:binary` _type_)\n\n### `dump/1`\n\nCalls the `AES.encrypt/1` function we defined in section 3.1 (_above_)\nso data is _encrypted_ 'automatically' before we insert into the database.\n\n### `load/1`\n\nCalls the `AES.decrypt/1` function so data is 'automatically' _decrypted_ when it is _read_\nfrom the database.\n\n\u003e _**Note**: the_ `load/2` _function is **not required**\nfor Ecto Type compliance.\nFurther reading_: https://hexdocs.pm/ecto/Ecto.Type.html\n\n### `embed_as/1`\n\nThis callback is only of importance when the type is part of an \n[embed](https://hexdocs.pm/ecto/Ecto.Changeset.html#module-associations-embeds-and-on-replace). \nIt's not used here,\nbut required for modules adopting the `Ecto.Type` behaviour as of Ecto 3.2.\n\n### `equal?/2`\n\nThis callback is invoked when we cast changes into a changeset and want to\ndetermine whether the database record needs to be updated. We use a simple\nequality comparison (`==`) to compare the current value to the requested\nupdate. If both values are equal, there's no need to update the record.\n\n\n_Your_ `encrypted_field.ex` Custom Ecto Type should look like this:\n[`lib/encryption/encrypted_field.ex`](https://github.com/dwyl/phoenix-ecto-encryption-example/blob/master/lib/encryption/encrypted_field.ex)\n`try` to write the **tests** for the callback functions,\nif you get \"stuck\", take a look at:\n[`test/lib/encrypted_field_test.exs`](https://github.com/dwyl/phoenix-ecto-encryption-example/blob/master/test/lib/encrypted_field_test.exs)\n\n\nNow that we have defined a Custom Ecto Type `EncryptedField`,\nwe can _use_ the Type in our User Schema.\nAdd the following line to \"alias\" the Type and a User\nin the `lib/encryption/user.ex` file:\n\n```elixir\nalias Encryption.{HashField, PasswordField, EncryptedField, User}\n```\n\nUpdate the lines for `:email` and `:name` in the schema \u003cbr /\u003e\n***from***:\n```elixir\nschema \"users\" do\n  field :email, :binary\n  field :email_hash, HashField\n  field :name, :binary\n  field :password_hash, PasswordField\n\n  timestamps()\nend\n```\n\n**To**:\n```elixir\nschema \"users\" do\n  field :email, EncryptedField\n  field :email_hash, HashField\n  field :name, EncryptedField\n  field :password_hash, PasswordField\n\n  timestamps()\nend\n```\n\n\n\n## 8. Ensure All Tests Pass\n\nTypically we will create `git commit` (_if we don't already have one_)\nfor the \"known state\" where the tests were passing\n(_before starting the refactor_).\n\nThe commit _before_ refactoring the example is:\nhttps://github.com/dwyl/phoenix-ecto-encryption-example/tree/3659399ec32ca4f07f45d0552b9cf25c359a2456\n\nThe corresponding Travis-CI build for this commit is:\nhttps://travis-ci.org/dwyl/phoenix-ecto-encryption-example/jobs/379887597#L833\n\n\u003e _**Note**: if you are_ `new` _to Travis-CI see_:\n[https://github.com/dwyl/**learn-travis**](https://github.com/dwyl/learn-travis)\n\n\n# Conclusion\n\nWe have gone through how to create custom Ecto Types\nin order to define our own functions for handling\n(_transforming_) specific types of data.\n\nOur hope is that you have _understood_ the flow.\n\nWe plan to extend this tutorial include User Interface\nplease \"star\" the repo if you would find that useful.\n\n\n\n\u003cbr /\u003e \u003cbr /\u003e\n\n## How To Generate AES Encryption Keys?\n\nEncryption keys should be the appropriate length (in bits)\nas required by the chosen algorithm.\n\n\u003e An **AES 128-bit** key can be expressed\nas a hexadecimal string with 32 characters. \u003cbr /\u003e\nIt will require **24 characters** in **base64**.\n\n\u003e An **AES 256-bit** key can be expressed\nas a hexadecimal string with 64 characters. \u003cbr /\u003e\nIt will require **44 characters** in **base64**.\n\nsee: https://security.stackexchange.com/a/45334/117318\n\nOpen `iex` in your Terminal and paste the following line (_then press enter_)\n```elixir\n:crypto.strong_rand_bytes(32) |\u003e :base64.encode\n```\n\nYou should see terminal output similar to the following:\n\n![elixir-generate-encryption-key](https://user-images.githubusercontent.com/194400/38561017-dd93d186-3cce-11e8-91cd-c70f920ac79a.png)\n\nWe generated 3 keys for demonstration purposes:\n+ \"h6pUk0ZccS0pYsibHZZ4Cd+PRO339rMA7sMz7FnmcGs=\"\n+ \"nMd/yQpR0aoasLaq1g94FL/a+A+wB44JLko47sVQXMg=\"\n+ \"L+ZVX8iheoqgqb22mUpATmMDsvVGt/foAe/0KN5uWf0=\"\n\n\n\nThese two Erlang functions are described in:\n+ https://erlang.org/doc/man/crypto.html#strong_rand_bytes-1\n+ https://erlang.org/doc/man/base64.html#encode-1\n\nBase64 encoding the bytes generated by `strong_rand_bytes`\nwill make the output human-readable\n(_whereas bytes are less user-friendly_).\n\n\n\u003cbr /\u003e \u003cbr /\u003e\n\n# Useful Links, FAQ \u0026 Background Reading\n\n+ Bits and Bytes: https://web.stanford.edu/class/cs101/bits-bytes.html\n+ Thinking in Ecto - Schemas and Changesets:\nhttps://cultofmetatron.io/2017/04/22/thinking-in-ecto---schemas-and-changesets/\n+ Initialization Vector Length:\nhttps://stackoverflow.com/questions/4608489/how-to-pick-an-appropriate-iv-initialization-vector-for-aes-ctr-nopadding (128 bits is 16 bytes).\n+ What is the effect of the different AES key lengths?\nhttps://crypto.stackexchange.com/questions/3615/what-is-the-effect-of-the-different-aes-key-lengths\n+ How is decryption done in AES CTR mode?: https://crypto.stackexchange.com/questions/34918/how-is-decryption-done-in-aes-ctr-mode\n+ Block Cipher Counter (CTR) Mode:\nhttps://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_.28CTR.29\n+ Is AES-256 _weaker_ than 192 and 128 bit versions?\nhttps://crypto.stackexchange.com/questions/5118/is-aes-256-weaker-than-192-and-128-bit-versions\n+ What are the practical differences between 256-bit, 192-bit, and 128-bit AES encryption?\nhttps://crypto.stackexchange.com/questions/20/what-are-the-practical-differences-between-256-bit-192-bit-and-128-bit-aes-enc\n+ **How to Choose** an **Authenticated Encryption mode**\n(_by Matthew Green cryptography professor at Johns Hopkins University_):\nhttps://blog.cryptographyengineering.com/2012/05/19/how-to-choose-authenticated-encryption\n+ How to choose an AES encryption mode (CBC ECB CTR OCB CFB)? (v. long answers, but good comparison!)\nhttps://stackoverflow.com/questions/1220751/how-to-choose-an-aes-encryption-mode-cbc-ecb-ctr-ocb-cfb\n+ AES GCM vs CTR+HMAC tradeoffs:\nhttps://crypto.stackexchange.com/questions/14747/gcm-vs-ctrhmac-tradeoffs\n+ Galois/Counter Mode for symmetric key cryptographic block ciphers:\nhttps://en.wikipedia.org/wiki/Galois/Counter_Mode\n+ What is the difference between CBC and GCM mode?\nhttps://crypto.stackexchange.com/questions/2310/what-is-the-difference-between-cbc-and-gcm-mode\n+ Ciphertext and tag size and IV transmission with AES in GCM mode:\nhttps://crypto.stackexchange.com/questions/26783/ciphertext-and-tag-size-and-iv-transmission-with-aes-in-gcm-mode\n+ How long (in letters) are encryption keys for AES?\nhttps://security.stackexchange.com/questions/45318/how-long-in-letters-are-encryption-keys-for-aes\n+ Why we can't implement AES 512 key size?\nhttps://crypto.stackexchange.com/questions/20253/why-we-cant-implement-aes-512-key-size\n+ Generate random alphanumeric string (_used for AES keys_)\nhttps://stackoverflow.com/questions/12788799/how-to-generate-a-random-alphanumeric-string-with-erlang\n+ Singular or Plural controller names?: https://stackoverflow.com/questions/35882394/phoenix-controllers-singular-or-plural\n+ What's the purpose of key-rotation?\nhttps://crypto.stackexchange.com/questions/41796/whats-the-purpose-of-key-rotation\n+ Postgres Data Type for storing `bcrypt` hashed passwords: https://stackoverflow.com/questions/33944199/bcrypt-and-postgresql-what-data-type-should-be-used \u003e\u003e `bytea` (_byte_)\n+ Do security experts recommend bcrypt? https://security.stackexchange.com/questions/4781/do-any-security-experts-recommend-bcrypt-for-password-storage/6415#6415\n+ Hacker News discussion thread \"***Don't use `bcrypt`***\":\nhttps://news.ycombinator.com/item?id=3724560\n+ Storing Passwords in a Highly Parallelized World:\nhttps://hynek.me/articles/storing-passwords\n+ Password hashing security of argon2 versus bcrypt/PBKDF2?\nhttps://crypto.stackexchange.com/questions/30785/password-hashing-security-of-argon2-versus-bcrypt-pbkdf2\n+ The memory-hard Argon2 password hash function (ietf proposal):\nhttps://tools.ietf.org/id/draft-irtf-cfrg-argon2-03.html\nunlikely to be a \"standard\" any time soon...\n+ Erlang Dirty Scheduler Overhead:\nhttps://medium.com/@jlouis666/erlang-dirty-scheduler-overhead-6e1219dcc7\n+ Erlang Scheduler Details and Why They Matter:\nhttps://news.ycombinator.com/item?id=11064763\n+ Why use argon2i or argon2d if argon2id exists?\nhttps://crypto.stackexchange.com/questions/48935/why-use-argon2i-or-argon2d-if-argon2id-exists\n+ Good explanation of _Custom_ Ecto Types:\nhttps://medium.com/acutario/ecto-custom-types-a-practical-case-with-enumerize-rails-gem-b5496c2912ac\n+ Consider using ETS to store encryption/decryption keys:\nhttps://elixir-lang.org/getting-started/mix-otp/ets.html \u0026\nhttps://elixirschool.com/en/lessons/specifics/ets\n\n## Understanding Advanced Encryption Standard (AES)\n\nIf you prefer to _read_, \nRyo Nakao wrote an excellent post on\nunderstanding how AES encryption works:\nhttps://nakabonne.dev/posts/understanding-how-aes-encryption-works/\n\nIf you have the bandwidth and prefer a video,\nComputerphile (YouTube channel)\nhas an great explaination:\n\n[![aes-explanation](https://user-images.githubusercontent.com/6057298/69802900-8aefd800-11d2-11ea-92de-03406751b910.png)](https://www.youtube.com/watch?v=O4xNJsjtN6E)\n\n## Running a Single Test\n\nTo run a _single_ test (_e.g: while debugging_), use the following syntax:\n```sh\nmix test test/user/user_test.exs:9\n```\nFor more detail, please see: https://hexdocs.pm/phoenix/testing.html\n\n## Ecto Validation Error format\n\nWhen Ecto `changeset` validation fails,\nfor example if there is a \"unique\" constraint on email address\n(_so that people cannot re-register with the same email address twice_),\nEcto returns the `changeset` with an `errors` key:\n\n```elixir\n#Ecto.Changeset\u003c\n  action: :insert,\n  changes: %{\n    email: \u003c\u003c224, 124, 228, 125, 105, 102, 38, 170, 15, 199, 228, 198, 245, 189,\n      82, 193, 164, 14, 182, 8, 189, 19, 231, 49, 80, 223, 84, 143, 232, 92, 96,\n      156, 100, 4, 7, 162, 26, 2, 121, 32, 187, 65, 254, 50, 253, 101, 202\u003e\u003e,\n    email_hash: \u003c\u003c21, 173, 0, 16, 69, 67, 184, 120, 1, 57, 56, 254, 167, 254,\n      154, 78, 221, 136, 159, 193, 162, 130, 220, 43, 126, 49, 176, 236, 140,\n      131, 133, 130\u003e\u003e,\n    key_id: 1,\n    name: \u003c\u003c2, 215, 188, 71, 109, 131, 60, 147, 219, 168, 106, 157, 224, 120,\n      49, 224, 225, 181, 245, 237, 23, 68, 102, 133, 85, 62, 22, 166, 105, 51,\n      239, 198, 107, 247, 32\u003e\u003e,\n    password_hash: \u003c\u003c132, 220, 9, 85, 60, 135, 183, 155, 214, 215, 156, 180,\n      205, 103, 189, 137, 81, 201, 37, 214, 154, 204, 185, 253, 144, 74, 222,\n      80, 158, 33, 173, 254\u003e\u003e\n  },\n  errors: [email_hash: {\"has already been taken\", []}],\n  data: #Encryption.User\u003c\u003e,\n  valid?: false\n\u003e\n```\n\nThe `errors` part is:\n```elixir\n[email_hash: {\"has already been taken\", []}]\n```\nA `tuple` _wrapped_ in a `keyword list`.\n\nWhy this construct? A changeset can have multiple errors, so they're stored as a keyword list, where the _key_ is the field, and the _value_ is the error tuple.\nThe first item in the tuple is the error message, and the second is another keyword list, with additional information that we would use when mapping over the errors in order to make them more user-friendly (though here, it's empty).\nSee the Ecto docs for [`add_error/4`](https://hexdocs.pm/ecto/Ecto.Changeset.html#add_error/4) and [`traverse_errors/2`](https://hexdocs.pm/ecto/Ecto.Changeset.html#traverse_errors/2) for more information.\n\nSo to _access_ the error message `\"has already been taken\"`\nwe need some pattern-matching and list popping:\n```elixir\n{:error, changeset} = Repo.insert User.changeset(%User{}, @valid_attrs)\n{:ok, message} = Keyword.fetch(changeset.errors, :email_hash)\nmsg = List.first(Tuple.to_list(message))\nassert \"has already been taken\" == msg\n```\nTo see this in _action_ run:\n```sh\nmix test test/user/user_test.exs:40\n```\n\n# Stuck / Need Help?\n\nIf _you_ get \"stuck\", please open an issue on GitHub:\nhttps://github.com/nelsonic/phoenix-ecto-encryption-example/issues\ndescribing the issue you are facing with as much detail as you can.\n\n\u003c!--\nTIL: app names in Phoneix _must_ be lowercase letters: \u003cbr /\u003e\n![lower-case-app-names](https://user-images.githubusercontent.com/194400/35360087-73d69d88-0154-11e8-9f47-d9a9333d1e6c.png)\n(_basic, I know, now..._)\n\nWorks with lowercase:  \u003cbr /\u003e\n![second-time-lucky](https://user-images.githubusercontent.com/194400/35360183-c522063c-0154-11e8-994a-7516bc0e5c1e.png)\n--\u003e\n\n\u003cbr /\u003e \u003cbr /\u003e\n\n# Credits\n\nInspiration/credit/thanks for this example goes to **Daniel Berkompas**\n[@danielberkompas](https://github.com/danielberkompas)\nfor his post: \u003cbr /\u003e\nhttps://blog.danielberkompas.com/2015/07/03/encrypting-data-with-ecto \u003cbr /\u003e\n\nDaniel's post is for\n[Phoenix `v0.14.0`](https://github.com/danielberkompas/danielberkompas.github.io/blob/c6eb249e5019e782e891bfeb591bc75f084fd97c/_posts/2015-07-03-encrypting-data-with-ecto.md)\nwhich is quite \"old\" now ...\u003cbr /\u003e\ntherefore a few changes/updates are required. \u003cbr /\u003e\ne.g: There are no more \"**Models**\" in Phoenix 1.3 or Ecto callbacks.\n\n_Also_ his post only includes the \"sample code\"\nand is _not_ a _complete_ example \u003cbr /\u003e\nand does _not_ _explain_ the functions \u0026 Custom Ecto Types. \u003cbr /\u003e\nWhich means anyone following the post needs\nto _manually_ copy-paste the code ...\nand \"figure out\" the \"gaps\" themselves to make it work.\u003cbr /\u003e\nWe _prefer_ to include the _complete_ \"**end state**\"\nof any tutorial (_not just \"samples\"_) \u003cbr /\u003e\nso that _anyone_ can `git clone` and _`run`_\nthe code locally to _fully understand_ it.\n\nStill, props to Daniel for his post, a _good intro_ to the topic!\n\n\u003c!--\nI reached out to Daniel on Twitter\nasking if he would accept a Pull Request\nupdating the post to latest version of Phoenix: \u003cbr /\u003e\n[![credit-tweet](https://user-images.githubusercontent.com/194400/35771850-32b1cfba-092b-11e8-9bbf-0e693016bb76.png)](https://twitter.com/nelsonic/status/959901100760498181) \u003cbr /\u003e\nIf he replies I will _gladly_ create a PR.\n_Meanwhile_ this example will fill in the gaps\nand provide a more up-to-date example.\n--\u003e\n","funding_links":[],"categories":["Examples and funny stuff"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdwyl%2Fphoenix-ecto-encryption-example","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdwyl%2Fphoenix-ecto-encryption-example","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdwyl%2Fphoenix-ecto-encryption-example/lists"}