scrypt/params.rs
1use crate::errors::InvalidParams;
2
3#[cfg(feature = "phc")]
4use password_hash::{Error, phc};
5
6#[cfg(all(feature = "phc", doc))]
7use password_hash::PasswordHasher;
8
9/// The Scrypt parameter values.
10#[derive(Clone, Copy, Debug, PartialEq)]
11pub struct Params {
12 pub(crate) log_n: u8,
13 pub(crate) r: u32,
14 pub(crate) p: u32,
15 #[cfg(feature = "password-hash")]
16 pub(crate) len: Option<usize>,
17}
18
19impl Params {
20 /// Recommended log₂ of the Scrypt parameter `N`: CPU/memory cost.
21 pub const RECOMMENDED_LOG_N: u8 = 17;
22
23 /// Recommended Scrypt parameter `r`: block size.
24 pub const RECOMMENDED_R: u32 = 8;
25
26 /// Recommended Scrypt parameter `p`: parallelism.
27 pub const RECOMMENDED_P: u32 = 1;
28
29 /// Recommended Scrypt parameter `Key length`.
30 pub const RECOMMENDED_LEN: usize = 32;
31
32 /// Recommended values according to the [OWASP cheat sheet].
33 /// - `log_n = 17` (`n = 131072`)
34 /// - `r = 8`
35 /// - `p = 1`
36 ///
37 /// [OWASP cheat sheet]: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#scrypt
38 pub const RECOMMENDED: Self = Self {
39 log_n: Self::RECOMMENDED_LOG_N,
40 r: Self::RECOMMENDED_R,
41 p: Self::RECOMMENDED_P,
42 #[cfg(feature = "password-hash")]
43 len: None,
44 };
45
46 /// Create a new instance of [`Params`].
47 ///
48 /// # Arguments
49 /// - `log_n` - The log₂ of the Scrypt parameter `N`
50 /// - `r` - The Scrypt parameter `r`
51 /// - `p` - The Scrypt parameter `p`
52 ///
53 /// # Conditions
54 /// - `log_n` must be less than `64`
55 /// - `r` must be greater than `0` and less than or equal to `4294967295`
56 /// - `p` must be greater than `0` and less than `4294967295`
57 ///
58 /// # Errors
59 /// Returns [`InvalidParams`] if one of the parameters is incorrect.
60 pub fn new(log_n: u8, r: u32, p: u32) -> Result<Params, InvalidParams> {
61 let cond1 = (log_n as usize) < usize::BITS as usize;
62 let cond2 = size_of::<usize>() >= size_of::<u32>();
63 let cond3 = usize::try_from(r).is_ok() && usize::try_from(p).is_ok();
64 if !(r > 0 && p > 0 && cond1 && (cond2 || cond3)) {
65 return Err(InvalidParams);
66 }
67
68 let n = 1usize << log_n;
69
70 // check that r * 128 doesn't overflow
71 let r128 = usize::try_from(r)
72 .map_err(|_| InvalidParams)?
73 .checked_mul(128)
74 .ok_or(InvalidParams)?;
75
76 // check that n * r * 128 doesn't overflow
77 r128.checked_mul(n).ok_or(InvalidParams)?;
78
79 // check that p * r * 128 doesn't overflow
80 usize::try_from(p)
81 .ok()
82 .and_then(|p| r128.checked_mul(p))
83 .ok_or(InvalidParams)?;
84
85 // Note: RFC 7914 requires `n < 2^(128 * r / 8)`, i.e. `log_n < r * 16`,
86 // but this upper bound is based on an error in the RFC where a bit count
87 // was treated as a byte count. The correct bound from the original scrypt
88 // paper (Percival, 2009) is `N < 2^(128*r)`, i.e. `log_n < r * 128`,
89 // which far exceeds any practical parameter value.
90 // We intentionally omit this check, consistent with the Tarsnap reference
91 // implementation and Go's x/crypto/scrypt.
92 // See: https://github.com/RustCrypto/password-hashes/issues/866
93 // See: https://www.rfc-editor.org/errata/eid5971
94
95 // This check required by Scrypt:
96 // check: p <= ((2^32-1) * 32) / (128 * r)
97 // It takes a bit of re-arranging to get the check above into this form,
98 // but it is indeed the same.
99 if r * p >= 0x4000_0000 {
100 return Err(InvalidParams);
101 }
102
103 Ok(Params {
104 log_n,
105 r,
106 p,
107 #[cfg(feature = "password-hash")]
108 len: None,
109 })
110 }
111
112 /// Create a new instance of [`Params`], overriding the output length.
113 ///
114 /// Note that this length is only intended for use with the [`PasswordHasher`] API, and not with
115 /// the low-level [`scrypt::scrypt`][`crate::scrypt`] API, which determines the output length
116 /// using the size of the `output` slice.
117 ///
118 /// The allowed values for `len` are between 10 bytes (80 bits) and 64 bytes inclusive.
119 /// These lengths come from the [PHC string format specification](https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md)
120 /// because they are intended for use with password hash strings.
121 ///
122 /// # Errors
123 /// Returns [`InvalidParams`] if one of the parameters is incorrect.
124 #[cfg(feature = "phc")]
125 pub fn new_with_output_len(
126 log_n: u8,
127 r: u32,
128 p: u32,
129 len: usize,
130 ) -> Result<Params, InvalidParams> {
131 if !(phc::Output::MIN_LENGTH..=phc::Output::MAX_LENGTH).contains(&len) {
132 return Err(InvalidParams);
133 }
134
135 let mut ret = Self::new(log_n, r, p)?;
136 ret.len = Some(len);
137 Ok(ret)
138 }
139
140 /// Deprecated: recommended values according to the OWASP cheat sheet.
141 #[deprecated(since = "0.12.0", note = "use Params::RECOMMENDED instead")]
142 #[must_use]
143 pub const fn recommended() -> Params {
144 Self::RECOMMENDED
145 }
146
147 /// log₂ of the Scrypt parameter `N`, the work factor.
148 ///
149 /// Memory and CPU usage scale linearly with `N`. If you need `N`, use
150 /// [`Params::n`] instead.
151 #[must_use]
152 pub const fn log_n(&self) -> u8 {
153 self.log_n
154 }
155
156 /// `N` parameter: the work factor.
157 ///
158 /// This method returns 2 to the power of [`Params::log_n`]. Memory and CPU
159 /// usage scale linearly with `N`.
160 #[must_use]
161 pub const fn n(&self) -> u64 {
162 1 << self.log_n
163 }
164
165 /// `r` parameter: resource usage.
166 ///
167 /// scrypt iterates 2*r times. Memory and CPU time scale linearly
168 /// with this parameter.
169 #[must_use]
170 pub const fn r(&self) -> u32 {
171 self.r
172 }
173
174 /// `p` parameter: parallelization.
175 #[must_use]
176 pub const fn p(&self) -> u32 {
177 self.p
178 }
179}
180
181impl Default for Params {
182 fn default() -> Params {
183 Params::RECOMMENDED
184 }
185}
186
187#[cfg(feature = "phc")]
188impl TryFrom<&phc::ParamsString> for Params {
189 type Error = Error;
190
191 fn try_from(params: &phc::ParamsString) -> password_hash::Result<Self> {
192 let mut log_n = Self::RECOMMENDED_LOG_N;
193 let mut r = Self::RECOMMENDED_R;
194 let mut p = Self::RECOMMENDED_P;
195
196 for (ident, value) in params.iter() {
197 match ident.as_str() {
198 "ln" => {
199 log_n = value
200 .decimal()
201 .ok()
202 .and_then(|dec| dec.try_into().ok())
203 .ok_or(Error::ParamInvalid { name: "ln" })?;
204 }
205 "r" => {
206 r = value
207 .decimal()
208 .map_err(|_| Error::ParamInvalid { name: "r" })?;
209 }
210 "p" => {
211 p = value
212 .decimal()
213 .map_err(|_| Error::ParamInvalid { name: "p" })?;
214 }
215 _ => return Err(Error::ParamsInvalid),
216 }
217 }
218
219 Params::new(log_n, r, p).map_err(|_| Error::ParamsInvalid)
220 }
221}
222
223#[cfg(feature = "phc")]
224impl TryFrom<&phc::PasswordHash> for Params {
225 type Error = Error;
226
227 fn try_from(hash: &phc::PasswordHash) -> password_hash::Result<Self> {
228 if hash.version.is_some() {
229 return Err(Error::Version);
230 }
231
232 let mut params = Params::try_from(&hash.params)?;
233
234 params.len = Some(hash.hash.map_or(Self::RECOMMENDED_LEN, |out| out.len()));
235
236 Ok(params)
237 }
238}
239
240#[cfg(feature = "phc")]
241impl TryFrom<Params> for phc::ParamsString {
242 type Error = Error;
243
244 fn try_from(params: Params) -> Result<phc::ParamsString, Error> {
245 Self::try_from(¶ms)
246 }
247}
248
249#[cfg(feature = "phc")]
250impl TryFrom<&Params> for phc::ParamsString {
251 type Error = Error;
252
253 fn try_from(input: &Params) -> Result<phc::ParamsString, Error> {
254 let mut output = phc::ParamsString::new();
255
256 for (name, value) in [
257 ("ln", phc::Decimal::from(input.log_n)),
258 ("r", input.r),
259 ("p", input.p),
260 ] {
261 output
262 .add_decimal(name, value)
263 .map_err(|_| Error::ParamInvalid { name })?;
264 }
265
266 Ok(output)
267 }
268}