Skip to main content
This is unreleased documentation for the main (development) branch of crypto-glue.

scrypt/
params.rs

1use core::mem::size_of;
2
3use crate::errors::InvalidParams;
4
5#[cfg(feature = "simple")]
6use password_hash::{errors::InvalidValue, Error, ParamsString, PasswordHash};
7
8/// The Scrypt parameter values.
9#[derive(Clone, Copy, Debug)]
10pub struct Params {
11    pub(crate) log_n: u8,
12    pub(crate) r: u32,
13    pub(crate) p: u32,
14    #[allow(dead_code)] // this field is used only with the `PasswordHasher` impl
15    pub(crate) len: usize,
16}
17
18impl Params {
19    /// Recommended log₂ of the Scrypt parameter `N`: CPU/memory cost.
20    pub const RECOMMENDED_LOG_N: u8 = 17;
21
22    /// Recommended Scrypt parameter `r`: block size.
23    pub const RECOMMENDED_R: u32 = 8;
24
25    /// Recommended Scrypt parameter `p`: parallelism.
26    pub const RECOMMENDED_P: u32 = 1;
27
28    /// Recommended Scrypt parameter `Key length`.
29    pub const RECOMMENDED_LEN: usize = 32;
30
31    /// Create a new instance of [`Params`].
32    ///
33    /// # Arguments
34    /// - `log_n` - The log₂ of the Scrypt parameter `N`
35    /// - `r` - The Scrypt parameter `r`
36    /// - `p` - The Scrypt parameter `p`
37    /// - `len` - The Scrypt parameter `Key length`
38    ///
39    /// # Conditions
40    /// - `log_n` must be less than `64`
41    /// - `r` must be greater than `0` and less than or equal to `4294967295`
42    /// - `p` must be greater than `0` and less than `4294967295`
43    /// - `len` must be greater than `9` and less than or equal to `64`
44    pub fn new(log_n: u8, r: u32, p: u32, len: usize) -> Result<Params, InvalidParams> {
45        let cond1 = (log_n as usize) < usize::BITS as usize;
46        let cond2 = size_of::<usize>() >= size_of::<u32>();
47        let cond3 = r <= usize::MAX as u32 && p < usize::MAX as u32;
48        let cond4 = (10..=64).contains(&len);
49        if !(r > 0 && p > 0 && cond1 && (cond2 || cond3) && cond4) {
50            return Err(InvalidParams);
51        }
52
53        let r = r as usize;
54        let p = p as usize;
55
56        let n: usize = 1 << log_n;
57
58        // check that r * 128 doesn't overflow
59        let r128 = r.checked_mul(128).ok_or(InvalidParams)?;
60
61        // check that n * r * 128 doesn't overflow
62        r128.checked_mul(n).ok_or(InvalidParams)?;
63
64        // check that p * r * 128 doesn't overflow
65        r128.checked_mul(p).ok_or(InvalidParams)?;
66
67        // This check required by Scrypt:
68        // check: n < 2^(128 * r / 8)
69        // r * 16 won't overflow since r128 didn't
70        if (log_n as usize) >= r * 16 {
71            return Err(InvalidParams);
72        }
73
74        // This check required by Scrypt:
75        // check: p <= ((2^32-1) * 32) / (128 * r)
76        // It takes a bit of re-arranging to get the check above into this form,
77        // but it is indeed the same.
78        if r * p >= 0x4000_0000 {
79            return Err(InvalidParams);
80        }
81
82        Ok(Params {
83            log_n,
84            r: r as u32,
85            p: p as u32,
86            len,
87        })
88    }
89
90    /// Recommended values sufficient for most use-cases
91    /// - `log_n = 15` (`n = 32768`)
92    /// - `r = 8`
93    /// - `p = 1`
94    pub fn recommended() -> Params {
95        Params {
96            log_n: Self::RECOMMENDED_LOG_N,
97            r: Self::RECOMMENDED_R,
98            p: Self::RECOMMENDED_P,
99            len: Self::RECOMMENDED_LEN,
100        }
101    }
102
103    /// log₂ of the Scrypt parameter `N`, the work factor.
104    ///
105    /// Memory and CPU usage scale linearly with `N`.
106    pub fn log_n(&self) -> u8 {
107        self.log_n
108    }
109
110    /// `r` parameter: resource usage.
111    ///
112    /// scrypt iterates 2*r times. Memory and CPU time scale linearly
113    /// with this parameter.
114    pub fn r(&self) -> u32 {
115        self.r
116    }
117
118    /// `p` parameter: parallelization.
119    pub fn p(&self) -> u32 {
120        self.p
121    }
122}
123
124impl Default for Params {
125    fn default() -> Params {
126        Params::recommended()
127    }
128}
129
130#[cfg(feature = "simple")]
131#[cfg_attr(docsrs, doc(cfg(feature = "simple")))]
132impl<'a> TryFrom<&'a PasswordHash<'a>> for Params {
133    type Error = password_hash::Error;
134
135    fn try_from(hash: &'a PasswordHash<'a>) -> Result<Self, password_hash::Error> {
136        let mut log_n = Self::RECOMMENDED_LOG_N;
137        let mut r = Self::RECOMMENDED_R;
138        let mut p = Self::RECOMMENDED_P;
139
140        if hash.version.is_some() {
141            return Err(Error::Version);
142        }
143
144        for (ident, value) in hash.params.iter() {
145            match ident.as_str() {
146                "ln" => {
147                    log_n = value
148                        .decimal()?
149                        .try_into()
150                        .map_err(|_| InvalidValue::Malformed.param_error())?
151                }
152                "r" => r = value.decimal()?,
153                "p" => p = value.decimal()?,
154                _ => return Err(password_hash::Error::ParamNameInvalid),
155            }
156        }
157
158        let len = hash
159            .hash
160            .map(|out| out.len())
161            .unwrap_or(Self::RECOMMENDED_LEN);
162        Params::new(log_n, r, p, len).map_err(|_| InvalidValue::Malformed.param_error())
163    }
164}
165
166#[cfg(feature = "simple")]
167#[cfg_attr(docsrs, doc(cfg(feature = "simple")))]
168impl TryFrom<Params> for ParamsString {
169    type Error = password_hash::Error;
170
171    fn try_from(input: Params) -> Result<ParamsString, password_hash::Error> {
172        let mut output = ParamsString::new();
173        output.add_decimal("ln", input.log_n as u32)?;
174        output.add_decimal("r", input.r)?;
175        output.add_decimal("p", input.p)?;
176        Ok(output)
177    }
178}