1pub use password_hash::phc::{Ident, Output, PasswordHash, Salt};
4
5use crate::{Params, Scrypt, scrypt};
6use password_hash::{CustomizedPasswordHasher, Error, PasswordHasher, Result, Version};
7
8const ALG_NAME: &str = "scrypt";
10
11pub const ALG_ID: Ident = Ident::new_unwrap(ALG_NAME);
13
14impl CustomizedPasswordHasher<PasswordHash> for Scrypt {
15 type Params = Params;
16
17 fn hash_password_customized(
18 &self,
19 password: &[u8],
20 salt: &[u8],
21 alg_id: Option<&str>,
22 version: Option<Version>,
23 params: Params,
24 ) -> Result<PasswordHash> {
25 match alg_id {
26 Some(ALG_NAME) | None => (),
27 Some(_) => return Err(Error::Algorithm),
28 }
29
30 if version.is_some() {
32 return Err(Error::Version);
33 }
34
35 let salt = Salt::new(salt)?;
36 let len = params.len.unwrap_or(Params::RECOMMENDED_LEN);
37
38 let mut buffer = [0u8; Output::MAX_LENGTH];
39 let out = buffer.get_mut(..len).ok_or(Error::OutputSize)?;
40 scrypt(password, &salt, ¶ms, out).map_err(|_| Error::OutputSize)?;
41 let output = Output::new(out)?;
42
43 Ok(PasswordHash {
44 algorithm: ALG_ID,
45 version: None,
46 params: params.try_into()?,
47 salt: Some(salt),
48 hash: Some(output),
49 })
50 }
51}
52
53impl PasswordHasher<PasswordHash> for Scrypt {
54 fn hash_password_with_salt(&self, password: &[u8], salt: &[u8]) -> Result<PasswordHash> {
55 self.hash_password_customized(password, salt, None, None, self.params)
56 }
57}
58
59#[cfg(test)]
60mod tests {
61 use super::{PasswordHash, Scrypt};
62 use password_hash::PasswordVerifier;
63
64 #[cfg(feature = "password-hash")]
67 const EXAMPLE_PASSWORD_HASH: &str =
68 "$scrypt$ln=16,r=8,p=1$aM15713r3Xsvxbi31lqr1Q$nFNh2CVHVjNldFVKDHDlm4CbdRSCdEBsjjJxD+iCs5E";
69
70 #[cfg(feature = "password-hash")]
71 #[test]
72 fn password_hash_verify_password() {
73 let password = "password";
74 let hash = PasswordHash::new(EXAMPLE_PASSWORD_HASH).unwrap();
75 assert_eq!(
76 Scrypt::new().verify_password(password.as_bytes(), &hash),
77 Ok(())
78 );
79 }
80
81 #[cfg(feature = "password-hash")]
82 #[test]
83 fn password_hash_reject_incorrect_password() {
84 let hash = PasswordHash::new(EXAMPLE_PASSWORD_HASH).unwrap();
85 assert!(Scrypt::new().verify_password(b"invalid", &hash).is_err());
86 }
87}