Argon2 passwords
Argon2id password hashes with the memory and passes you choose, any Argon2 hash verified, its parameters read, old ones marked for a rehash, and the cost timed on this server.
Version 1.0.0 · 5 functions · 2 crates
Functions
What use argon2.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use argon2_hash, argon2_verify from argon2 (Quillo 0.6).
argon2_hash(password: str, memory_kib: int, iterations: int, parallelism: int) → stror an errorpassword hashed with Argon2id and a random salt, as a PHC string ($argon2id$v=19$m=65536,t=3,p=1$...). memory_kib is the memory in KiB (19456 is OWASP's floor, 65536 is 64 MiB), iterations the passes, parallelism the lanes.
argon2_verify(password: str, hash: str) → boolWhether password matches hash, an Argon2id, Argon2i or Argon2d PHC string with any parameters (Quillo's own crypto.hash_password, PHP's password_hash, Django's, libsodium's). False for a wrong password or a hash that is not Argon2.
argon2_params(hash: str) → anyor an errorWhat a hash was made with: { algorithm, version, memory_kib, iterations, parallelism, salt_bytes, hash_bytes }.
argon2_needs_rehash(hash: str, memory_kib: int, iterations: int, parallelism: int) → boolWhether a stored hash should be replaced, at the next sign-in, by one made with these parameters: true when it is not Argon2id version 19, or uses less memory, fewer passes or other lanes than asked. True also for a hash that is not Argon2 at all.
argon2_tune(target_ms: int, memory_kib: int, parallelism: int) → anyor an errorThe passes that make one hash take about target_ms on this server with memory_kib and parallelism, measured: { iterations, ms, memory_kib, parallelism }. Run it once on the production machine, then hash with what it says.
Get it
$ quillo install argon2 # argon2.qwer into this folder$ quillo install argon2 --example # and its example, argon2.qweOr with curl:
$ curl -O https://quillolang.org/plugins/files/argon2.qwer$ curl -O https://quillolang.org/plugins/files/argon2.qweThen, in your program, all of it or only what you call:
use argon2.qweruse argon2_hash, argon2_verify from argon2 // Quillo 0.6: only theseIt uses these crates, which cargo fetches the first time the plugin is built:
argon2 = "0.5"rand_core = { version = "0.6", features = ["getrandom"] }Example
// Passwords hashed with a stronger Argon2id than the default, and users moved to it as they sign in.//// quillo run argon2.qwe then open http://localhost:8080/hash and http://localhost:8080/rehashuse "argon2.qwer"// 64 MiB, 3 passes, 1 lane: RFC 9106's second choice, for servers with memory to spare.let MEMORY = 65536let PASSES = 3let LANES = 1route GET /hash { let hash = argon2_hash("correct horse battery staple", MEMORY, PASSES, LANES)? { starts: slice(hash, 0, 30), verified: argon2_verify("correct horse battery staple", hash), wrong: argon2_verify("Tr0ub4dor&3", hash), params: argon2_params(hash)?, }}// A hash from Quillo's own crypto.hash_password still verifies, and is marked for a rehash// with the stronger cost; one made with it is not.route GET /rehash { let old = crypto.hash_password("hunter2") let fresh = argon2_hash("hunter2", MEMORY, PASSES, LANES)? { builtin: argon2_params(old)?, builtin_verified: argon2_verify("hunter2", old), builtin_needs_rehash: argon2_needs_rehash(old, MEMORY, PASSES, LANES), fresh_needs_rehash: argon2_needs_rehash(fresh, MEMORY, PASSES, LANES), bcrypt_needs_rehash: argon2_needs_rehash("$2b$12$R9h/cIPz0gi.URNNX3kh2OPST9/PgBkqquzi.Ss7KIUgO2t0jWMUW", MEMORY, PASSES, LANES), }}// How many passes make a hash take about 250 ms here, with 19 MiB.route GET /tune = argon2_tune(250, 19456, 1)?$ quillo run argon2.qwe$ curl 'localhost:8080/hash'$ curl 'localhost:8080/rehash'Source
//! Argon2 password hashes with the cost you choose: hash with tuned memory and passes, verify any Argon2 hash (id, i or d), read a hash's parameters, tell when one should be rehashed, and time the cost on this server.//! version: 1.0.0//!//! [dependencies]//! argon2 = "0.5"//! rand_core = { version = "0.6", features = ["getrandom"] }use argon2::password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString};use argon2::{Algorithm, Argon2, Params, Version};use rand_core::OsRng;use serde_json::{json, Value};fn params(memory_kib: i64, iterations: i64, parallelism: i64) -> Result<Params, String> { if memory_kib < 8 || iterations < 1 || parallelism < 1 { return Err("memory_kib is at least 8, iterations and parallelism at least 1".into()); } if memory_kib > 4 * 1024 * 1024 { return Err("memory_kib above 4 GiB is surely a mistake".into()); } Params::new(memory_kib as u32, iterations as u32, parallelism as u32, None).map_err(|e| e.to_string())}fn hasher(memory_kib: i64, iterations: i64, parallelism: i64) -> Result<Argon2<'static>, String> { Ok(Argon2::new(Algorithm::Argon2id, Version::V0x13, params(memory_kib, iterations, parallelism)?))}fn parsed(hash: &str) -> Result<PasswordHash<'_>, String> { let h = PasswordHash::new(hash.trim()).map_err(|e| format!("not a PHC hash string ($argon2id$v=19$m=...,t=...,p=...$salt$hash): {}", e))?; if !h.algorithm.as_str().starts_with("argon2") { return Err(format!("a {} hash, not Argon2", h.algorithm)); } Ok(h)}fn param(h: &PasswordHash, name: &str) -> i64 { h.params.get_decimal(name).map(|v| v as i64).unwrap_or(0)}/// `password` hashed with Argon2id and a random salt, as a PHC string (`$argon2id$v=19$m=65536,t=3,p=1$...`)./// `memory_kib` is the memory in KiB (19456 is OWASP's floor, 65536 is 64 MiB), `iterations` the passes, `parallelism` the lanes.pub fn argon2_hash(password: &str, memory_kib: i64, iterations: i64, parallelism: i64) -> Result<String, String> { let salt = SaltString::generate(&mut OsRng); let a = hasher(memory_kib, iterations, parallelism)?; a.hash_password(password.as_bytes(), &salt).map(|h| h.to_string()).map_err(|e| e.to_string())}/// Whether `password` matches `hash`, an Argon2id, Argon2i or Argon2d PHC string with any parameters/// (Quillo's own `crypto.hash_password`, PHP's `password_hash`, Django's, libsodium's). False for a wrong password or a hash that is not Argon2.pub fn argon2_verify(password: &str, hash: &str) -> bool { match parsed(hash) { Ok(h) => Argon2::default().verify_password(password.as_bytes(), &h).is_ok(), Err(_) => false, }}/// What a hash was made with: `{ algorithm, version, memory_kib, iterations, parallelism, salt_bytes, hash_bytes }`.pub fn argon2_params(hash: &str) -> Result<Value, String> { let h = parsed(hash)?; let salt_bytes = h.salt.map(|s| { let mut buf = [0u8; 64]; s.decode_b64(&mut buf).map(|b| b.len()).unwrap_or(0) }); Ok(json!({ "algorithm": h.algorithm.as_str(), "version": h.version.unwrap_or(0x10), "memory_kib": param(&h, "m"), "iterations": param(&h, "t"), "parallelism": param(&h, "p"), "salt_bytes": salt_bytes, "hash_bytes": h.hash.map(|o| o.len()), }))}/// Whether a stored hash should be replaced, at the next sign-in, by one made with these parameters:/// true when it is not Argon2id version 19, or uses less memory, fewer passes or other lanes than asked. True also for a hash that is not Argon2 at all.pub fn argon2_needs_rehash(hash: &str, memory_kib: i64, iterations: i64, parallelism: i64) -> bool { let Ok(h) = parsed(hash) else { return true }; h.algorithm.as_str() != "argon2id" || h.version != Some(0x13) || param(&h, "m") < memory_kib || param(&h, "t") < iterations || param(&h, "p") != parallelism}/// The passes that make one hash take about `target_ms` on this server with `memory_kib` and `parallelism`, measured:/// `{ iterations, ms, memory_kib, parallelism }`. Run it once on the production machine, then hash with what it says.pub fn argon2_tune(target_ms: i64, memory_kib: i64, parallelism: i64) -> Result<Value, String> { let target = target_ms.clamp(1, 10_000) as u128; let mut t = 1i64; loop { let a = hasher(memory_kib, t, parallelism)?; let mut out = [0u8; 32]; let start = std::time::Instant::now(); a.hash_password_into(b"tuning the cost", b"a salt of 16 b..", &mut out).map_err(|e| e.to_string())?; let ms = start.elapsed().as_millis(); if ms >= target || t >= 64 { return Ok(json!({ "iterations": t, "ms": ms as i64, "memory_kib": memory_kib, "parallelism": parallelism })); } // Time grows with the passes, so jump close to the target, then step. let next = ((t as u128 * target) / ms.max(1)) as i64; t = next.clamp(t + 1, t * 4); }}Related
bcrypt.qwer · 1.0.0Sign in users whose bcrypt hashes came from PHP, Rails, Django or Laravel, then move them to Quillo's Argon2.8 views · 9 downloadsLegacy passwords legacy_passwords.qwer · 1.0.0Check passwords against Django, Flask/Werkzeug and WordPress hashes, to sign in users moved from those apps and rehash them with Argon2.7 views · 7 downloadsScrypt passwords scrypt.qwer · 1.0.0Scrypt password hashes made, checked and marked for a rehash, the raw key Node's crypto.scrypt makes, and Firebase Authentication's modified scrypt, to sign in users exported from Firebase.6 views · 7 downloadsPassword strength password_strength.qwer · 1.0.0How guessable a password is (zxcvbn): a score from 0 to 4, a warning and suggestions, and how long it would take to crack.6 views · 8 downloadsPwned passwords pwned.qwer · 1.0.0Have I Been Pwned's Pwned Passwords by k-anonymity: only 5 characters of the SHA-1 leave the server, the answer is checked here.8 views · 9 downloadsRecovery codes mfa_codes.qwer · 1.0.0Two-factor recovery codes: a set made and shown once, stored only as hashes, each one usable a single time.7 views · 6 downloadsThread 0 posts
On Argon2 passwords: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.