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

pkcs5/pbes2/
kdf.rs

1//! Key derivation functions.
2
3mod salt;
4
5pub use self::salt::Salt;
6
7use crate::{AlgorithmIdentifierRef, Error, Result};
8use der::{
9    Decode, DecodeValue, Encode, EncodeValue, ErrorKind, Length, Reader, Sequence, Tag, Tagged,
10    Writer,
11    asn1::{AnyRef, ObjectIdentifier},
12};
13
14/// Password-Based Key Derivation Function (PBKDF2) OID.
15pub const PBKDF2_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.5.12");
16
17/// HMAC-SHA1 (for use with PBKDF2)
18pub const HMAC_WITH_SHA1_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.2.7");
19
20/// HMAC-SHA-224 (for use with PBKDF2)
21pub const HMAC_WITH_SHA224_OID: ObjectIdentifier =
22    ObjectIdentifier::new_unwrap("1.2.840.113549.2.8");
23
24/// HMAC-SHA-256 (for use with PBKDF2)
25pub const HMAC_WITH_SHA256_OID: ObjectIdentifier =
26    ObjectIdentifier::new_unwrap("1.2.840.113549.2.9");
27
28/// HMAC-SHA-384 (for use with PBKDF2)
29pub const HMAC_WITH_SHA384_OID: ObjectIdentifier =
30    ObjectIdentifier::new_unwrap("1.2.840.113549.2.10");
31
32/// HMAC-SHA-512 (for use with PBKDF2)
33pub const HMAC_WITH_SHA512_OID: ObjectIdentifier =
34    ObjectIdentifier::new_unwrap("1.2.840.113549.2.11");
35
36/// `id-scrypt` ([RFC 7914])
37///
38/// [RFC 7914]: https://datatracker.ietf.org/doc/html/rfc7914#section-7
39pub const SCRYPT_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.3.6.1.4.1.11591.4.11");
40
41/// Type used for expressing scrypt cost
42type ScryptCost = u64;
43
44/// Password-based key derivation function.
45#[derive(Clone, Debug, Eq, PartialEq)]
46#[non_exhaustive]
47pub enum Kdf {
48    /// Password-Based Key Derivation Function 2 (PBKDF2).
49    Pbkdf2(Pbkdf2Params),
50
51    /// scrypt sequential memory-hard password hashing function.
52    Scrypt(ScryptParams),
53}
54
55impl Kdf {
56    /// Get derived key length in bytes, if defined.
57    // TODO(tarcieri): rename to `key_size` to match `EncryptionScheme::key_size`?
58    #[must_use]
59    pub fn key_length(&self) -> Option<u16> {
60        match self {
61            Self::Pbkdf2(params) => params.key_length,
62            Self::Scrypt(params) => params.key_length,
63        }
64    }
65
66    /// Get the [`ObjectIdentifier`] (a.k.a OID) for this algorithm.
67    #[must_use]
68    pub fn oid(&self) -> ObjectIdentifier {
69        match self {
70            Self::Pbkdf2(_) => PBKDF2_OID,
71            Self::Scrypt(_) => SCRYPT_OID,
72        }
73    }
74
75    /// Get [`Pbkdf2Params`] if it is the selected algorithm.
76    #[must_use]
77    pub fn pbkdf2(&self) -> Option<&Pbkdf2Params> {
78        match self {
79            Self::Pbkdf2(params) => Some(params),
80            _ => None,
81        }
82    }
83
84    /// Get [`ScryptParams`] if it is the selected algorithm.
85    #[must_use]
86    pub fn scrypt(&self) -> Option<&ScryptParams> {
87        match self {
88            Self::Scrypt(params) => Some(params),
89            _ => None,
90        }
91    }
92
93    /// Is the selected KDF PBKDF2?
94    #[must_use]
95    pub fn is_pbkdf2(&self) -> bool {
96        self.pbkdf2().is_some()
97    }
98
99    /// Is the selected KDF scrypt?
100    #[must_use]
101    pub fn is_scrypt(&self) -> bool {
102        self.scrypt().is_some()
103    }
104
105    /// Convenience function to turn the OID (see [`oid`](Self::oid))
106    /// of this [`Kdf`] into error case [`Error::AlgorithmParametersInvalid`]
107    #[must_use]
108    pub fn to_alg_params_invalid(&self) -> Error {
109        Error::AlgorithmParametersInvalid { oid: self.oid() }
110    }
111}
112
113impl<'a> DecodeValue<'a> for Kdf {
114    type Error = der::Error;
115
116    fn decode_value<R: Reader<'a>>(reader: &mut R, header: der::Header) -> der::Result<Self> {
117        AlgorithmIdentifierRef::decode_value(reader, header)?.try_into()
118    }
119}
120
121impl EncodeValue for Kdf {
122    fn value_len(&self) -> der::Result<Length> {
123        self.oid().encoded_len()?
124            + match self {
125                Self::Pbkdf2(params) => params.encoded_len()?,
126                Self::Scrypt(params) => params.encoded_len()?,
127            }
128    }
129
130    fn encode_value(&self, writer: &mut impl Writer) -> der::Result<()> {
131        self.oid().encode(writer)?;
132
133        match self {
134            Self::Pbkdf2(params) => params.encode(writer)?,
135            Self::Scrypt(params) => params.encode(writer)?,
136        }
137
138        Ok(())
139    }
140}
141
142impl Sequence<'_> for Kdf {}
143
144impl From<Pbkdf2Params> for Kdf {
145    fn from(params: Pbkdf2Params) -> Self {
146        Kdf::Pbkdf2(params)
147    }
148}
149
150impl From<ScryptParams> for Kdf {
151    fn from(params: ScryptParams) -> Self {
152        Kdf::Scrypt(params)
153    }
154}
155
156impl TryFrom<AlgorithmIdentifierRef<'_>> for Kdf {
157    type Error = der::Error;
158
159    fn try_from(alg: AlgorithmIdentifierRef<'_>) -> der::Result<Self> {
160        if let Some(params) = alg.parameters {
161            match alg.oid {
162                PBKDF2_OID => params.try_into().map(Self::Pbkdf2),
163                SCRYPT_OID => params.try_into().map(Self::Scrypt),
164                oid => Err(ErrorKind::OidUnknown { oid }.into()),
165            }
166        } else {
167            Err(Tag::OctetString.value_error().into())
168        }
169    }
170}
171
172/// Password-Based Key Derivation Scheme 2 parameters as defined in
173/// [RFC 8018 Appendix A.2].
174///
175/// ```text
176/// PBKDF2-params ::= SEQUENCE {
177///     salt CHOICE {
178///         specified OCTET STRING,
179///         otherSource AlgorithmIdentifier {{PBKDF2-SaltSources}}
180///     },
181///     iterationCount INTEGER (1..MAX),
182///     keyLength INTEGER (1..MAX) OPTIONAL,
183///     prf AlgorithmIdentifier {{PBKDF2-PRFs}} DEFAULT
184///     algid-hmacWithSHA1 }
185/// ```
186///
187/// [RFC 8018 Appendix A.2]: https://tools.ietf.org/html/rfc8018#appendix-A.2
188#[derive(Clone, Copy, Debug, Eq, PartialEq)]
189pub struct Pbkdf2Params {
190    /// PBKDF2 salt
191    // TODO(tarcieri): support `CHOICE` with `otherSource`
192    pub salt: Salt,
193
194    /// PBKDF2 iteration count
195    pub iteration_count: u32,
196
197    /// PBKDF2 output length
198    pub key_length: Option<u16>,
199
200    /// Pseudo-random function to use with PBKDF2
201    pub prf: Pbkdf2Prf,
202}
203
204impl Pbkdf2Params {
205    /// Implementation defined maximum iteration count of 100,000,000.
206    ///
207    /// > For especially critical keys, or
208    /// > for very powerful systems or systems where user-perceived
209    /// > performance is not critical, an iteration count of 10,000,000 may
210    /// > be appropriate.
211    ///
212    /// See [RFC 8018, §4.2](https://datatracker.ietf.org/doc/html/rfc8018#section-4.2)
213    /// and [RFC 8018, §A.2](https://datatracker.ietf.org/doc/html/rfc8018#appendix-A.2)
214    pub const MAX_ITERATION_COUNT: u32 = 100_000_000;
215
216    /// OWASP recommended number of iterations for PBKDF2-HMAC-SHA256.
217    #[cfg(all(feature = "pbes2", feature = "rand_core"))]
218    pub(super) const DEFAULT_SHA256_ITERATIONS: u32 = 600_000;
219
220    const INVALID_ERR: Error = Error::AlgorithmParametersInvalid { oid: PBKDF2_OID };
221
222    /// Initialize PBKDF2-HMAC-SHA256 with the given iteration count and salt.
223    ///
224    /// # Errors
225    /// Returns [`Error::AlgorithmParametersInvalid`] if `iteration_count` exceeds
226    /// [`Pbkdf2Params::MAX_ITERATION_COUNT`] or `salt` exceeds [`Salt::MAX_LEN`].
227    pub fn hmac_sha256(iteration_count: u32, salt: &[u8]) -> Result<Self> {
228        if iteration_count > Self::MAX_ITERATION_COUNT {
229            return Err(Self::INVALID_ERR);
230        }
231
232        Ok(Self {
233            salt: salt.try_into().map_err(|_| Self::INVALID_ERR)?,
234            iteration_count,
235            key_length: None,
236            prf: Pbkdf2Prf::HmacWithSha256,
237        })
238    }
239}
240
241impl<'a> DecodeValue<'a> for Pbkdf2Params {
242    type Error = der::Error;
243    fn decode_value<R: Reader<'a>>(reader: &mut R, header: der::Header) -> der::Result<Self> {
244        AnyRef::decode_value(reader, header)?.try_into()
245    }
246}
247
248impl EncodeValue for Pbkdf2Params {
249    fn value_len(&self) -> der::Result<Length> {
250        let len = self.salt.encoded_len()?
251            + self.iteration_count.encoded_len()?
252            + self.key_length.encoded_len()?;
253
254        if self.prf == Pbkdf2Prf::default() {
255            len
256        } else {
257            len + self.prf.encoded_len()?
258        }
259    }
260
261    fn encode_value(&self, writer: &mut impl Writer) -> der::Result<()> {
262        self.salt.encode(writer)?;
263        self.iteration_count.encode(writer)?;
264        self.key_length.encode(writer)?;
265
266        if self.prf == Pbkdf2Prf::default() {
267            Ok(())
268        } else {
269            self.prf.encode(writer)
270        }
271    }
272}
273
274impl Sequence<'_> for Pbkdf2Params {}
275
276impl TryFrom<AnyRef<'_>> for Pbkdf2Params {
277    type Error = der::Error;
278
279    fn try_from(any: AnyRef<'_>) -> der::Result<Self> {
280        any.sequence(|reader| {
281            // TODO(tarcieri): support salt `CHOICE` w\ `AlgorithmIdentifier`
282            Ok(Self {
283                salt: reader.decode()?,
284                iteration_count: reader.decode()?,
285                key_length: reader.decode()?,
286                prf: Option::<AlgorithmIdentifierRef<'_>>::decode(reader)?
287                    .map(TryInto::try_into)
288                    .transpose()?
289                    .unwrap_or_default(),
290            })
291        })
292    }
293}
294
295/// Pseudo-random function used by PBKDF2.
296#[derive(Copy, Clone, Debug, Eq, PartialEq)]
297#[non_exhaustive]
298pub enum Pbkdf2Prf {
299    /// HMAC with SHA1
300    HmacWithSha1,
301
302    /// HMAC with SHA-224
303    HmacWithSha224,
304
305    /// HMAC with SHA-256
306    HmacWithSha256,
307
308    /// HMAC with SHA-384
309    HmacWithSha384,
310
311    /// HMAC with SHA-512
312    HmacWithSha512,
313}
314
315impl Pbkdf2Prf {
316    /// Get the [`ObjectIdentifier`] (a.k.a OID) for this algorithm.
317    #[must_use]
318    pub fn oid(self) -> ObjectIdentifier {
319        match self {
320            Self::HmacWithSha1 => HMAC_WITH_SHA1_OID,
321            Self::HmacWithSha224 => HMAC_WITH_SHA224_OID,
322            Self::HmacWithSha256 => HMAC_WITH_SHA256_OID,
323            Self::HmacWithSha384 => HMAC_WITH_SHA384_OID,
324            Self::HmacWithSha512 => HMAC_WITH_SHA512_OID,
325        }
326    }
327}
328
329/// Default PRF as specified in RFC 8018 Appendix A.2:
330///
331/// ```text
332/// prf AlgorithmIdentifier {{PBKDF2-PRFs}} DEFAULT algid-hmacWithSHA1
333/// ```
334///
335/// Note that modern usage should avoid the use of SHA-1, despite it being
336/// the "default" here.
337impl Default for Pbkdf2Prf {
338    fn default() -> Self {
339        Self::HmacWithSha1
340    }
341}
342
343impl TryFrom<AlgorithmIdentifierRef<'_>> for Pbkdf2Prf {
344    type Error = der::Error;
345
346    fn try_from(alg: AlgorithmIdentifierRef<'_>) -> der::Result<Self> {
347        if let Some(params) = alg.parameters {
348            // TODO(tarcieri): support non-NULL parameters?
349            if !params.is_null() {
350                return Err(params.tag().value_error().into());
351            }
352        }
353
354        match alg.oid {
355            HMAC_WITH_SHA1_OID => Ok(Self::HmacWithSha1),
356            HMAC_WITH_SHA224_OID => Ok(Self::HmacWithSha224),
357            HMAC_WITH_SHA256_OID => Ok(Self::HmacWithSha256),
358            HMAC_WITH_SHA384_OID => Ok(Self::HmacWithSha384),
359            HMAC_WITH_SHA512_OID => Ok(Self::HmacWithSha512),
360            oid => Err(ErrorKind::OidUnknown { oid }.into()),
361        }
362    }
363}
364
365impl From<Pbkdf2Prf> for AlgorithmIdentifierRef<'_> {
366    fn from(prf: Pbkdf2Prf) -> Self {
367        // TODO(tarcieri): support non-NULL parameters?
368        let parameters = der::asn1::Null;
369
370        AlgorithmIdentifierRef {
371            oid: prf.oid(),
372            parameters: Some(parameters.into()),
373        }
374    }
375}
376
377impl Encode for Pbkdf2Prf {
378    fn encoded_len(&self) -> der::Result<Length> {
379        AlgorithmIdentifierRef::from(*self).encoded_len()
380    }
381
382    fn encode(&self, writer: &mut impl Writer) -> der::Result<()> {
383        AlgorithmIdentifierRef::from(*self).encode(writer)
384    }
385}
386
387/// scrypt parameters as defined in [RFC 7914 Section 7.1].
388///
389/// ```text
390/// scrypt-params ::= SEQUENCE {
391///     salt OCTET STRING,
392///     costParameter INTEGER (1..MAX),
393///     blockSize INTEGER (1..MAX),
394///     parallelizationParameter INTEGER (1..MAX),
395///     keyLength INTEGER (1..MAX) OPTIONAL
396/// }
397/// ```
398///
399/// [RFC 7914 Section 7.1]: https://datatracker.ietf.org/doc/html/rfc7914#section-7.1
400#[derive(Clone, Copy, Debug, Eq, PartialEq)]
401pub struct ScryptParams {
402    /// scrypt salt
403    pub salt: Salt,
404
405    /// CPU/Memory cost parameter `N`.
406    pub cost_parameter: ScryptCost,
407
408    /// Block size parameter `r`.
409    pub block_size: u16,
410
411    /// Parallelization parameter `p`.
412    pub parallelization: u16,
413
414    /// PBKDF2 output length
415    pub key_length: Option<u16>,
416}
417
418impl ScryptParams {
419    // NOTE: scrypt parameters are deliberately chosen to retain compatibility with OpenSSL v3.
420    // See RustCrypto/formats#1205 for more information.
421    #[cfg(all(feature = "pbes2", feature = "rand_core"))]
422    pub(super) const DEFAULT_LOG_N: u8 = 14;
423    #[cfg(all(feature = "pbes2", feature = "rand_core"))]
424    pub(super) const DEFAULT_R: u32 = 8;
425    #[cfg(all(feature = "pbes2", feature = "rand_core"))]
426    pub(super) const DEFAULT_P: u32 = 1;
427
428    #[cfg(feature = "pbes2")]
429    const INVALID_ERR: Error = Error::AlgorithmParametersInvalid { oid: SCRYPT_OID };
430
431    /// Get the [`ScryptParams`] for the provided upstream [`scrypt::Params`]
432    /// and a provided salt string.
433    ///
434    /// # Errors
435    /// Returns [`Error::AlgorithmParametersInvalid`] if the parameters are invalid.
436    // TODO(tarcieri): encapsulate `scrypt::Params`?
437    #[cfg(feature = "pbes2")]
438    pub fn from_params_and_salt(params: scrypt::Params, salt: &[u8]) -> Result<Self> {
439        Ok(Self {
440            salt: salt.try_into().map_err(|_| Self::INVALID_ERR)?,
441            cost_parameter: 1 << params.log_n(),
442            block_size: params.r().try_into().map_err(|_| Self::INVALID_ERR)?,
443            parallelization: params.p().try_into().map_err(|_| Self::INVALID_ERR)?,
444            key_length: None,
445        })
446    }
447}
448
449impl<'a> DecodeValue<'a> for ScryptParams {
450    type Error = der::Error;
451
452    fn decode_value<R: Reader<'a>>(reader: &mut R, header: der::Header) -> der::Result<Self> {
453        AnyRef::decode_value(reader, header)?.try_into()
454    }
455}
456
457impl EncodeValue for ScryptParams {
458    fn value_len(&self) -> der::Result<Length> {
459        self.salt.encoded_len()?
460            + self.cost_parameter.encoded_len()?
461            + self.block_size.encoded_len()?
462            + self.parallelization.encoded_len()?
463            + self.key_length.encoded_len()?
464    }
465
466    fn encode_value(&self, writer: &mut impl Writer) -> der::Result<()> {
467        self.salt.encode(writer)?;
468        self.cost_parameter.encode(writer)?;
469        self.block_size.encode(writer)?;
470        self.parallelization.encode(writer)?;
471        self.key_length.encode(writer)?;
472        Ok(())
473    }
474}
475
476impl Sequence<'_> for ScryptParams {}
477
478impl TryFrom<AnyRef<'_>> for ScryptParams {
479    type Error = der::Error;
480
481    fn try_from(any: AnyRef<'_>) -> der::Result<Self> {
482        any.sequence(|reader| {
483            Ok(Self {
484                salt: reader.decode()?,
485                cost_parameter: reader.decode()?,
486                block_size: reader.decode()?,
487                parallelization: reader.decode()?,
488                key_length: reader.decode()?,
489            })
490        })
491    }
492}
493
494#[cfg(feature = "pbes2")]
495impl TryFrom<ScryptParams> for scrypt::Params {
496    type Error = Error;
497
498    fn try_from(params: ScryptParams) -> Result<scrypt::Params> {
499        scrypt::Params::try_from(&params)
500    }
501}
502
503#[cfg(feature = "pbes2")]
504impl TryFrom<&ScryptParams> for scrypt::Params {
505    type Error = Error;
506
507    #[allow(clippy::unwrap_in_result, reason = "invariant should hold")]
508    fn try_from(params: &ScryptParams) -> Result<scrypt::Params> {
509        // Compute log2 and verify its correctness
510        let n = params.cost_parameter;
511        let log_n =
512            u8::try_from(ScryptCost::BITS - n.leading_zeros() - 1).expect("should always fit");
513
514        if 1 << log_n != n {
515            return Err(ScryptParams::INVALID_ERR);
516        }
517
518        scrypt::Params::new(
519            log_n,
520            params.block_size.into(),
521            params.parallelization.into(),
522        )
523        .map_err(|_| ScryptParams::INVALID_ERR)
524    }
525}