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}