extensible_encrypter/hasher/
pbkdf2.rs1use crate::error;
2use pbkdf2::{
3 password_hash::{Ident, PasswordHasher, SaltString},
4 Pbkdf2,
5};
6use rand_chacha::rand_core::SeedableRng;
7use rand_chacha::ChaCha12Rng;
8
9pub enum Algorithm {
10 Pbkdf2Sha256,
11 Pbkdf2Sha512,
12}
13
14pub struct Hasher;
15
16impl Hasher {
17 pub fn hash(
27 password: &str,
28 rounds: &u32,
29 algorithm: Algorithm,
30 override_salt: Option<SaltString>,
31 ) -> error::Result<HasherResult>
32where {
33 let mut rng = ChaCha12Rng::from_os_rng();
35 let mut salt = SaltString::from_rng(&mut rng);
36 if let Some(value) = override_salt {
37 salt = SaltString::from_b64(value.as_str()).expect("salt is base64 encoded");
38 }
39
40 let algo = match algorithm {
42 Algorithm::Pbkdf2Sha256 => Ident::new("pbkdf2-sha256").expect("use SHA-256"),
43 Algorithm::Pbkdf2Sha512 => Ident::new("pbkdf2-sha512").expect("use SHA-512"),
44 };
45 let key = Pbkdf2
46 .hash_password_customized(
47 password.as_bytes(),
48 Some(algo),
49 None,
50 pbkdf2::Params {
51 rounds: *rounds,
52 output_length: 32,
53 },
54 &salt,
55 )
56 .expect("32-byte key generated");
57
58 let key_hash = key.hash.unwrap();
60 let key_bytes = key_hash.as_bytes();
61 let key_array: [u8; 32] = key_bytes.try_into().unwrap();
62 let hash_hex = hex::encode(key_array);
63
64 let result = HasherResult::new(hash_hex, salt.to_string());
65
66 Ok(result)
67 }
68}
69
70pub struct HasherResult {
71 hash: String,
72 salt: String,
73}
74
75impl HasherResult {
76 pub fn new(hash: String, salt: String) -> Self {
77 Self { hash, salt }
78 }
79
80 pub fn hash(&self) -> String {
81 self.hash.to_string()
82 }
83
84 pub fn salt(&self) -> String {
85 self.salt.to_string()
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use crate::hasher::pbkdf2::Algorithm;
92 use pbkdf2::password_hash::SaltString;
93
94 use super::Hasher;
95
96 #[test]
97 fn assert_32_byte_key_length() {
98 const PBKDF_ROUNDS: u32 = 2;
99
100 let result = Hasher::hash(
102 "password",
103 &PBKDF_ROUNDS,
104 Algorithm::Pbkdf2Sha512,
105 Some(SaltString::from_b64("salt").unwrap()),
106 )
107 .unwrap();
108
109 let decoded_hash = hex::decode(&result.hash).unwrap();
110 assert_eq!(decoded_hash.len(), 32_usize);
111 assert_eq!(
112 &result.hash,
113 &"8eb89352a0724cd4dfd8230e895c0ed0182574c37a1173b40489366cd0a78723".to_string()
114 );
115 assert_eq!(result.salt, "salt".to_string());
116 }
117}