Skip to content

Commit ed8fa9e

Browse files
committed
Add plug encryptions and sign to crypto module
1 parent 8520640 commit ed8fa9e

File tree

3 files changed

+250
-82
lines changed

3 files changed

+250
-82
lines changed

lib/helper/crypto.ex

+246-82
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,21 @@ defmodule MishkaDeveloperTools.Helper.Crypto do
77
It should be brought to your attention that certain functions necessitate the addition of their
88
dependencies to the primary project. Consequently, prior to making use of these functionalities,
99
establish the appropriate dependence within the project in accordance with your requirements.
10+
11+
These functions are custom or wrappers, Copy from:
12+
13+
- https://hexdocs.pm/plug_crypto/2.0.0/Plug.Crypto.html
14+
- https://hexdocs.pm/phoenix/Phoenix.Token.html
15+
- https://github.com/dashbitco/nimble_totp/blob/master/lib/nimble_totp.ex
16+
- https://dashbit.co/blog/introducing-nimble-totp
17+
- https://hex.pm/packages/bcrypt_elixir
18+
- https://hex.pm/packages/pbkdf2_elixir
19+
- https://password-hashing.net/
20+
- https://hex.pm/packages/argon2_elixir
21+
- https://github.com/malach-it/boruta_auth
22+
- https://github.com/joken-elixir/joken
23+
- https://hexdocs.pm/phoenix/mix_phx_gen_auth.html
24+
- https://hexdocs.pm/phoenix/Mix.Tasks.Phx.Gen.Secret.html
1025
"""
1126
alias Postgrex.Extensions.JSON
1227
@type based32_url :: <<_::64, _::_*8>>
@@ -230,119 +245,122 @@ defmodule MishkaDeveloperTools.Helper.Crypto do
230245
end
231246
end
232247

233-
@doc """
234-
### Bcrypt
248+
if Code.ensure_loaded?(Bcrypt) or Code.ensure_loaded?(Pbkdf2) or Code.ensure_loaded?(Argon2) do
249+
@doc """
250+
### Bcrypt
235251
236-
- `bcrypt_elixir`: https://hex.pm/packages/bcrypt_elixir
237-
- `LICENSE`: https://github.com/riverrun/comeonin/blob/master/LICENSE
252+
- `bcrypt_elixir`: https://hex.pm/packages/bcrypt_elixir
253+
- `LICENSE`: https://github.com/riverrun/comeonin/blob/master/LICENSE
238254
239-
> #### Use cases information {: .warning}
240-
>
241-
> Make sure you have a `C compiler` installed. See the Comeonin wiki for details.
242-
> Wiki link: https://github.com/riverrun/comeonin/wiki/Requirements
255+
> #### Use cases information {: .warning}
256+
>
257+
> Make sure you have a `C compiler` installed. See the Comeonin wiki for details.
258+
> Wiki link: https://github.com/riverrun/comeonin/wiki/Requirements
243259
244-
Bcrypt is a key derivation function for passwords designed by Niels
245-
Provos and David Mazières. Bcrypt is an adaptive function, which means
246-
that it can be configured to remain slow and resistant to brute-force
247-
attacks even as computational power increases.
260+
Bcrypt is a key derivation function for passwords designed by Niels
261+
Provos and David Mazières. Bcrypt is an adaptive function, which means
262+
that it can be configured to remain slow and resistant to brute-force
263+
attacks even as computational power increases.
248264
249-
Bcrypt has no known vulnerabilities and has been widely tested for over 15 years.
250-
However, as it has a low memory use, it is susceptible to GPU cracking attacks.
265+
Bcrypt has no known vulnerabilities and has been widely tested for over 15 years.
266+
However, as it has a low memory use, it is susceptible to GPU cracking attacks.
251267
252-
---
268+
---
253269
254-
You are required to make use of this function in order to generate an irreversible (hashed)
255-
duplicate of the user's password when you are storing your password.
270+
You are required to make use of this function in order to generate an irreversible (hashed)
271+
duplicate of the user's password when you are storing your password.
256272
257-
Additionally, you should save it in the database together with other unique features
258-
of your unique program.
273+
Additionally, you should save it in the database together with other unique features
274+
of your unique program.
259275
260-
### Exmple:
276+
### Exmple:
261277
262-
```elixir
263-
create_hash_password("USER_HARD_PASSWORD", :bcrypt)
264-
```
278+
```elixir
279+
create_hash_password("USER_HARD_PASSWORD", :bcrypt)
280+
```
265281
266-
---
282+
---
267283
268-
### Pbkdf2
284+
### Pbkdf2
269285
270-
- `pbkdf2` - pbkdf2_elixir https://hex.pm/packages/pbkdf2_elixir
271-
- `LICENSE`: https://github.com/riverrun/pbkdf2_elixir/blob/master/LICENSE.md
286+
- `pbkdf2` - pbkdf2_elixir https://hex.pm/packages/pbkdf2_elixir
287+
- `LICENSE`: https://github.com/riverrun/pbkdf2_elixir/blob/master/LICENSE.md
272288
273-
Pbkdf2 is a password-based key derivation function that uses a password, a variable-length
274-
salt and an iteration count and applies a pseudorandom function to these to produce a key.
289+
Pbkdf2 is a password-based key derivation function that uses a password, a variable-length
290+
salt and an iteration count and applies a pseudorandom function to these to produce a key.
275291
276-
Pbkdf2 has no known vulnerabilities and has been widely tested for over 15 years.
277-
However, like Bcrypt, as it has a low memory use, it is susceptible to GPU cracking attacks.
292+
Pbkdf2 has no known vulnerabilities and has been widely tested for over 15 years.
293+
However, like Bcrypt, as it has a low memory use, it is susceptible to GPU cracking attacks.
278294
279-
The original implementation of Pbkdf2 used SHA-1 as the pseudorandom function,
280-
but this version uses HMAC-SHA-512, the default, or HMAC-SHA-256.
295+
The original implementation of Pbkdf2 used SHA-1 as the pseudorandom function,
296+
but this version uses HMAC-SHA-512, the default, or HMAC-SHA-256.
281297
282-
### Exmple:
298+
### Exmple:
283299
284-
```elixir
285-
create_hash_password("USER_HARD_PASSWORD", :pbkdf2)
286-
```
300+
```elixir
301+
create_hash_password("USER_HARD_PASSWORD", :pbkdf2)
302+
```
287303
288-
---
304+
---
289305
290-
### Argon2
306+
### Argon2
291307
292-
Argon2 is the winner of the Password Hashing Competition (PHC).
308+
Argon2 is the winner of the Password Hashing Competition (PHC).
293309
294-
- https://password-hashing.net/
295-
- `argon2`: argon2_elixir https://hex.pm/packages/argon2_elixir (recommended)
296-
- https://github.com/riverrun/argon2_elixir/blob/master/LICENSE.md
310+
- https://password-hashing.net/
311+
- `argon2`: argon2_elixir https://hex.pm/packages/argon2_elixir (recommended)
312+
- https://github.com/riverrun/argon2_elixir/blob/master/LICENSE.md
297313
298-
Argon2 is a memory-hard password hashing function which can be used to hash passwords for credential
299-
storage, key derivation, or other applications.
314+
Argon2 is a memory-hard password hashing function which can be used to hash passwords for credential
315+
storage, key derivation, or other applications.
300316
301-
Being memory-hard means that it is not only computationally expensive, but it also uses a
302-
lot of memory (which can be configured). This means that it is much more difficult
303-
to attack Argon2 hashes using GPUs or dedicated hardware.
317+
Being memory-hard means that it is not only computationally expensive, but it also uses a
318+
lot of memory (which can be configured). This means that it is much more difficult
319+
to attack Argon2 hashes using GPUs or dedicated hardware.
304320
305-
> #### Use cases information {: .warning}
306-
>
307-
> Make sure you have a `C compiler` installed. See the Comeonin wiki for details.
308-
> Wiki link: https://github.com/riverrun/comeonin/wiki/Requirements
321+
> #### Use cases information {: .warning}
322+
>
323+
> Make sure you have a `C compiler` installed. See the Comeonin wiki for details.
324+
> Wiki link: https://github.com/riverrun/comeonin/wiki/Requirements
309325
310-
#### Configuration
311-
The following four parameters can be set in the config file (these can all be overridden using keyword options):
326+
#### Configuration
327+
The following four parameters can be set in the config file (these can all be overridden using keyword options):
312328
313-
- t_cost - time cost
314-
> the amount of computation, given in number of iterations
315-
>
316-
> 3 is the default
329+
- t_cost - time cost
330+
> the amount of computation, given in number of iterations
331+
>
332+
> 3 is the default
317333
318-
- m_cost - memory usage
334+
- m_cost - memory usage
319335
320-
> 16 is the default - this will produce a memory usage of 64 MiB (2 ^ 16 KiB)
321-
>
322-
> parallelism - number of parallel threads
323-
>
324-
> 4 is the default
336+
> 16 is the default - this will produce a memory usage of 64 MiB (2 ^ 16 KiB)
337+
>
338+
> parallelism - number of parallel threads
339+
>
340+
> 4 is the default
325341
326-
- argon2_type - argon2 variant to use
342+
- argon2_type - argon2 variant to use
327343
328-
> 0 (Argon2d), 1 (Argon2i) or 2 (Argon2id)
329-
>
330-
> 2 is the default (Argon2id)
344+
> 0 (Argon2d), 1 (Argon2i) or 2 (Argon2id)
345+
>
346+
> 2 is the default (Argon2id)
331347
332-
---
348+
---
333349
334-
For verifing you can use like this:
350+
For verifing you can use like this:
335351
336-
```elixir
337-
verify_password(hash, "USER_HARD_PASSWORD", :bcrypt)
352+
```elixir
353+
verify_password(hash, "USER_HARD_PASSWORD", :bcrypt)
338354
339-
verify_password(hash, "USER_HARD_PASSWORD", :pbkdf2)
355+
verify_password(hash, "USER_HARD_PASSWORD", :pbkdf2)
356+
357+
verify_password(hash, "USER_HARD_PASSWORD", :argon2)
358+
```
359+
"""
360+
end
340361

341-
verify_password(hash, "USER_HARD_PASSWORD", :argon2)
342-
```
343-
"""
344-
@spec create_hash_password(String.t(), :argon2 | :bcrypt | :pbkdf2) :: String.t()
345362
if Code.ensure_loaded?(Bcrypt) do
363+
@spec create_hash_password(String.t(), :argon2 | :bcrypt | :pbkdf2) :: String.t()
346364
def create_hash_password(password, :bcrypt) do
347365
Bcrypt.hash_pwd_salt(password)
348366
end
@@ -360,12 +378,12 @@ defmodule MishkaDeveloperTools.Helper.Crypto do
360378
end
361379
end
362380

363-
@doc """
364-
For information See `create_hash_password/2`.
365-
"""
366-
@spec verify_password(binary(), String.t(), :argon2 | :bcrypt | :bcrypt_2b | :pbkdf2) ::
367-
boolean()
368381
if Code.ensure_loaded?(Bcrypt) do
382+
@doc """
383+
For information See `create_hash_password/2`.
384+
"""
385+
@spec verify_password(binary(), String.t(), :argon2 | :bcrypt | :bcrypt_2b | :pbkdf2) ::
386+
boolean()
369387
def verify_password(hash, password, :bcrypt) do
370388
Bcrypt.verify_pass(password, hash)
371389
end
@@ -578,4 +596,150 @@ defmodule MishkaDeveloperTools.Helper.Crypto do
578596
else: Token.verify_and_validate!(token)
579597
end
580598
end
599+
600+
if Code.ensure_loaded?(Plug) do
601+
@doc """
602+
Encodes, encrypts, and signs data into a token you can send to
603+
clients. Its usage is identical to that of `sign/4`, but the data
604+
is extracted using `decrypt/4`, rather than `verify/4`.
605+
606+
- Copy from: https://github.com/phoenixframework/phoenix/blob/v1.7.12/lib/phoenix/token.ex
607+
608+
## Options
609+
610+
* `:key_iterations` - option passed to `Plug.Crypto.KeyGenerator`
611+
when generating the encryption and signing keys. Defaults to 1000
612+
* `:key_length` - option passed to `Plug.Crypto.KeyGenerator`
613+
when generating the encryption and signing keys. Defaults to 32
614+
* `:key_digest` - option passed to `Plug.Crypto.KeyGenerator`
615+
when generating the encryption and signing keys. Defaults to `:sha256`
616+
* `:signed_at` - set the timestamp of the token in seconds.
617+
Defaults to `System.system_time(:second)`
618+
* `:max_age` - the default maximum age of the token. Defaults to
619+
86400 seconds (1 day) and it may be overridden on `decrypt/4`.
620+
"""
621+
def encrypt(key_base, secret, data, opts \\ []) when is_binary(secret) do
622+
key_base
623+
|> Plug.Crypto.encrypt(secret, data, opts)
624+
end
625+
626+
@doc """
627+
Decrypts the original data from the token and verifies its integrity.
628+
Its usage is identical to `verify/4` but for encrypted tokens.
629+
630+
- Copy from: https://github.com/phoenixframework/phoenix/blob/v1.7.12/lib/phoenix/token.ex
631+
632+
## Options
633+
634+
* `:key_iterations` - option passed to `Plug.Crypto.KeyGenerator`
635+
when generating the encryption and signing keys. Defaults to 1000
636+
* `:key_length` - option passed to `Plug.Crypto.KeyGenerator`
637+
when generating the encryption and signing keys. Defaults to 32
638+
* `:key_digest` - option passed to `Plug.Crypto.KeyGenerator`
639+
when generating the encryption and signing keys. Defaults to `:sha256`
640+
* `:max_age` - verifies the token only if it has been generated
641+
"max age" ago in seconds. Defaults to the max age signed in the
642+
token by `encrypt/4`.
643+
"""
644+
def decrypt(key_base, secret, token, opts \\ []) when is_binary(secret) do
645+
key_base
646+
|> Plug.Crypto.decrypt(secret, token, opts)
647+
end
648+
649+
@doc """
650+
Decodes the original data from the token and verifies its integrity.
651+
652+
## Examples
653+
654+
In this scenario we will create a token, sign it, then provide it to a client
655+
application.
656+
657+
```elixir
658+
iex> user_id = 99
659+
iex> secret = "kjoy3o1zeidquwy1398juxzldjlksahdk3"
660+
iex> namespace = "user auth"
661+
iex> token = sign(secret, namespace, user_id)
662+
```
663+
664+
The mechanism for passing the token to the client is typically through a
665+
cookie, a JSON response body, or HTTP header. For now, assume the client has
666+
received a token it can use to validate requests for protected resources.
667+
668+
When the server receives a request, it can use `verify/4` to determine if it
669+
should provide the requested resources to the client:
670+
671+
```elixir
672+
iex> verify(secret, namespace, token, max_age: 86400)
673+
{:ok, 99}
674+
```
675+
676+
In this example, we know the client sent a valid token because `verify/4`
677+
returned a tuple of type `{:ok, user_id}`. The server can now proceed with
678+
the request.
679+
680+
However, if the client had sent an expired token, an invalid token, or `nil`,
681+
`verify/4` would have returned an error instead:
682+
683+
```elixir
684+
iex> verify(secret, namespace, expired, max_age: 86400)
685+
{:error, :expired}
686+
687+
iex> verify(secret, namespace, invalid, max_age: 86400)
688+
{:error, :invalid}
689+
690+
iex> verify(secret, namespace, nil, max_age: 86400)
691+
{:error, :missing}
692+
```
693+
694+
## Options
695+
696+
* `:key_iterations` - option passed to `Plug.Crypto.KeyGenerator`
697+
when generating the encryption and signing keys. Defaults to 1000
698+
* `:key_length` - option passed to `Plug.Crypto.KeyGenerator`
699+
when generating the encryption and signing keys. Defaults to 32
700+
* `:key_digest` - option passed to `Plug.Crypto.KeyGenerator`
701+
when generating the encryption and signing keys. Defaults to `:sha256`
702+
* `:max_age` - verifies the token only if it has been generated
703+
"max age" ago in seconds. Defaults to the max age signed in the
704+
token by `sign/4`.
705+
"""
706+
def verify(key_base, salt, token, opts \\ []) when is_binary(salt) do
707+
key_base
708+
|> Plug.Crypto.verify(salt, token, opts)
709+
end
710+
711+
@doc """
712+
Encodes and signs data into a token you can send to clients.
713+
714+
## Options
715+
716+
* `:key_iterations` - option passed to `Plug.Crypto.KeyGenerator`
717+
when generating the encryption and signing keys. Defaults to 1000
718+
* `:key_length` - option passed to `Plug.Crypto.KeyGenerator`
719+
when generating the encryption and signing keys. Defaults to 32
720+
* `:key_digest` - option passed to `Plug.Crypto.KeyGenerator`
721+
when generating the encryption and signing keys. Defaults to `:sha256`
722+
* `:signed_at` - set the timestamp of the token in seconds.
723+
Defaults to `System.system_time(:second)`
724+
* `:max_age` - the default maximum age of the token. Defaults to
725+
86400 seconds (1 day) and it may be overridden on `verify/4`.
726+
727+
### Example:
728+
```elixir
729+
key_base = random_key_base()
730+
sign(key_base, "user-secret", {:elixir, :terms})
731+
```
732+
"""
733+
def sign(key_base, salt, data, opts \\ []) when is_binary(salt) do
734+
key_base
735+
|> Plug.Crypto.sign(salt, data, opts)
736+
end
737+
738+
@doc false
739+
def random_key_base(length \\ 64) when length > 31 do
740+
:crypto.strong_rand_bytes(length)
741+
|> Base.encode64(padding: false)
742+
|> binary_part(0, length)
743+
end
744+
end
581745
end

mix.exs

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ defmodule MishkaDeveloperTools.MixProject do
5050
{:nimble_totp, "~> 1.0", optional: true},
5151
{:joken, "~> 2.6", optional: true},
5252
{:jason, "~> 1.4", optional: true},
53+
{:plug, "~> 1.15", optional: true},
5354
# Make sure you have a C compiler installed. See the Comeonin wiki for details.
5455
# Wiki link: https://github.com/riverrun/comeonin/wiki/Requirements
5556
{:bcrypt_elixir, "~> 3.1", optional: true},

0 commit comments

Comments
 (0)