@@ -211,7 +211,9 @@ sensitive information and can be stored in plaintext.
211
211
212
212
## Testing
213
213
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:
215
217
216
218
```
217
219
ENCRYPTION_KEYS="key1,key2" SECRET_KEY_BASE="key" mix test
@@ -224,6 +226,9 @@ that is not already in the **`Fields`** package,
224
226
please open an issue so we can add it!
225
227
[ github.com/dwyl/fields/issues] ( https://github.com/dwyl/fields/issues )
226
228
229
+ <br />
230
+
231
+
227
232
<br />
228
233
229
234
## Background / Further Reading 🔗
@@ -235,3 +240,199 @@ encryption/decryption works using Ecto Types, <br />see:
235
240
If you are rusty/new on Binaries in Elixir,
236
241
take a look at this post by @blackode : <br />
237
242
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
+
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
+
0 commit comments