Skip to main content

arcanum_hash/
scrypt_impl.rs

1//! scrypt password hashing.
2//!
3//! scrypt is a memory-hard password hashing function designed to be
4//! expensive to attack with custom hardware.
5
6use crate::traits::PasswordHash;
7use arcanum_core::error::{Error, Result};
8use rand::rngs::OsRng;
9use scrypt::{
10    Params, Scrypt as ScryptInner,
11    password_hash::{PasswordHasher, PasswordVerifier, SaltString},
12};
13use serde::{Deserialize, Serialize};
14
15/// scrypt parameters.
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct ScryptParams {
18    /// Log2 of the CPU/memory cost parameter (default: 15, meaning 2^15)
19    pub log_n: u8,
20    /// Block size parameter (default: 8)
21    pub r: u32,
22    /// Parallelization parameter (default: 1)
23    pub p: u32,
24    /// Output length in bytes (default: 32)
25    pub output_len: usize,
26}
27
28impl Default for ScryptParams {
29    fn default() -> Self {
30        Self::moderate()
31    }
32}
33
34impl ScryptParams {
35    /// Create custom parameters.
36    pub fn new(log_n: u8, r: u32, p: u32) -> Self {
37        Self {
38            log_n,
39            r,
40            p,
41            output_len: 32,
42        }
43    }
44
45    /// Low security parameters (for testing).
46    ///
47    /// log_n: 12, r: 8, p: 1 (~4 MiB memory)
48    pub fn low() -> Self {
49        Self {
50            log_n: 12,
51            r: 8,
52            p: 1,
53            output_len: 32,
54        }
55    }
56
57    /// Moderate security parameters.
58    ///
59    /// log_n: 15, r: 8, p: 1 (~32 MiB memory)
60    pub fn moderate() -> Self {
61        Self {
62            log_n: 15,
63            r: 8,
64            p: 1,
65            output_len: 32,
66        }
67    }
68
69    /// High security parameters.
70    ///
71    /// log_n: 17, r: 8, p: 1 (~128 MiB memory)
72    pub fn high() -> Self {
73        Self {
74            log_n: 17,
75            r: 8,
76            p: 1,
77            output_len: 32,
78        }
79    }
80
81    /// Interactive parameters (fast response time).
82    ///
83    /// log_n: 14, r: 8, p: 1 (~16 MiB memory)
84    pub fn interactive() -> Self {
85        Self {
86            log_n: 14,
87            r: 8,
88            p: 1,
89            output_len: 32,
90        }
91    }
92
93    /// Sensitive parameters (for highly sensitive data).
94    ///
95    /// log_n: 20, r: 8, p: 1 (~1 GiB memory)
96    pub fn sensitive() -> Self {
97        Self {
98            log_n: 20,
99            r: 8,
100            p: 1,
101            output_len: 32,
102        }
103    }
104}
105
106/// scrypt password hashing.
107pub struct Scrypt;
108
109impl PasswordHash for Scrypt {
110    type Params = ScryptParams;
111    const ALGORITHM: &'static str = "scrypt";
112
113    fn hash_password(password: &[u8], params: &Self::Params) -> Result<String> {
114        let salt = SaltString::generate(&mut OsRng);
115
116        let scrypt_params = Params::new(params.log_n, params.r, params.p, params.output_len)
117            .map_err(|e| Error::InternalError(e.to_string()))?;
118
119        let hash = ScryptInner
120            .hash_password_customized(password, None, None, scrypt_params, &salt)
121            .map_err(|e| Error::InternalError(e.to_string()))?;
122
123        Ok(hash.to_string())
124    }
125
126    fn verify_password(password: &[u8], hash: &str) -> Result<bool> {
127        let parsed_hash = scrypt::password_hash::PasswordHash::new(hash)
128            .map_err(|e| Error::ParseError(e.to_string()))?;
129
130        Ok(ScryptInner.verify_password(password, &parsed_hash).is_ok())
131    }
132
133    fn derive_key(
134        password: &[u8],
135        salt: &[u8],
136        params: &Self::Params,
137        output_len: usize,
138    ) -> Result<Vec<u8>> {
139        let scrypt_params = Params::new(params.log_n, params.r, params.p, output_len)
140            .map_err(|e| Error::InternalError(e.to_string()))?;
141
142        let mut output = vec![0u8; output_len];
143        scrypt::scrypt(password, salt, &scrypt_params, &mut output)
144            .map_err(|_| Error::KeyDerivationFailed)?;
145
146        Ok(output)
147    }
148}
149
150impl Scrypt {
151    /// Hash a password with default (moderate) parameters.
152    pub fn hash(password: &[u8]) -> Result<String> {
153        Self::hash_password(password, &ScryptParams::default())
154    }
155
156    /// Verify a password against a hash.
157    pub fn verify(password: &[u8], hash: &str) -> Result<bool> {
158        Self::verify_password(password, hash)
159    }
160}
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165
166    #[test]
167    fn test_hash_verify() {
168        let password = b"correct horse battery staple";
169        let hash = Scrypt::hash_password(password, &ScryptParams::low()).unwrap();
170
171        assert!(Scrypt::verify_password(password, &hash).unwrap());
172        assert!(!Scrypt::verify_password(b"wrong password", &hash).unwrap());
173    }
174
175    #[test]
176    fn test_derive_key() {
177        let password = b"password";
178        let salt = b"somesalt12345678";
179
180        let key1 = Scrypt::derive_key(password, salt, &ScryptParams::low(), 32).unwrap();
181        let key2 = Scrypt::derive_key(password, salt, &ScryptParams::low(), 32).unwrap();
182
183        assert_eq!(key1, key2);
184        assert_eq!(key1.len(), 32);
185    }
186
187    #[test]
188    fn test_hash_format() {
189        let password = b"test";
190        let hash = Scrypt::hash_password(password, &ScryptParams::low()).unwrap();
191
192        assert!(hash.starts_with("$scrypt$"));
193    }
194}