firebase_scrypt/simple.rs
1use crate::{generate_raw_hash, verify_password, GenerateHashError};
2
3/// Struct to simplify the usage of hash generation and checking.
4///
5/// Holds the salt separator, signer key, round and memory cost to make the usage of the hash generation
6/// and checking function easier.
7///
8/// # Example
9/// ```
10/// use firebase_scrypt::FirebaseScrypt;
11///
12/// const SALT_SEPARATOR: &str = "Bw==";
13/// const SIGNER_KEY: &str = "jxspr8Ki0RYycVU8zykbdLGjFQ3McFUH0uiiTvC8pVMXAn210wjLNmdZJzxUECKbm0QsEmYUSDzZvpjeJ9WmXA==";
14/// const ROUNDS: u32 = 8;
15/// const MEM_COST: u32 = 14;
16///
17/// let firebase_scrypt = FirebaseScrypt::new(SALT_SEPARATOR, SIGNER_KEY, ROUNDS, MEM_COST);
18///
19/// let password = "user1password";
20/// let salt = "42xEC+ixf3L2lw==";
21/// let password_hash ="lSrfV15cpx95/sZS2W9c9Kp6i/LVgQNDNC/qzrCnh1SAyZvqmZqAjTdn3aoItz+VHjoZilo78198JAdRuid5lQ==";
22///
23/// assert!(firebase_scrypt.verify_password(password, salt, password_hash).unwrap())
24/// ```
25#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
26pub struct FirebaseScrypt {
27 salt_separator: String,
28 signer_key: String,
29 rounds: u32,
30 mem_cost: u32,
31}
32
33impl FirebaseScrypt {
34 /// Create a new [`FirebaseScrypt`] instance.
35 pub fn new(salt_separator: &str, signer_key: &str, rounds: u32, mem_cost: u32) -> Self {
36 Self {
37 salt_separator: salt_separator.to_string(),
38 signer_key: signer_key.to_string(),
39 rounds,
40 mem_cost,
41 }
42 }
43
44 /// Calls [`verify_password`] with the data from the [`FirebaseScrypt`]
45 ///
46 /// # Example
47 /// ```no_test
48 /// # This test doesn't pass for (some?) reason. But the ``verify_password_with_simple_works`` test
49 /// # passes, so no idea.
50 /// use firebase_scrypt::FirebaseScrypt;
51 ///
52 /// const SALT_SEPARATOR: &str = "Bw==";
53 /// const SIGNER_KEY: &str = "jxspr8Ki0RYycVU8zykbdLGjFQ3McFUH0uiiTvC8pVMXAn210wjLNmdZJzxUECKbm0QsEmYUSDzZvpjeJ9WmXA==";
54 /// const ROUNDS: u32 = 8;
55 /// const MEM_COST: u32 = 14;
56 ///
57 /// let password: &str = "user1password";
58 /// let salt: &str = "42xEC+ixf3L2lw==";
59 /// let password_hash: &str = "lSrfV15cpx95/sZS2W9c9Kp6i/LVgQNDNC/qzrCnh1SAyZvqmZqAjTdn3aoItz+VHjoZilo78198JAdRuid5lQ==";
60 ///
61 /// let firebase_scrypt = FirebaseScrypt::new(SALT_SEPARATOR, SIGNER_KEY, ROUNDS, MEM_COST);
62 ///
63 /// let is_valid = firebase_scrypt.verify_password(
64 /// password,
65 /// password_hash,
66 /// salt,
67 /// ).unwrap();
68 ///
69 /// assert!(is_valid)
70 /// ```
71 pub fn verify_password(
72 &self,
73 password: &str,
74 salt: &str,
75 known_hash: &str,
76 ) -> Result<bool, GenerateHashError> {
77 verify_password(
78 password,
79 known_hash,
80 salt,
81 self.salt_separator.as_str(),
82 self.signer_key.as_str(),
83 self.rounds,
84 self.mem_cost,
85 )
86 }
87
88 /// Calls [`FirebaseScrypt::verify_password`] but returns false also if an error occurs, which
89 /// is _usually_ the best thing to do.
90 pub fn verify_password_bool(&self, password: &str, salt: &str, known_hash: &str) -> bool {
91 if let Ok(result) = self.verify_password(password, salt, known_hash) {
92 result
93 } else {
94 false
95 }
96 }
97
98 /// Generates a hash and returns its Base64 form, the same as the hashes from Firebase
99 ///
100 /// <div class="example-wrap" style="display:inline-block"><pre class="compile_fail" style="white-space:normal;font:inherit;">
101 ///
102 /// **Warning**: Do not use this function to check if a given password is valid, because that
103 /// could result in [side-channel attacks](https://en.wikipedia.org/wiki/Side-channel_attack).
104 ///
105 /// Use the [`FirebaseScrypt::verify_password`] function instead.
106 ///
107 /// </pre></div>
108 ///
109 /// # Example
110 /// ```
111 /// use firebase_scrypt::FirebaseScrypt;
112 ///
113 /// const SALT_SEPARATOR: &str = "Bw==";
114 /// const SIGNER_KEY: &str = "jxspr8Ki0RYycVU8zykbdLGjFQ3McFUH0uiiTvC8pVMXAn210wjLNmdZJzxUECKbm0QsEmYUSDzZvpjeJ9WmXA==";
115 /// const ROUNDS: u32 = 8;
116 /// const MEM_COST: u32 = 14;
117 ///
118 /// let password = "user1password";
119 /// let salt = "42xEC+ixf3L2lw==";
120 ///
121 /// let firebase_scrypt = FirebaseScrypt::new(SALT_SEPARATOR, SIGNER_KEY, ROUNDS, MEM_COST);
122 ///
123 /// firebase_scrypt.generate_base64_hash(password, salt).unwrap();
124 /// ```
125 pub fn generate_base64_hash(
126 &self,
127 password: &str,
128 salt: &str,
129 ) -> Result<String, GenerateHashError> {
130 let hash = generate_raw_hash(
131 password,
132 salt,
133 self.salt_separator.as_str(),
134 self.signer_key.as_str(),
135 self.rounds,
136 self.mem_cost,
137 )?;
138
139 Ok(base64::encode(hash))
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 const SALT_SEPARATOR: &str = "Bw==";
146 const SIGNER_KEY: &str =
147 "jxspr8Ki0RYycVU8zykbdLGjFQ3McFUH0uiiTvC8pVMXAn210wjLNmdZJzxUECKbm0QsEmYUSDzZvpjeJ9WmXA==";
148 const ROUNDS: u32 = 8;
149 const MEM_COST: u32 = 14;
150
151 const PASSWORD: &str = "user1password";
152 const SALT: &str = "42xEC+ixf3L2lw==";
153 const PASSWORD_HASH: &str =
154 "lSrfV15cpx95/sZS2W9c9Kp6i/LVgQNDNC/qzrCnh1SAyZvqmZqAjTdn3aoItz+VHjoZilo78198JAdRuid5lQ==";
155
156 use super::*;
157
158 #[test]
159 fn verify_password_with_simple_works() {
160 let firebase_scrypt = FirebaseScrypt::new(SALT_SEPARATOR, SIGNER_KEY, ROUNDS, MEM_COST);
161
162 assert!(firebase_scrypt
163 .verify_password(PASSWORD, SALT, PASSWORD_HASH,)
164 .unwrap())
165 }
166
167 #[test]
168 fn generate_hash_with_simple_works() {
169 let firebase_scrypt = FirebaseScrypt::new(SALT_SEPARATOR, SIGNER_KEY, ROUNDS, MEM_COST);
170
171 assert_eq!(
172 firebase_scrypt
173 .generate_base64_hash(PASSWORD, SALT,)
174 .unwrap(),
175 PASSWORD_HASH
176 )
177 }
178}