@@ -7,6 +7,21 @@ defmodule MishkaDeveloperTools.Helper.Crypto do
7
7
It should be brought to your attention that certain functions necessitate the addition of their
8
8
dependencies to the primary project. Consequently, prior to making use of these functionalities,
9
9
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
10
25
"""
11
26
alias Postgrex.Extensions.JSON
12
27
@ type based32_url :: << _ :: 64 , _ :: _ * 8 >>
@@ -230,119 +245,122 @@ defmodule MishkaDeveloperTools.Helper.Crypto do
230
245
end
231
246
end
232
247
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
235
251
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
238
254
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
243
259
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.
248
264
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.
251
267
252
- ---
268
+ ---
253
269
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.
256
272
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.
259
275
260
- ### Exmple:
276
+ ### Exmple:
261
277
262
- ```elixir
263
- create_hash_password("USER_HARD_PASSWORD", :bcrypt)
264
- ```
278
+ ```elixir
279
+ create_hash_password("USER_HARD_PASSWORD", :bcrypt)
280
+ ```
265
281
266
- ---
282
+ ---
267
283
268
- ### Pbkdf2
284
+ ### Pbkdf2
269
285
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
272
288
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.
275
291
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.
278
294
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.
281
297
282
- ### Exmple:
298
+ ### Exmple:
283
299
284
- ```elixir
285
- create_hash_password("USER_HARD_PASSWORD", :pbkdf2)
286
- ```
300
+ ```elixir
301
+ create_hash_password("USER_HARD_PASSWORD", :pbkdf2)
302
+ ```
287
303
288
- ---
304
+ ---
289
305
290
- ### Argon2
306
+ ### Argon2
291
307
292
- Argon2 is the winner of the Password Hashing Competition (PHC).
308
+ Argon2 is the winner of the Password Hashing Competition (PHC).
293
309
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
297
313
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.
300
316
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.
304
320
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
309
325
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):
312
328
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
317
333
318
- - m_cost - memory usage
334
+ - m_cost - memory usage
319
335
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
325
341
326
- - argon2_type - argon2 variant to use
342
+ - argon2_type - argon2 variant to use
327
343
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)
331
347
332
- ---
348
+ ---
333
349
334
- For verifing you can use like this:
350
+ For verifing you can use like this:
335
351
336
- ```elixir
337
- verify_password(hash, "USER_HARD_PASSWORD", :bcrypt)
352
+ ```elixir
353
+ verify_password(hash, "USER_HARD_PASSWORD", :bcrypt)
338
354
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
340
361
341
- verify_password(hash, "USER_HARD_PASSWORD", :argon2)
342
- ```
343
- """
344
- @ spec create_hash_password ( String . t ( ) , :argon2 | :bcrypt | :pbkdf2 ) :: String . t ( )
345
362
if Code . ensure_loaded? ( Bcrypt ) do
363
+ @ spec create_hash_password ( String . t ( ) , :argon2 | :bcrypt | :pbkdf2 ) :: String . t ( )
346
364
def create_hash_password ( password , :bcrypt ) do
347
365
Bcrypt . hash_pwd_salt ( password )
348
366
end
@@ -360,12 +378,12 @@ defmodule MishkaDeveloperTools.Helper.Crypto do
360
378
end
361
379
end
362
380
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 ( )
368
381
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 ( )
369
387
def verify_password ( hash , password , :bcrypt ) do
370
388
Bcrypt . verify_pass ( password , hash )
371
389
end
@@ -578,4 +596,150 @@ defmodule MishkaDeveloperTools.Helper.Crypto do
578
596
else: Token . verify_and_validate! ( token )
579
597
end
580
598
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
581
745
end
0 commit comments