Skip to main content

argon2/
params.rs

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