argon_hash_password/
lib.rs1use argon2::{
18 password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
19 Argon2,
20};
21use rand::{distributions::Alphanumeric, Rng};
22use std::error::Error;
23
24pub fn create_hash_and_salt(password: &str) -> Result<(String, String), Box<dyn Error>> {
26 let salt = SaltString::generate(&mut OsRng);
27 let hash = match hash_and_verify(password, salt.clone()) {
28 Ok(hash) => hash,
29 Err(e) => return Err(e),
30 };
31 Ok((hash, salt.to_string()))
32}
33
34pub fn check_password_matches_hash(
36 password: &str,
37 expected_hash: &str,
38 salt: &str,
39) -> Result<bool, Box<dyn Error>> {
40 let parsed_salt = parse_saltstring(salt)?;
41 let hash = match hash_and_verify(password, parsed_salt) {
42 Ok(hash) => hash,
43 Err(e) => return Err(e),
44 };
45 if hash != expected_hash {
46 return Ok(false);
47 }
48 Ok(true)
49}
50
51pub fn verify_password_len(password: &str) -> bool {
53 if password.len() < 8 {
54 return false;
55 }
56 if password.len() > 128 {
57 return false;
58 }
59 true
60}
61
62pub fn parse_saltstring(salt: &str) -> Result<SaltString, Box<dyn Error>> {
63 match SaltString::new(salt) {
64 Ok(parsed_salt) => Ok(parsed_salt),
65 Err(e) => return Err(format!("Failed to parse provided salt: {}", e).into()),
66 }
67}
68
69pub fn gen_session_id() -> String {
71 rand::thread_rng()
72 .sample_iter(&Alphanumeric)
73 .take(128)
74 .map(char::from)
75 .collect()
76}
77
78pub fn hash_and_verify(password: &str, salt: SaltString) -> Result<String, Box<dyn Error>> {
80 let argon2 = Argon2::default();
81 let hash = match argon2.hash_password(password.as_bytes(), &salt) {
82 Ok(hash) => hash.to_string(),
83 Err(e) => return Err(format!("Failed to hash password: {}", e).into()),
84 };
85
86 let parsed_hash = match PasswordHash::new(&hash) {
87 Ok(parsed_hash) => parsed_hash,
88 Err(e) => return Err(format!("Failed parse hash: {}", e).into()),
89 };
90 match argon2.verify_password(password.as_bytes(), &parsed_hash) {
91 Ok(_) => (),
92 Err(e) => {
93 return Err(format!("Failed to verify hashed password: {}", e).into());
94 }
95 }
96 Ok(hash)
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 const PASSWORD: &str = "samplePass123";
104
105 #[test]
106 fn argon_same_password_not_matching_due_to_salt() {
107 let (first_hash, first_salt) =
108 create_hash_and_salt(PASSWORD).expect("Failed to create hashed password first time");
109 let (second_hash, second_salt) =
110 create_hash_and_salt(PASSWORD).expect("Failed to create hashed password second time");
111 assert_ne!(first_hash, second_hash);
112 assert_ne!(first_salt, second_salt);
113 }
114
115 #[test]
116 fn argon_same_password_does_match() {
117 let (hash, salt) =
118 create_hash_and_salt(PASSWORD).expect("Failed to create hashed password");
119 let check_hash = check_password_matches_hash(PASSWORD, &hash, &salt)
120 .expect("Failed to check password hash");
121 assert!(check_hash);
122 }
123
124 #[test]
125 fn argon_different_password_does_not_match() {
126 let (hash, salt) =
127 create_hash_and_salt(PASSWORD).expect("Failed to create hashed password");
128 let check_hash = check_password_matches_hash("aDifferentPassword123", &hash, &salt)
129 .expect("Failed to check password hash");
130 assert!(!check_hash);
131 }
132}