arcanum_hash/
scrypt_impl.rs1use 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#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct ScryptParams {
18 pub log_n: u8,
20 pub r: u32,
22 pub p: u32,
24 pub output_len: usize,
26}
27
28impl Default for ScryptParams {
29 fn default() -> Self {
30 Self::moderate()
31 }
32}
33
34impl ScryptParams {
35 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 pub fn low() -> Self {
49 Self {
50 log_n: 12,
51 r: 8,
52 p: 1,
53 output_len: 32,
54 }
55 }
56
57 pub fn moderate() -> Self {
61 Self {
62 log_n: 15,
63 r: 8,
64 p: 1,
65 output_len: 32,
66 }
67 }
68
69 pub fn high() -> Self {
73 Self {
74 log_n: 17,
75 r: 8,
76 p: 1,
77 output_len: 32,
78 }
79 }
80
81 pub fn interactive() -> Self {
85 Self {
86 log_n: 14,
87 r: 8,
88 p: 1,
89 output_len: 32,
90 }
91 }
92
93 pub fn sensitive() -> Self {
97 Self {
98 log_n: 20,
99 r: 8,
100 p: 1,
101 output_len: 32,
102 }
103 }
104}
105
106pub 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 pub fn hash(password: &[u8]) -> Result<String> {
153 Self::hash_password(password, &ScryptParams::default())
154 }
155
156 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}