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