Skip to content

Commit 08562ba

Browse files
committed
adds .env_sample and README.md usage instructions for managing Encryption keys
1 parent 9876f34 commit 08562ba

File tree

2 files changed

+131
-37
lines changed

2 files changed

+131
-37
lines changed

.env_sample

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export SECRET_KEY_BASE=2PzB7PPnpuLsbWmWtXpGyI+kfSQSQ1zUW2Atz/+8PdZuSEJzHgzGnJWV35nTKRwx
2+
export ENCRYPTION_KEYS='nMdayQpR0aoasLaq1g94FLba+A+wB44JLko47sVQXMg=,L+ZVX8iheoqgqb22mUpATmMDsvVGtafoAeb0KN5uWf0='

README.md

Lines changed: 129 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ is quite simple; (_only 4 lines_):<br />
221221
```elixir
222222
def encrypt(plaintext) do
223223
iv = :crypto.strong_rand_bytes(16) # create random Initialization Vector
224-
state = :crypto.stream_init(:aes_ctr, key(), iv) # Initialise crypto stream
224+
state = :crypto.stream_init(:aes_ctr, get_key(), iv) # create crypto stream
225225
# peform the encryption:
226226
{_state, ciphertext} = :crypto.stream_encrypt(state, to_string(plaintext))
227227
iv <> ciphertext # "return" iv concatenated with the ciphertext
@@ -245,9 +245,10 @@ it accepts a "blob" of `ciphertext` (_which as you may recall_),
245245
has the IV prepended to it, and returns the original `plaintext`.
246246

247247
```elixir
248-
def decrypt(ciphertext) do
249-
<<iv::binary-16, ciphertext::binary>> = ciphertext # split iv from ciphertext
250-
state = :crypto.stream_init(:aes_ctr, key(), iv) # create crypto stream
248+
def decrypt(ciphertext, key_id) do
249+
<<iv::binary-16, ciphertext::binary>> = ciphertext # split iv & ciphertext
250+
# get encryption key based on key_id & Initialise crypto stream:
251+
state = :crypto.stream_init(:aes_ctr, get_key(key_id), iv)
251252
# perform decryption
252253
{_state, plaintext} = :crypto.stream_decrypt(state, ciphertext)
253254
plaintext # "return" just the plaintext
@@ -257,34 +258,66 @@ end
257258
The fist step (line) is to "split" the IV from the `ciphertext`
258259
using Elixir's binary pattern matching.
259260

260-
To _understand_ the `<<iv::binary-16, ciphertext::binary>>`
261-
pattern matching line, read the following guide:
261+
> If you are unfamiliar with Elixir binary pattern matching syntax
262+
`<<iv::binary-16, ciphertext::binary>>`
263+
read the following guide:
262264
https://elixir-lang.org/getting-started/binaries-strings-and-char-lists.html
263265

264-
The `state = :crypto.stream_init(:aes_ctr, key(), iv)` line is the _same_
265-
as in the `encrypt` function, this should reassure you that it's the
266-
_same_ setup (init) to `decrypt` as it was to `encrypt`.
266+
The `state = :crypto.stream_init(:aes_ctr, get_key(key_id), iv)` line
267+
is the _same_ as in the `encrypt` function,
268+
_except_ that this time we initialise the stream with a _specific_ key
269+
(_the key that was used to `encrypt` the data originally_).
267270

268-
Then `ciphertext` is decrypted using `stream_decrypt`
271+
`ciphertext` is decrypted using `stream_decrypt`
269272
http://erlang.org/doc/man/crypto.html#stream_decrypt-2
270273

271-
Finally the original `plaintext` is returned.
272-
274+
Finally the original `plaintext` is _returned_.
273275

274276

275277

276278
#### 3.3 Get (Encryption) Key
277279

278-
You will have noticed that both `encrypt` and `decrypt` functions
279-
call a `key()` function on the `state` line.
280+
You will have noticed that _both_ `encrypt` and `decrypt` functions
281+
call a `get_key()` function on the `state = ...` line.
280282
It is not a "built-in" function, we are about to define it!
281283

284+
285+
286+
287+
##### `ENCRYPTION_KEYS` Environment Variable
288+
289+
In order for our `get_key` function to work,
290+
it needs to be know how to "read" the encryption keys.
291+
292+
> _**Note**: we prefer to store our Encryption Keys as
293+
**Environment Variables**
294+
295+
e need to "export" an Environment Variable
296+
containing a (_comma-separated_) list of (_one or more_)
297+
encryption key(s).
298+
_Copy-paste_ (_and run_) the following command into your terminal:
299+
300+
```elixir
301+
echo "export ENCRYPTION_KEYS='nMdayQpR0aoasLaq1g94FLba+A+wB44JLko47sVQXMg=,L+ZVX8iheoqgqb22mUpATmMDsvVGtafoAeb0KN5uWf0='" >> .env && echo ".env" >> .gitignore
302+
```
303+
> For _now_, copy paste this command exactly as it is.<br />
304+
> When you are deploying your own App,
305+
> generate your own AES encryption key(s)
306+
> see below for how to do this.
307+
> _**Note**: there are **two** encryption keys separated by a comma.
308+
This is to **demonstrate** that it's **possible** to use **multiple keys**._
309+
310+
282311
```elixir
283312

284313
```
285314

286315

287316

317+
https://elixirschool.com/en/lessons/specifics/ets/
318+
https://elixir-lang.org/getting-started/mix-otp/ets.html
319+
320+
288321

289322
In your terminal run the following **_three_ commands**:
290323

@@ -365,32 +398,45 @@ far less likely_) as `bcrypt` has a CPU-bound "work-factor".
365398

366399

367400

401+
### How To Generate AES Encryption Keys?
368402

369-
<br /> <br />
403+
Encryption keys should be the appropriate length (in bits)
404+
as required by the chosen algorithm.
370405

371-
## Credits
406+
> An **AES 128-bit** key can be expressed
407+
as a hexadecimal string with 32 characters.
408+
It will require **24 characters** in **base64**.
372409

373-
Credit for this example goes to [@danielberkompas](https://github.com/danielberkompas)
374-
for his post:
375-
https://blog.danielberkompas.com/2015/07/03/encrypting-data-with-ecto<br />
410+
> An **AES 256-bit** key can be expressed
411+
as a hexadecimal string with 64 characters.
412+
It will require **44 characters** in **base64**.
376413

377-
Daniel's post is for [Phoenix `v0.14.0`](https://github.com/danielberkompas/danielberkompas.github.io/blob/c6eb249e5019e782e891bfeb591bc75f084fd97c/_posts/2015-07-03-encrypting-data-with-ecto.md) which is quite "old" now ...
378-
therefore a few changes/updates are required.
379-
e.g: There are no more "**Models**" in Phoenix 1.3 or Ecto callbacks.
414+
see: https://security.stackexchange.com/a/45334/117318
380415

381-
_Also_ his post only includes the "sample code"
382-
and is _not_ a _complete_ example. <br />
383-
Which means anyone following the post needs to _manually_ copy-paste the code...
384-
We prefer to include the _complete_ "end state" of any tutorial so that
385-
people can `git clone` and _`run`_ the code locally.
416+
Open `iex` in your Terminal and paste the following line (_then press enter_)
417+
```elixir
418+
:crypto.strong_rand_bytes(32) |> :base64.encode
419+
```
420+
421+
You should see terminal output similar to the following:
422+
423+
![elixir-generate-encryption-key](https://user-images.githubusercontent.com/194400/38561017-dd93d186-3cce-11e8-91cd-c70f920ac79a.png)
424+
425+
We generated 3 keys for demonstration purposes:
426+
+ "h6pUk0ZccS0pYsibHZZ4Cd+PRO339rMA7sMz7FnmcGs="
427+
+ "nMd/yQpR0aoasLaq1g94FL/a+A+wB44JLko47sVQXMg="
428+
+ "L+ZVX8iheoqgqb22mUpATmMDsvVGt/foAe/0KN5uWf0="
429+
430+
431+
432+
These two Erlang functions are described in:
433+
+ http://erlang.org/doc/man/crypto.html#strong_rand_bytes-1
434+
+ http://erlang.org/doc/man/base64.html#encode-1
435+
436+
Base64 encoding the bytes generated by `strong_rand_bytes`
437+
will make the output human-readable
438+
(_whereas bytes are less user-friendly_).
386439

387-
I reached out to Daniel on Twitter
388-
asking if he would accept a Pull Request
389-
updating the post to latest version of Phoenix: <br />
390-
[![credit-tweet](https://user-images.githubusercontent.com/194400/35771850-32b1cfba-092b-11e8-9bbf-0e693016bb76.png)](https://twitter.com/nelsonic/status/959901100760498181) <br />
391-
If he replies I will _gladly_ create a PR.
392-
_Meanwhile_ this example will fill in the gaps
393-
and provide a more up-to-date example.
394440

395441
<br /> <br />
396442

@@ -403,19 +449,36 @@ https://crypto.stackexchange.com/questions/3615/what-is-the-effect-of-the-differ
403449
+ How is decryption done in AES CTR mode?: https://crypto.stackexchange.com/questions/34918/how-is-decryption-done-in-aes-ctr-mode
404450
+ Block Cipher Counter (CTR) Mode:
405451
https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_.28CTR.29
452+
+ Is AES-256 weaker than 192 and 128 bit versions?
453+
https://crypto.stackexchange.com/questions/5118/is-aes-256-weaker-than-192-and-128-bit-versions
454+
+ What are the practical differences between 256-bit, 192-bit, and 128-bit AES encryption?
455+
https://crypto.stackexchange.com/questions/20/what-are-the-practical-differences-between-256-bit-192-bit-and-128-bit-aes-enc
456+
+ How to choose an Authenticated Encryption mode
457+
(_by Matthew Green cryptography professor at Johns Hopkins University_):
458+
https://blog.cryptographyengineering.com/2012/05/19/how-to-choose-authenticated-encryption/
459+
+ How to choose an AES encryption mode (CBC ECB CTR OCB CFB)? (v. long answers, but good comparison!)
460+
https://stackoverflow.com/questions/1220751/how-to-choose-an-aes-encryption-mode-cbc-ecb-ctr-ocb-cfb
461+
+ AES GCM vs CTR+HMAC tradeoffs:
462+
https://crypto.stackexchange.com/questions/14747/gcm-vs-ctrhmac-tradeoffs
463+
+ Galois/Counter Mode for symmetric key cryptographic block ciphers: https://en.wikipedia.org/wiki/Galois/Counter_Mode
464+
+ How long (in letters) are encryption keys for AES?
465+
https://security.stackexchange.com/questions/45318/how-long-in-letters-are-encryption-keys-for-aes
466+
+ Generate random alphanumeric string (_used for AES keys_)
467+
https://stackoverflow.com/questions/12788799/how-to-generate-a-random-alphanumeric-string-with-erlang
406468
+ Singular or Plural controller names?: https://stackoverflow.com/questions/35882394/phoenix-controllers-singular-or-plural
407-
+ Postgres Data Type for storing `bcrypt` hashed passwords: https://stackoverflow.com/questions/33944199/bcrypt-and-postgresql-what-data-type-should-be-used >> `bytea`
408-
+ https://security.stackexchange.com/questions/4781/do-any-security-experts-recommend-bcrypt-for-password-storage/6415#6415
469+
+ Postgres Data Type for storing `bcrypt` hashed passwords: https://stackoverflow.com/questions/33944199/bcrypt-and-postgresql-what-data-type-should-be-used >> `bytea` (_byte_)
470+
+ Do security experts recommend bcrypt? https://security.stackexchange.com/questions/4781/do-any-security-experts-recommend-bcrypt-for-password-storage/6415#6415
409471
+ Hacker News discussion thread "***Don't use `bcrypt`***":
410472
https://news.ycombinator.com/item?id=3724560
411473

412474

413475
## Troubleshooting
414476

415-
If you get "stuck", please open an issue describing the issue you are facing.
477+
If _you_ get "stuck", please open an issue describing the issue you are facing.
416478

417479
TIL: app names in Phoneix _must_ be lowercase letters: <br />
418480
![lower-case-app-names](https://user-images.githubusercontent.com/194400/35360087-73d69d88-0154-11e8-9f47-d9a9333d1e6c.png)
481+
(_basic, I know, now..._)
419482

420483
Works with lowercase: <br />
421484
![second-time-lucky](https://user-images.githubusercontent.com/194400/35360183-c522063c-0154-11e8-994a-7516bc0e5c1e.png)
@@ -426,3 +489,32 @@ To run a _single_ test while debugging, use the following syntax:
426489
```sh
427490
mix test test/user/user_test.exs:9
428491
```
492+
493+
<br /> <br />
494+
495+
## Credits
496+
497+
Credit for this example goes to [@danielberkompas](https://github.com/danielberkompas)
498+
for his post:
499+
https://blog.danielberkompas.com/2015/07/03/encrypting-data-with-ecto <br />
500+
501+
Daniel's post is for [Phoenix `v0.14.0`](https://github.com/danielberkompas/danielberkompas.github.io/blob/c6eb249e5019e782e891bfeb591bc75f084fd97c/_posts/2015-07-03-encrypting-data-with-ecto.md) which is quite "old" now ...
502+
therefore a few changes/updates are required.
503+
e.g: There are no more "**Models**" in Phoenix 1.3 or Ecto callbacks.
504+
505+
_Also_ his post only includes the "sample code"
506+
and is _not_ a _complete_ example
507+
_explaining_ the functions & Custom Ecto Types. <br />
508+
Which means anyone following the post needs to _manually_ copy-paste the code...
509+
We prefer to include the _complete_ "end state" of any tutorial so that
510+
people can `git clone` and _`run`_ the code locally.
511+
512+
<!--
513+
I reached out to Daniel on Twitter
514+
asking if he would accept a Pull Request
515+
updating the post to latest version of Phoenix: <br />
516+
[![credit-tweet](https://user-images.githubusercontent.com/194400/35771850-32b1cfba-092b-11e8-9bbf-0e693016bb76.png)](https://twitter.com/nelsonic/status/959901100760498181) <br />
517+
If he replies I will _gladly_ create a PR.
518+
_Meanwhile_ this example will fill in the gaps
519+
and provide a more up-to-date example.
520+
-->

0 commit comments

Comments
 (0)