From ee98911a194aa1c2c37eec9de6f2cd4feaafb8a7 Mon Sep 17 00:00:00 2001 From: Andre Boechat Date: Tue, 4 Feb 2025 04:37:43 -0300 Subject: [PATCH] Cookbook recipe for hashing passwords with Argon2 (#2944) --- .../00-hashargon2.ml | 54 +++++++++++++++++++ data/cookbook/tasks.yml | 2 + 2 files changed, 56 insertions(+) create mode 100644 data/cookbook/salt-and-hash-password-with-argon2/00-hashargon2.ml diff --git a/data/cookbook/salt-and-hash-password-with-argon2/00-hashargon2.ml b/data/cookbook/salt-and-hash-password-with-argon2/00-hashargon2.ml new file mode 100644 index 0000000000..3b8a1f70a0 --- /dev/null +++ b/data/cookbook/salt-and-hash-password-with-argon2/00-hashargon2.ml @@ -0,0 +1,54 @@ +--- +packages: +- name: argon2 + tested_version: "1.0.2" + used_libraries: + - argon2 +--- + +(* Configuration for password hashing based on OWASP recommendations and Argon2 + default values. Note this example uses only the recommended "Argon2id" + variation. + References: + - https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html + - https://github.com/P-H-C/phc-winner-argon2 *) +let t_cost = 2 and + m_cost = 65536 and + parallelism = 1 and + hash_len = 32 and + salt_len = 10 + +(* The hash output length. *) +let encoded_len = + Argon2.encoded_len ~t_cost ~m_cost ~parallelism ~salt_len ~hash_len ~kind:ID + +(* Generate a salt string for a given length using only characters in the range + "A-Z". This is a simplistic generator and should be replaced with + cryptographically secure random number generator ("mirage-crypto", for + example). *) +let gen_salt len = + let rand_char _ = 65 + (Random.int 26) |> char_of_int in + String.init len rand_char + +(* Return an encoded hash string for the given password. The encoded password + contains the required metadata for future verification. *) +let hash_password passwd = + Result.map Argon2.ID.encoded_to_string + (Argon2.ID.hash_encoded + ~t_cost ~m_cost ~parallelism ~hash_len ~encoded_len + ~pwd:passwd ~salt:(gen_salt salt_len)) + +(* Verifie if the encoded hash string matches the given password. *) +let verify encoded_hash pwd = + match Argon2.verify ~encoded:encoded_hash ~pwd ~kind:ID with + | Ok true_or_false -> true_or_false + | Error VERIFY_MISMATCH -> false + | Error e -> raise (Failure (Argon2.ErrorCodes.message e)) + +let () = + let hashed_pwd = Result.get_ok (hash_password "my insecure password") in + Printf.printf "Hashed password: %s\n" hashed_pwd; + let fst_attempt = "my secure password" in + Printf.printf "'%s' is correct? %B\n" fst_attempt (verify hashed_pwd fst_attempt); + let snd_attempt = "my insecure password" in + Printf.printf "'%s' is correct? %B\n" snd_attempt (verify hashed_pwd snd_attempt) diff --git a/data/cookbook/tasks.yml b/data/cookbook/tasks.yml index a359210ac9..bb78481a4c 100644 --- a/data/cookbook/tasks.yml +++ b/data/cookbook/tasks.yml @@ -56,6 +56,8 @@ categories: slug: calculate-sha-256-digest-of-file - title: Sign and Verify a Message With an HMAC Digest slug: sign-and-verify-hmac-digest + - title: Salt and Hash a Password With Argon2 + slug: salt-and-hash-password-with-argon2 - title: Salt and Hash a Password With PBKDF2 slug: salt-and-hash-password-with-pkgdf2 - title: Data Structures & Algorithms