argon2/
params.rs

1//! Argon2 password hash parameters.
2
3use crate::{Algorithm, Argon2, Error, Result, Version, SYNC_POINTS};
4use base64ct::{Base64Unpadded as B64, Encoding};
5use core::str::FromStr;
6
7#[cfg(feature = "password-hash")]
8use password_hash::{ParamsString, PasswordHash};
9
10/// Argon2 password hash parameters.
11///
12/// These are parameters which can be encoded into a PHC hash string.
13#[derive(Clone, Debug, Eq, PartialEq)]
14pub struct Params {
15    /// Memory size, expressed in kibibytes, between 8\*`p_cost` and (2^32)-1.
16    ///
17    /// Value is an integer in decimal (1 to 10 digits).
18    m_cost: u32,
19
20    /// Number of iterations, between 1 and (2^32)-1.
21    ///
22    /// Value is an integer in decimal (1 to 10 digits).
23    t_cost: u32,
24
25    /// Degree of parallelism, between 1 and (2^24)-1.
26    ///
27    /// Value is an integer in decimal (1 to 8 digits).
28    p_cost: u32,
29
30    /// Key identifier.
31    keyid: KeyId,
32
33    /// Associated data.
34    data: AssociatedData,
35
36    /// Size of the output (in bytes).
37    output_len: Option<usize>,
38}
39
40impl Params {
41    /// Default memory cost.
42    pub const DEFAULT_M_COST: u32 = 19 * 1024;
43
44    /// Minimum number of 1 KiB memory blocks.
45    #[allow(clippy::cast_possible_truncation)]
46    pub const MIN_M_COST: u32 = 2 * SYNC_POINTS as u32; // 2 blocks per slice
47
48    /// Maximum number of 1 KiB memory blocks.
49    pub const MAX_M_COST: u32 = u32::MAX;
50
51    /// Default number of iterations (i.e. "time").
52    pub const DEFAULT_T_COST: u32 = 2;
53
54    /// Minimum number of passes.
55    pub const MIN_T_COST: u32 = 1;
56
57    /// Maximum number of passes.
58    pub const MAX_T_COST: u32 = u32::MAX;
59
60    /// Default degree of parallelism.
61    pub const DEFAULT_P_COST: u32 = 1;
62
63    /// Minimum and maximum number of threads (i.e. parallelism).
64    pub const MIN_P_COST: u32 = 1;
65
66    /// Minimum and maximum number of threads (i.e. parallelism).
67    pub const MAX_P_COST: u32 = 0xFFFFFF;
68
69    /// Maximum length of a key ID in bytes.
70    pub const MAX_KEYID_LEN: usize = 8;
71
72    /// Maximum length of associated data in bytes.
73    pub const MAX_DATA_LEN: usize = 32;
74
75    /// Default output length.
76    pub const DEFAULT_OUTPUT_LEN: usize = 32;
77
78    /// Minimum digest size in bytes.
79    pub const MIN_OUTPUT_LEN: usize = 4;
80
81    /// Maximum digest size in bytes.
82    pub const MAX_OUTPUT_LEN: usize = 0xFFFFFFFF;
83
84    /// Default parameters (recommended).
85    pub const DEFAULT: Self = Params {
86        m_cost: Self::DEFAULT_M_COST,
87        t_cost: Self::DEFAULT_T_COST,
88        p_cost: Self::DEFAULT_P_COST,
89        keyid: KeyId {
90            bytes: [0u8; Self::MAX_KEYID_LEN],
91            len: 0,
92        },
93        data: AssociatedData {
94            bytes: [0u8; Self::MAX_DATA_LEN],
95            len: 0,
96        },
97        output_len: None,
98    };
99
100    /// Create new parameters.
101    ///
102    /// # Arguments
103    /// - `m_cost`: memory size in 1 KiB blocks. Between 8\*`p_cost` and (2^32)-1.
104    /// - `t_cost`: number of iterations. Between 1 and (2^32)-1.
105    /// - `p_cost`: degree of parallelism. Between 1 and (2^24)-1.
106    /// - `output_len`: size of the KDF output in bytes. Default 32.
107    pub const fn new(
108        m_cost: u32,
109        t_cost: u32,
110        p_cost: u32,
111        output_len: Option<usize>,
112    ) -> Result<Self> {
113        if m_cost < Params::MIN_M_COST {
114            return Err(Error::MemoryTooLittle);
115        }
116
117        // Note: we don't need to check `MAX_M_COST`, since it's `u32::MAX`
118
119        if m_cost < p_cost * 8 {
120            return Err(Error::MemoryTooLittle);
121        }
122
123        if t_cost < Params::MIN_T_COST {
124            return Err(Error::TimeTooSmall);
125        }
126
127        // Note: we don't need to check `MAX_T_COST`, since it's `u32::MAX`
128
129        if p_cost < Params::MIN_P_COST {
130            return Err(Error::ThreadsTooFew);
131        }
132
133        if p_cost > Params::MAX_P_COST {
134            return Err(Error::ThreadsTooMany);
135        }
136
137        if let Some(len) = output_len {
138            if len < Params::MIN_OUTPUT_LEN {
139                return Err(Error::OutputTooShort);
140            }
141
142            if len > Params::MAX_OUTPUT_LEN {
143                return Err(Error::OutputTooLong);
144            }
145        }
146
147        Ok(Params {
148            m_cost,
149            t_cost,
150            p_cost,
151            keyid: KeyId::EMPTY,
152            data: AssociatedData::EMPTY,
153            output_len,
154        })
155    }
156
157    /// Memory size, expressed in kibibytes. Between 8\*`p_cost` and (2^32)-1.
158    ///
159    /// Value is an integer in decimal (1 to 10 digits).
160    pub const fn m_cost(&self) -> u32 {
161        self.m_cost
162    }
163
164    /// Number of iterations. Between 1 and (2^32)-1.
165    ///
166    /// Value is an integer in decimal (1 to 10 digits).
167    pub const fn t_cost(&self) -> u32 {
168        self.t_cost
169    }
170
171    /// Degree of parallelism. Between 1 and (2^24)-1.
172    ///
173    /// Value is an integer in decimal (1 to 3 digits).
174    pub const fn p_cost(&self) -> u32 {
175        self.p_cost
176    }
177
178    /// Key identifier: byte slice between 0 and 8 bytes in length.
179    ///
180    /// Defaults to an empty byte slice.
181    ///
182    /// Note this field is only present as a helper for reading/storing in
183    /// the PHC hash string format (i.e. it is totally ignored from a
184    /// cryptographical standpoint).
185    ///
186    /// On top of that, this field is not longer part of the Argon2 standard
187    /// (see: <https://github.com/P-H-C/phc-winner-argon2/pull/173>), and should
188    /// not be used for any non-legacy work.
189    pub fn keyid(&self) -> &[u8] {
190        self.keyid.as_bytes()
191    }
192
193    /// Associated data: byte slice between 0 and 32 bytes in length.
194    ///
195    /// Defaults to an empty byte slice.
196    ///
197    /// This field is not longer part of the argon2 standard
198    /// (see: <https://github.com/P-H-C/phc-winner-argon2/pull/173>), and should
199    /// not be used for any non-legacy work.
200    pub fn data(&self) -> &[u8] {
201        self.data.as_bytes()
202    }
203
204    /// Length of the output (in bytes).
205    pub const fn output_len(&self) -> Option<usize> {
206        self.output_len
207    }
208
209    /// Get the number of lanes.
210    #[allow(clippy::cast_possible_truncation)]
211    pub(crate) const fn lanes(&self) -> usize {
212        self.p_cost as usize
213    }
214
215    /// Get the number of blocks in a lane.
216    pub(crate) const fn lane_length(&self) -> usize {
217        self.segment_length() * SYNC_POINTS
218    }
219
220    /// Get the segment length given the configured `m_cost` and `p_cost`.
221    ///
222    /// Minimum memory_blocks = 8*`L` blocks, where `L` is the number of lanes.
223    pub(crate) const fn segment_length(&self) -> usize {
224        let m_cost = self.m_cost as usize;
225
226        let memory_blocks = if m_cost < 2 * SYNC_POINTS * self.lanes() {
227            2 * SYNC_POINTS * self.lanes()
228        } else {
229            m_cost
230        };
231
232        memory_blocks / (self.lanes() * SYNC_POINTS)
233    }
234
235    /// Get the number of blocks required given the configured `m_cost` and `p_cost`.
236    pub const fn block_count(&self) -> usize {
237        self.segment_length() * self.lanes() * SYNC_POINTS
238    }
239}
240
241impl Default for Params {
242    fn default() -> Params {
243        Params::DEFAULT
244    }
245}
246
247macro_rules! param_buf {
248    ($ty:ident, $name:expr, $max_len:expr, $error:expr, $doc:expr) => {
249        #[doc = $doc]
250        #[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)]
251        pub struct $ty {
252            /// Byte array
253            bytes: [u8; Self::MAX_LEN],
254
255            /// Length of byte array
256            len: usize,
257        }
258
259        impl $ty {
260            /// Maximum length in bytes
261            pub const MAX_LEN: usize = $max_len;
262
263            #[doc = "Create a new"]
264            #[doc = $name]
265            #[doc = "from a slice."]
266            pub fn new(slice: &[u8]) -> Result<Self> {
267                let mut bytes = [0u8; Self::MAX_LEN];
268                let len = slice.len();
269                bytes.get_mut(..len).ok_or($error)?.copy_from_slice(slice);
270
271                Ok(Self { bytes, len })
272            }
273
274            /// Empty value.
275            pub const EMPTY: Self = Self {
276                bytes: [0u8; Self::MAX_LEN],
277                len: 0,
278            };
279
280            #[doc = "Decode"]
281            #[doc = $name]
282            #[doc = " from a B64 string"]
283            pub fn from_b64(s: &str) -> Result<Self> {
284                let mut bytes = [0u8; Self::MAX_LEN];
285                let len = B64::decode(s, &mut bytes)?.len();
286
287                Ok(Self { bytes, len })
288            }
289
290            /// Borrow the inner value as a byte slice.
291            pub fn as_bytes(&self) -> &[u8] {
292                &self.bytes[..self.len]
293            }
294
295            /// Get the length in bytes.
296            pub const fn len(&self) -> usize {
297                self.len
298            }
299
300            /// Is this value empty?
301            pub const fn is_empty(&self) -> bool {
302                self.len() == 0
303            }
304        }
305
306        impl AsRef<[u8]> for $ty {
307            fn as_ref(&self) -> &[u8] {
308                self.as_bytes()
309            }
310        }
311
312        impl FromStr for $ty {
313            type Err = Error;
314
315            fn from_str(s: &str) -> Result<Self> {
316                Self::from_b64(s)
317            }
318        }
319
320        impl TryFrom<&[u8]> for $ty {
321            type Error = Error;
322
323            fn try_from(bytes: &[u8]) -> Result<Self> {
324                Self::new(bytes)
325            }
326        }
327    };
328}
329
330// KeyId
331param_buf!(
332    KeyId,
333    "KeyId",
334    Params::MAX_KEYID_LEN,
335    Error::KeyIdTooLong,
336    "Key identifier"
337);
338
339// AssociatedData
340param_buf!(
341    AssociatedData,
342    "AssociatedData",
343    Params::MAX_DATA_LEN,
344    Error::AdTooLong,
345    "Associated data"
346);
347
348#[cfg(feature = "password-hash")]
349#[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))]
350impl<'a> TryFrom<&'a PasswordHash<'a>> for Params {
351    type Error = password_hash::Error;
352
353    fn try_from(hash: &'a PasswordHash<'a>) -> password_hash::Result<Self> {
354        let mut builder = ParamsBuilder::new();
355
356        for (ident, value) in hash.params.iter() {
357            match ident.as_str() {
358                "m" => {
359                    builder.m_cost(value.decimal()?);
360                }
361                "t" => {
362                    builder.t_cost(value.decimal()?);
363                }
364                "p" => {
365                    builder.p_cost(value.decimal()?);
366                }
367                "keyid" => {
368                    builder.keyid(value.as_str().parse()?);
369                }
370                "data" => {
371                    builder.data(value.as_str().parse()?);
372                }
373                _ => return Err(password_hash::Error::ParamNameInvalid),
374            }
375        }
376
377        if let Some(output) = &hash.hash {
378            builder.output_len(output.len());
379        }
380
381        Ok(builder.build()?)
382    }
383}
384
385#[cfg(feature = "password-hash")]
386#[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))]
387impl TryFrom<Params> for ParamsString {
388    type Error = password_hash::Error;
389
390    fn try_from(params: Params) -> password_hash::Result<ParamsString> {
391        ParamsString::try_from(&params)
392    }
393}
394
395#[cfg(feature = "password-hash")]
396#[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))]
397impl TryFrom<&Params> for ParamsString {
398    type Error = password_hash::Error;
399
400    fn try_from(params: &Params) -> password_hash::Result<ParamsString> {
401        let mut output = ParamsString::new();
402        output.add_decimal("m", params.m_cost)?;
403        output.add_decimal("t", params.t_cost)?;
404        output.add_decimal("p", params.p_cost)?;
405
406        if !params.keyid.is_empty() {
407            output.add_b64_bytes("keyid", params.keyid.as_bytes())?;
408        }
409
410        if !params.data.is_empty() {
411            output.add_b64_bytes("data", params.data.as_bytes())?;
412        }
413
414        Ok(output)
415    }
416}
417
418/// Builder for Argon2 [`Params`].
419#[derive(Clone, Debug, Eq, PartialEq)]
420pub struct ParamsBuilder {
421    m_cost: u32,
422    t_cost: u32,
423    p_cost: u32,
424    keyid: Option<KeyId>,
425    data: Option<AssociatedData>,
426    output_len: Option<usize>,
427}
428
429impl ParamsBuilder {
430    /// Create a new builder with the default parameters.
431    pub const fn new() -> Self {
432        Self::DEFAULT
433    }
434
435    /// Set memory size, expressed in kibibytes, between 8\*`p_cost` and (2^32)-1.
436    pub fn m_cost(&mut self, m_cost: u32) -> &mut Self {
437        self.m_cost = m_cost;
438        self
439    }
440
441    /// Set number of iterations, between 1 and (2^32)-1.
442    pub fn t_cost(&mut self, t_cost: u32) -> &mut Self {
443        self.t_cost = t_cost;
444        self
445    }
446
447    /// Set degree of parallelism, between 1 and (2^24)-1.
448    pub fn p_cost(&mut self, p_cost: u32) -> &mut Self {
449        self.p_cost = p_cost;
450        self
451    }
452
453    /// Set key identifier.
454    pub fn keyid(&mut self, keyid: KeyId) -> &mut Self {
455        self.keyid = Some(keyid);
456        self
457    }
458
459    /// Set associated data.
460    pub fn data(&mut self, data: AssociatedData) -> &mut Self {
461        self.data = Some(data);
462        self
463    }
464
465    /// Set length of the output (in bytes).
466    pub fn output_len(&mut self, len: usize) -> &mut Self {
467        self.output_len = Some(len);
468        self
469    }
470
471    /// Get the finished [`Params`].
472    ///
473    /// This performs validations to ensure that the given parameters are valid
474    /// and compatible with each other, and will return an error if they are not.
475    pub const fn build(&self) -> Result<Params> {
476        let mut params = match Params::new(self.m_cost, self.t_cost, self.p_cost, self.output_len) {
477            Ok(params) => params,
478            Err(err) => return Err(err),
479        };
480
481        if let Some(keyid) = self.keyid {
482            params.keyid = keyid;
483        }
484
485        if let Some(data) = self.data {
486            params.data = data;
487        };
488
489        Ok(params)
490    }
491
492    /// Create a new [`Argon2`] context using the provided algorithm/version.
493    pub fn context(&self, algorithm: Algorithm, version: Version) -> Result<Argon2<'_>> {
494        Ok(Argon2::new(algorithm, version, self.build()?))
495    }
496    /// Default parameters (recommended).
497    pub const DEFAULT: ParamsBuilder = {
498        let params = Params::DEFAULT;
499        Self {
500            m_cost: params.m_cost,
501            t_cost: params.t_cost,
502            p_cost: params.p_cost,
503            keyid: None,
504            data: None,
505            output_len: params.output_len,
506        }
507    };
508}
509
510impl Default for ParamsBuilder {
511    fn default() -> Self {
512        Self::DEFAULT
513    }
514}
515
516impl TryFrom<ParamsBuilder> for Params {
517    type Error = Error;
518
519    fn try_from(builder: ParamsBuilder) -> Result<Params> {
520        builder.build()
521    }
522}
523
524#[cfg(all(test, feature = "alloc", feature = "password-hash"))]
525mod tests {
526
527    use super::*;
528
529    #[test]
530    fn params_builder_bad_values() {
531        assert_eq!(
532            ParamsBuilder::new().m_cost(Params::MIN_M_COST - 1).build(),
533            Err(Error::MemoryTooLittle)
534        );
535        assert_eq!(
536            ParamsBuilder::new().t_cost(Params::MIN_T_COST - 1).build(),
537            Err(Error::TimeTooSmall)
538        );
539        assert_eq!(
540            ParamsBuilder::new().p_cost(Params::MIN_P_COST - 1).build(),
541            Err(Error::ThreadsTooFew)
542        );
543        assert_eq!(
544            ParamsBuilder::new()
545                .m_cost(Params::DEFAULT_P_COST * 8 - 1)
546                .build(),
547            Err(Error::MemoryTooLittle)
548        );
549        assert_eq!(
550            ParamsBuilder::new()
551                .m_cost((Params::MAX_P_COST + 1) * 8)
552                .p_cost(Params::MAX_P_COST + 1)
553                .build(),
554            Err(Error::ThreadsTooMany)
555        );
556    }
557
558    #[test]
559    fn associated_data_too_long() {
560        let ret = AssociatedData::new(&[0u8; Params::MAX_DATA_LEN + 1]);
561        assert_eq!(ret, Err(Error::AdTooLong));
562    }
563
564    #[test]
565    fn keyid_too_long() {
566        let ret = KeyId::new(&[0u8; Params::MAX_KEYID_LEN + 1]);
567        assert_eq!(ret, Err(Error::KeyIdTooLong));
568    }
569}