Skip to main content

tuwunel_core/utils/hash/
argon.rs

1use std::sync::OnceLock;
2
3use argon2::{
4	Algorithm, Argon2, Params, PasswordHash, PasswordHasher, PasswordVerifier, Version,
5	password_hash,
6	password_hash::{Salt, SaltString},
7};
8
9use crate::{Error, Result, err};
10
11const M_COST: u32 = Params::DEFAULT_M_COST; // memory size in 1 KiB blocks
12const T_COST: u32 = Params::DEFAULT_T_COST; // nr of iterations
13const P_COST: u32 = Params::DEFAULT_P_COST; // parallelism
14
15static ARGON: OnceLock<Argon2<'static>> = OnceLock::new();
16
17fn init_argon() -> Argon2<'static> {
18	// 19456 Kib blocks, iterations = 2, parallelism = 1
19	// * <https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#argon2id>
20	debug_assert!(M_COST == 19_456, "M_COST default changed");
21	debug_assert!(T_COST == 2, "T_COST default changed");
22	debug_assert!(P_COST == 1, "P_COST default changed");
23
24	let algorithm = Algorithm::Argon2id;
25	let version = Version::default();
26	let out_len: Option<usize> = None;
27	let params = Params::new(M_COST, T_COST, P_COST, out_len).expect("valid parameters");
28	Argon2::new(algorithm, version, params)
29}
30
31pub(super) fn password(password: &str) -> Result<String> {
32	let mut bytes = [0_u8; Salt::RECOMMENDED_LENGTH];
33	rand::fill(&mut bytes);
34
35	let salt = SaltString::encode_b64(&bytes).map_err(map_err)?;
36	ARGON
37		.get_or_init(init_argon)
38		.hash_password(password.as_bytes(), &salt)
39		.map(|it| it.to_string())
40		.map_err(map_err)
41}
42
43pub(super) fn verify_password(password: &str, password_hash: &str) -> Result {
44	let password_hash = PasswordHash::new(password_hash).map_err(map_err)?;
45	ARGON
46		.get_or_init(init_argon)
47		.verify_password(password.as_bytes(), &password_hash)
48		.map_err(map_err)
49}
50
51fn map_err(e: password_hash::Error) -> Error { err!("{e}") }
52
53#[cfg(test)]
54mod tests {
55	#[test]
56	fn password_hash_and_verify() {
57		use crate::utils::hash;
58		let preimage = "temp123";
59		let digest = hash::password(preimage).expect("digest");
60		hash::verify_password(preimage, &digest).expect("verified");
61	}
62
63	#[test]
64	#[should_panic(expected = "unverified")]
65	fn password_hash_and_verify_fail() {
66		use crate::utils::hash;
67		let preimage = "temp123";
68		let fakeimage = "temp321";
69		let digest = hash::password(preimage).expect("digest");
70		hash::verify_password(fakeimage, &digest).expect("unverified");
71	}
72}