scrypt/
params.rs

1use crate::errors::InvalidParams;
2
3#[cfg(feature = "phc")]
4use {
5    core::{
6        fmt::{self, Display},
7        str::FromStr,
8    },
9    password_hash::{
10        Error,
11        phc::{Decimal, Output, ParamsString, PasswordHash},
12    },
13};
14
15#[cfg(all(feature = "phc", doc))]
16use password_hash::PasswordHasher;
17
18/// The Scrypt parameter values.
19#[derive(Clone, Copy, Debug, PartialEq)]
20pub struct Params {
21    pub(crate) log_n: u8,
22    pub(crate) r: u32,
23    pub(crate) p: u32,
24    #[cfg(feature = "password-hash")]
25    pub(crate) len: Option<usize>,
26}
27
28impl Params {
29    /// Recommended log₂ of the Scrypt parameter `N`: CPU/memory cost.
30    pub const RECOMMENDED_LOG_N: u8 = 17;
31
32    /// Recommended Scrypt parameter `r`: block size.
33    pub const RECOMMENDED_R: u32 = 8;
34
35    /// Recommended Scrypt parameter `p`: parallelism.
36    pub const RECOMMENDED_P: u32 = 1;
37
38    /// Recommended Scrypt parameter `Key length`.
39    pub const RECOMMENDED_LEN: usize = 32;
40
41    /// Recommended values according to the [OWASP cheat sheet].
42    /// - `log_n = 17` (`n = 131072`)
43    /// - `r = 8`
44    /// - `p = 1`
45    ///
46    /// [OWASP cheat sheet]: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#scrypt
47    pub const RECOMMENDED: Self = Self {
48        log_n: Self::RECOMMENDED_LOG_N,
49        r: Self::RECOMMENDED_R,
50        p: Self::RECOMMENDED_P,
51        #[cfg(feature = "password-hash")]
52        len: None,
53    };
54
55    /// Create a new instance of [`Params`].
56    ///
57    /// # Arguments
58    /// - `log_n` - The log₂ of the Scrypt parameter `N`
59    /// - `r` - The Scrypt parameter `r`
60    /// - `p` - The Scrypt parameter `p`
61    ///
62    /// # Conditions
63    /// - `log_n` must be less than `64`
64    /// - `r` must be greater than `0` and less than or equal to `4294967295`
65    /// - `p` must be greater than `0` and less than `4294967295`
66    pub fn new(log_n: u8, r: u32, p: u32) -> Result<Params, InvalidParams> {
67        let cond1 = (log_n as usize) < usize::BITS as usize;
68        let cond2 = size_of::<usize>() >= size_of::<u32>();
69        let cond3 = r <= usize::MAX as u32 && p < usize::MAX as u32;
70        if !(r > 0 && p > 0 && cond1 && (cond2 || cond3)) {
71            return Err(InvalidParams);
72        }
73
74        let r = r as usize;
75        let p = p as usize;
76
77        let n: usize = 1 << log_n;
78
79        // check that r * 128 doesn't overflow
80        let r128 = r.checked_mul(128).ok_or(InvalidParams)?;
81
82        // check that n * r * 128 doesn't overflow
83        r128.checked_mul(n).ok_or(InvalidParams)?;
84
85        // check that p * r * 128 doesn't overflow
86        r128.checked_mul(p).ok_or(InvalidParams)?;
87
88        // This check required by Scrypt:
89        // check: n < 2^(128 * r / 8)
90        // r * 16 won't overflow since r128 didn't
91        if (log_n as usize) >= r * 16 {
92            return Err(InvalidParams);
93        }
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: r as u32,
106            p: p as u32,
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    #[cfg(feature = "phc")]
122    pub fn new_with_output_len(
123        log_n: u8,
124        r: u32,
125        p: u32,
126        len: usize,
127    ) -> Result<Params, InvalidParams> {
128        if !(Output::MIN_LENGTH..=Output::MAX_LENGTH).contains(&len) {
129            return Err(InvalidParams);
130        }
131
132        let mut ret = Self::new(log_n, r, p)?;
133        ret.len = Some(len);
134        Ok(ret)
135    }
136
137    /// Deprecated: recommended values according to the OWASP cheat sheet.
138    #[deprecated(since = "0.12.0", note = "use Params::RECOMMENDED instead")]
139    pub const fn recommended() -> Params {
140        Self::RECOMMENDED
141    }
142
143    /// log₂ of the Scrypt parameter `N`, the work factor.
144    ///
145    /// Memory and CPU usage scale linearly with `N`. If you need `N`, use
146    /// [`Params::n`] instead.
147    pub const fn log_n(&self) -> u8 {
148        self.log_n
149    }
150
151    /// `N` parameter: the work factor.
152    ///
153    /// This method returns 2 to the power of [`Params::log_n`]. Memory and CPU
154    /// usage scale linearly with `N`.
155    pub const fn n(&self) -> u64 {
156        1 << self.log_n
157    }
158
159    /// `r` parameter: resource usage.
160    ///
161    /// scrypt iterates 2*r times. Memory and CPU time scale linearly
162    /// with this parameter.
163    pub const fn r(&self) -> u32 {
164        self.r
165    }
166
167    /// `p` parameter: parallelization.
168    pub const fn p(&self) -> u32 {
169        self.p
170    }
171}
172
173impl Default for Params {
174    fn default() -> Params {
175        Params::RECOMMENDED
176    }
177}
178
179#[cfg(feature = "phc")]
180impl Display for Params {
181    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
182        ParamsString::try_from(self).map_err(|_| fmt::Error)?.fmt(f)
183    }
184}
185
186#[cfg(feature = "phc")]
187impl FromStr for Params {
188    type Err = Error;
189
190    fn from_str(s: &str) -> password_hash::Result<Self> {
191        let params_string = ParamsString::from_str(s).map_err(|_| Error::ParamsInvalid)?;
192        Self::try_from(&params_string)
193    }
194}
195
196#[cfg(feature = "phc")]
197impl TryFrom<&ParamsString> for Params {
198    type Error = Error;
199
200    fn try_from(params: &ParamsString) -> password_hash::Result<Self> {
201        let mut log_n = Self::RECOMMENDED_LOG_N;
202        let mut r = Self::RECOMMENDED_R;
203        let mut p = Self::RECOMMENDED_P;
204
205        for (ident, value) in params.iter() {
206            match ident.as_str() {
207                "ln" => {
208                    log_n = value
209                        .decimal()
210                        .ok()
211                        .and_then(|dec| dec.try_into().ok())
212                        .ok_or(Error::ParamInvalid { name: "ln" })?;
213                }
214                "r" => {
215                    r = value
216                        .decimal()
217                        .map_err(|_| Error::ParamInvalid { name: "r" })?;
218                }
219                "p" => {
220                    p = value
221                        .decimal()
222                        .map_err(|_| Error::ParamInvalid { name: "p" })?;
223                }
224                _ => return Err(Error::ParamsInvalid),
225            }
226        }
227
228        Params::new(log_n, r, p).map_err(|_| Error::ParamsInvalid)
229    }
230}
231
232#[cfg(feature = "phc")]
233impl TryFrom<&PasswordHash> for Params {
234    type Error = Error;
235
236    fn try_from(hash: &PasswordHash) -> password_hash::Result<Self> {
237        if hash.version.is_some() {
238            return Err(Error::Version);
239        }
240
241        let mut params = Params::try_from(&hash.params)?;
242
243        params.len = Some(
244            hash.hash
245                .map(|out| out.len())
246                .unwrap_or(Self::RECOMMENDED_LEN),
247        );
248
249        Ok(params)
250    }
251}
252
253#[cfg(feature = "phc")]
254impl TryFrom<Params> for ParamsString {
255    type Error = Error;
256
257    fn try_from(params: Params) -> Result<ParamsString, Error> {
258        Self::try_from(&params)
259    }
260}
261
262#[cfg(feature = "phc")]
263impl TryFrom<&Params> for ParamsString {
264    type Error = Error;
265
266    fn try_from(input: &Params) -> Result<ParamsString, Error> {
267        let mut output = ParamsString::new();
268
269        for (name, value) in [
270            ("ln", input.log_n as Decimal),
271            ("r", input.r),
272            ("p", input.p),
273        ] {
274            output
275                .add_decimal(name, value)
276                .map_err(|_| Error::ParamInvalid { name })?;
277        }
278
279        Ok(output)
280    }
281}