Skip to main content

scrypt/
phc.rs

1//! Implementation of the `password-hash` crate API.
2
3pub use password_hash::phc::{Ident, Output, PasswordHash, Salt};
4
5use crate::{Params, Scrypt, scrypt};
6use password_hash::{CustomizedPasswordHasher, Error, PasswordHasher, Result, Version};
7
8/// Algorithm name
9const ALG_NAME: &str = "scrypt";
10
11/// Algorithm identifier
12pub 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        // Versions unsupported
31        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, &params, 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    /// Test vector from passlib:
65    /// <https://passlib.readthedocs.io/en/stable/lib/passlib.hash.scrypt.html>
66    #[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}