Skip to content

Commit bad211a

Browse files
committed
add answer for dwyl/auth#169
1 parent 368dc61 commit bad211a

File tree

3 files changed

+208
-2
lines changed

3 files changed

+208
-2
lines changed

README.md

Lines changed: 202 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,9 @@ sensitive information and can be stored in plaintext.
211211

212212
## Testing
213213

214-
To run tests locally, you'll need to specify two env vars. You can pull example values for these variables from the travis CI config file:
214+
To run tests locally, you'll need to specify two env vars.
215+
You can pull example values for these variables from the
216+
Travis CI config file:
215217

216218
```
217219
ENCRYPTION_KEYS="key1,key2" SECRET_KEY_BASE="key" mix test
@@ -224,6 +226,9 @@ that is not already in the **`Fields`** package,
224226
please open an issue so we can add it!
225227
[github.com/dwyl/fields/issues](https://github.com/dwyl/fields/issues)
226228

229+
<br />
230+
231+
227232
<br />
228233

229234
## Background / Further Reading 🔗
@@ -235,3 +240,199 @@ encryption/decryption works using Ecto Types, <br />see:
235240
If you are rusty/new on Binaries in Elixir,
236241
take a look at this post by @blackode: <br />
237242
https://medium.com/blackode/playing-with-elixir-binaries-strings-dd01a40039d5
243+
244+
245+
# Questions?
246+
247+
If you have questions, please open an issue:
248+
[github.com/dwyl/fields/issues](https://github.com/dwyl/fields/issues)
249+
250+
A recent/good example is: [issues/169](https://github.com/dwyl/auth/issues/169)
251+
252+
### Why do we have _both_ `EmailEncrypted` and `EmailHash` ?
253+
254+
[`EmailEncrypted`](https://github.com/dwyl/fields/blob/main/lib/email_encrypted.ex)
255+
and
256+
[`EmailHash`](https://github.com/dwyl/fields/blob/main/lib/email_hash.ex)
257+
serve very different purposes.
258+
Briefly:
259+
with
260+
[**encryption**](https://en.wikipedia.org/wiki/Encryption)
261+
the output is **_always_ different**
262+
is meant for safely storing sensitive data
263+
that we want to **_decrypt_** later
264+
whereas with
265+
[**hash**](https://en.wikipedia.org/wiki/Hash_function)
266+
the output is **_always_ the same**
267+
it cannot be "unhashed" but
268+
can be used to
269+
[***check***](https://en.wikipedia.org/wiki/Checksum) a value,
270+
i.e. you can lookup a _hashed_ value in a database.
271+
272+
The best way to understand how these work
273+
is to see it for yourself.
274+
Start an
275+
[`IEx`](https://hexdocs.pm/iex/1.1.1/IEx.html)
276+
session in your terminal:
277+
278+
```sh
279+
iex -S mix
280+
```
281+
282+
You should see output similar to the following:
283+
284+
```sh
285+
Erlang/OTP 24 [erts-12.0.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit] [dtrace]
286+
287+
Compiling 23 files (.ex)
288+
Generated fields app
289+
Interactive Elixir (1.12.3) - press Ctrl+C to exit (type h() ENTER for help)
290+
```
291+
That confirms the `fields` module has compiled.
292+
293+
#### Encryption
294+
295+
Now that you've initialized `IEx`,
296+
issue the following commands:
297+
298+
```sh
299+
iex> email = "[email protected]"
300+
301+
302+
303+
iex(2)> encrypted = Fields.AES.encrypt(email)
304+
305+
<<48, 48, 48, 49, 20, 6, 117, 239, 107, 251, 80, 156, 109, 46, 6, 75, 119, 89,
306+
72, 163, 156, 243, 60, 6, 17, 166, 130, 239, 93, 222, 65, 186, 185, 78, 77, 2,
307+
80, 194, 241, 31, 28, 24, 155, 172, 208, 185, 142, 64, 65, 127>>
308+
```
309+
310+
> **Note**: the `Fields.EmailEncrypted`
311+
uses the `AES.encrypt/1` behind the scenes,
312+
that's why we are using it here directly.
313+
You could just as easily have written:
314+
`{:ok, encrypted} = Fields.EmailEncrypted.dump(email)`
315+
this is just a shorthand.
316+
317+
That output `<<48, 48, 48 ... 64, 65, 127>>` is a
318+
[**bitstring**](https://elixir-lang.org/getting-started/binaries-strings-and-char-lists.html#bitstrings)
319+
which is the sequence of bits in memory.
320+
The encrypted data - usually called
321+
["ciphertext"](https://en.wikipedia.org/wiki/Ciphertext) -
322+
is not human readable, that's a feature.
323+
But if you want to _decrypt_ it back to its human-readable form,
324+
simply run:
325+
326+
```
327+
iex(3)> decrypted = Fields.AES.decrypt(encrypted)
328+
329+
330+
```
331+
332+
So we know that an encrypted value can be decrypted.
333+
In the case of `EmailEncrypted` this is useful
334+
when we want to send someone an email message.
335+
For security/privacy,
336+
we want their sensitive personal data to be stored
337+
_encrypted_ in the Database,
338+
but when we need to decrypt it to send them a message,
339+
it's easy enough.
340+
341+
If you run the `Fields.AES.encrypt/1` function
342+
multiple times in your terminal,
343+
you will _always_ see different output:
344+
345+
```elixir
346+
iex(4)> Fields.AES.encrypt(email)
347+
<<48, 48, 48, 49, 168, 212, 210, 53, 233, 104, 27, 235, 199, 43, 87, 74, 3, 2,
348+
211, 114, 187, 229, 157, 182, 37, 34, 209, 37, 66, 160, 30, 126, 238, 180,
349+
146, 133, 227, 53, 245, 228, 119, 191, 117, 247, 37, 176, 130, 110, ...>>
350+
iex(5)> Fields.AES.encrypt(email)
351+
<<48, 48, 48, 49, 196, 170, 48, 97, 75, 206, 148, 204, 41, 149, 64, 50, 27, 56,
352+
112, 19, 53, 108, 86, 153, 154, 53, 53, 97, 232, 133, 97, 88, 214, 254, 40,
353+
84, 65, 227, 75, 123, 212, 222, 63, 221, 176, 130, 11, 173, ...>>
354+
iex(6)> Fields.AES.encrypt(email)
355+
<<48, 48, 48, 49, 201, 239, 104, 101, 140, 232, 0, 216, 183, 168, 220, 130, 24,
356+
236, 205, 220, 239, 112, 112, 168, 86, 235, 84, 115, 108, 116, 16, 234, 184,
357+
72, 111, 144, 245, 1, 125, 207, 230, 68, 126, 111, 84, 83, 23, 90, ...>>
358+
iex(7)> Fields.AES.encrypt(email)
359+
<<48, 48, 48, 49, 176, 131, 145, 182, 128, 43, 11, 100, 253, 73, 179, 144, 139,
360+
45, 211, 156, 155, 117, 119, 59, 152, 148, 45, 36, 95, 141, 35, 242, 182, 51,
361+
235, 162, 186, 132, 23, 34, 174, 171, 157, 115, 54, 211, 124, 247, ...>>
362+
```
363+
364+
The first 4 bytes `<<48, 48, 48, 49,` are the same
365+
because we are using the same encryption key.
366+
But the rest is _always_ different.
367+
368+
369+
#### Hashing
370+
371+
A `hash` function
372+
can be used to map data of arbitrary size
373+
to fixed-size values.
374+
i.e. _any_ length of `plaintext` will
375+
result in the _same_ length `hash` _value_.
376+
A `hash` function is _one-way_,
377+
it cannot be reversed or "un-hashed".
378+
The the `hash` _value_ is _always_ the same
379+
for a given string of plaintext.
380+
381+
382+
Try it in `IEx`:
383+
384+
```elixir
385+
iex(1)> email = "[email protected]"
386+
387+
388+
iex(2)> Fields.Helpers.hash(:sha256, email)
389+
<<95, 251, 251, 204, 181, 59, 239, 4, 218, 193, 35, 20, 223, 131, 219, 101, 30,
390+
17, 97, 146, 103, 115, 3, 185, 230, 137, 218, 137, 209, 111, 48, 236>>
391+
iex(3)> Fields.Helpers.hash(:sha256, email)
392+
<<95, 251, 251, 204, 181, 59, 239, 4, 218, 193, 35, 20, 223, 131, 219, 101, 30,
393+
17, 97, 146, 103, 115, 3, 185, 230, 137, 218, 137, 209, 111, 48, 236>>
394+
iex(4)> Fields.Helpers.hash(:sha256, email)
395+
<<95, 251, 251, 204, 181, 59, 239, 4, 218, 193, 35, 20, 223, 131, 219, 101, 30,
396+
17, 97, 146, 103, 115, 3, 185, 230, 137, 218, 137, 209, 111, 48, 236>>
397+
```
398+
399+
The hash _value_ is identical for the given text.
400+
If you use the `Fields.EmailHash` function,
401+
you will see the same hash value
402+
(_because the same helper function is invoked_):
403+
404+
```elixir
405+
iex(5)> Fields.EmailHash.dump(email)
406+
{:ok,
407+
<<95, 251, 251, 204, 181, 59, 239, 4, 218, 193, 35, 20, 223, 131, 219, 101, 30,
408+
17, 97, 146, 103, 115, 3, 185, 230, 137, 218, 137, 209, 111, 48, 236>>}
409+
iex(6)> Fields.EmailHash.dump(email)
410+
{:ok,
411+
<<95, 251, 251, 204, 181, 59, 239, 4, 218, 193, 35, 20, 223, 131, 219, 101, 30,
412+
17, 97, 146, 103, 115, 3, 185, 230, 137, 218, 137, 209, 111, 48, 236>>}
413+
```
414+
415+
416+
417+
418+
<!--
419+
420+
[AES.encrypt/1](https://github.com/dwyl/fields/blob/519f2e9da9c6267e9b9b5359370b21a78390d020/lib/aes.ex#L30)
421+
has an
422+
[Initialization Vector](https://en.wikipedia.org/wiki/Initialization_vector) (**`IV`**)
423+
which is a random set of bytes
424+
prepended to the data each time it gets encrypted.
425+
This increases the randomness of the **`ciphertext`**
426+
and thus makes it more difficult to `decrypt`
427+
in the event an attacker accesses the DB.
428+
429+
430+
The `IV` is included in the `bitstring` returned by `AES.encrypt/1`
431+
which could be split and stored separately in a high security system.
432+
We are storing them together for now as we feel that having a unique key
433+
stored in a Key Management System (KMS) is adequate for our needs.
434+
-->
435+
436+
437+
### How does
438+

config/config.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,4 @@ use Mix.Config
2727
# Configuration from the imported file will override the ones defined
2828
# here (which is why it is important to import them last).
2929

30-
if Mix.env() == :test, do: import_config("#{Mix.env()}.exs")
30+
if Mix.env() == :test || Mix.env() == :dev, do: import_config("#{Mix.env()}.exs")

config/dev.exs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
use Mix.Config
2+
3+
config :fields,
4+
encryption_keys: "nMdayQpR0aoasLaq1g94FLba+A+wB44JLko47sVQXMg=,L+ZVX8iheoqgqb22mUpATmMDsvVGtafoAeb0KN5uWf0=",
5+
secret_key_base: "GLH2S6EU0eZt+GSEmb5wEtonWO847hsQ9fck0APr4VgXEdp9EKfni2WO61z0DMOF"

0 commit comments

Comments
 (0)