1use crate::errors::{DerivedKeyError, EncryptError, GenerateHashError};
30use aes::{
31 cipher::{KeyIvInit, StreamCipher},
32 Aes256,
33};
34use constant_time_eq::constant_time_eq;
35use ctr::Ctr128BE;
36use scrypt::Params;
37
38pub mod errors;
39#[cfg(feature = "simple")]
40mod simple;
41
42#[cfg(feature = "simple")]
43pub use simple::FirebaseScrypt;
44
45const IV: [u8; 16] = *b"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
46
47fn clean(a: &str) -> String {
48 a.replace("-", "+").replace("_", "/")
49}
50
51fn generate_derived_key<'a>(
52 password: &'a str,
53 salt: &'a str,
54 salt_separator: &'a str,
55 rounds: u32,
56 mem_cost: u32,
57) -> Result<[u8; 64], DerivedKeyError> {
58 let log2_n = 2_f32.powf(mem_cost as f32).log2().floor() as u32;
59 let p: u32 = 1;
60
61 debug_assert!(log2_n < 64, "log2 of n must not be larger than 64");
62
63 let mut salt = base64::decode(salt)?;
64 salt.append(&mut base64::decode(salt_separator)?);
65 let password = password.as_bytes();
66
67 let params = Params::new(log2_n as u8, rounds, p)?;
68
69 let mut result = [0u8; 64];
70 scrypt::scrypt(password, salt.as_slice(), ¶ms, &mut result)?;
71
72 Ok(result)
73}
74
75fn encrypt(signer_key: &[u8], key: [u8; 32]) -> Result<Vec<u8>, EncryptError> {
76 let mut cipher = Ctr128BE::<Aes256>::new(&key.into(), &IV.into());
77
78 let mut buffer = vec![0u8; signer_key.len()];
79 cipher.apply_keystream_b2b(signer_key, &mut buffer)?;
80
81 Ok(buffer)
82}
83
84pub fn verify_password(
115 password: &str,
116 known_hash: &str,
117 salt: &str,
118 salt_separator: &str,
119 signer_key: &str,
120 rounds: u32,
121 mem_cost: u32,
122) -> Result<bool, GenerateHashError> {
123 let password_hash =
124 generate_raw_hash(password, salt, salt_separator, signer_key, rounds, mem_cost)?;
125
126 Ok(constant_time_eq(
127 password_hash.as_slice(),
128 base64::decode(clean(known_hash))?.as_slice(),
129 ))
130}
131
132pub fn generate_raw_hash(
164 password: &str,
165 salt: &str,
166 salt_separator: &str,
167 signer_key: &str,
168 rounds: u32,
169 mem_cost: u32,
170) -> Result<Vec<u8>, GenerateHashError> {
171 let derived_key =
172 generate_derived_key(password, &clean(salt), salt_separator, rounds, mem_cost)?;
173 let signer_key = base64::decode(signer_key)?;
174
175 let result = encrypt(signer_key.as_slice(), derived_key[..32].try_into().unwrap())?;
176 Ok(base64::decode(base64::encode(result))?)
177}
178
179#[cfg(test)]
180mod tests {
181 const SALT_SEPARATOR: &str = "Bw==";
182 const SIGNER_KEY: &str =
183 "jxspr8Ki0RYycVU8zykbdLGjFQ3McFUH0uiiTvC8pVMXAn210wjLNmdZJzxUECKbm0QsEmYUSDzZvpjeJ9WmXA==";
184 const ROUNDS: u32 = 8;
185 const MEM_COST: u32 = 14;
186
187 const PASSWORD: &str = "user1password";
188 const SALT: &str = "42xEC+ixf3L2lw==";
189 const PASSWORD_HASH: &str =
190 "lSrfV15cpx95/sZS2W9c9Kp6i/LVgQNDNC/qzrCnh1SAyZvqmZqAjTdn3aoItz+VHjoZilo78198JAdRuid5lQ==";
191
192 use super::*;
193
194 #[test]
195 fn verify_password_works() {
196 assert!(verify_password(
197 PASSWORD,
198 PASSWORD_HASH,
199 SALT,
200 SALT_SEPARATOR,
201 SIGNER_KEY,
202 ROUNDS,
203 MEM_COST
204 )
205 .unwrap())
206 }
207
208 #[test]
209 fn generate_hash_works() {
210 assert_eq!(
211 base64::encode(
212 generate_raw_hash(PASSWORD, SALT, SALT_SEPARATOR, SIGNER_KEY, ROUNDS, MEM_COST,)
213 .unwrap()
214 ),
215 PASSWORD_HASH
216 )
217 }
218
219 #[test]
220 fn encrypt_works() {
221 let param_1 = b"randomrandomrandomrandomrandomrandomrandom";
222 let param_2 = b"12345678901234567890123456789012";
223
224 assert_eq!(
225 hex::encode(encrypt(param_1, *param_2).unwrap()),
226 "09f509fa3d09cde568f80709416681e4ed5d9677ca8b4807a932869ba3fd057be3606c2940877850ed96"
227 );
228 }
229
230 #[test]
231 fn generate_derived_key_works() {
232 assert_eq!(hex::encode(generate_derived_key(PASSWORD, SALT, SALT_SEPARATOR, ROUNDS, MEM_COST).unwrap()), "e87fa22d9b4e3be6bbd41214f2f98f8c78b694bd17e12c2b73501054a2099ce11fe896483c68a443c6cf9ff8a8dfe1dfe2adaa4be6c8ca1b7686687a26f48831");
233 }
234}