Coverage Report

Created: 2026-03-31 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/pbkdf2-0.12.2/src/simple.rs
Line
Count
Source
1
//! Implementation of the `password-hash` crate API.
2
3
use crate::pbkdf2_hmac;
4
use core::{cmp::Ordering, fmt, str::FromStr};
5
use password_hash::{
6
    errors::InvalidValue, Decimal, Error, Ident, Output, ParamsString, PasswordHash,
7
    PasswordHasher, Result, Salt,
8
};
9
use sha2::{Sha256, Sha512};
10
11
#[cfg(feature = "sha1")]
12
use sha1::Sha1;
13
14
/// PBKDF2 type for use with [`PasswordHasher`].
15
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
16
#[cfg_attr(docsrs, doc(cfg(feature = "simple")))]
17
pub struct Pbkdf2;
18
19
impl PasswordHasher for Pbkdf2 {
20
    type Params = Params;
21
22
0
    fn hash_password_customized<'a>(
23
0
        &self,
24
0
        password: &[u8],
25
0
        alg_id: Option<Ident<'a>>,
26
0
        version: Option<Decimal>,
27
0
        params: Params,
28
0
        salt: impl Into<Salt<'a>>,
29
0
    ) -> Result<PasswordHash<'a>> {
30
0
        let algorithm = Algorithm::try_from(alg_id.unwrap_or(Algorithm::default().ident()))?;
31
32
        // Versions unsupported
33
0
        if version.is_some() {
34
0
            return Err(Error::Version);
35
0
        }
36
37
0
        let salt = salt.into();
38
0
        let mut salt_arr = [0u8; 64];
39
0
        let salt_bytes = salt.decode_b64(&mut salt_arr)?;
40
41
0
        let output = Output::init_with(params.output_length, |out| {
42
0
            let f = match algorithm {
43
                #[cfg(feature = "sha1")]
44
                Algorithm::Pbkdf2Sha1 => pbkdf2_hmac::<Sha1>,
45
0
                Algorithm::Pbkdf2Sha256 => pbkdf2_hmac::<Sha256>,
46
0
                Algorithm::Pbkdf2Sha512 => pbkdf2_hmac::<Sha512>,
47
            };
48
49
0
            f(password, salt_bytes, params.rounds, out);
50
0
            Ok(())
51
0
        })?;
Unexecuted instantiation: <pbkdf2::simple::Pbkdf2 as password_hash::traits::PasswordHasher>::hash_password_customized::<password_hash::salt::Salt>::{closure#0}
Unexecuted instantiation: <pbkdf2::simple::Pbkdf2 as password_hash::traits::PasswordHasher>::hash_password_customized::<&password_hash::salt::SaltString>::{closure#0}
Unexecuted instantiation: <pbkdf2::simple::Pbkdf2 as password_hash::traits::PasswordHasher>::hash_password_customized::<_>::{closure#0}
52
53
        Ok(PasswordHash {
54
0
            algorithm: algorithm.ident(),
55
0
            version: None,
56
0
            params: params.try_into()?,
57
0
            salt: Some(salt),
58
0
            hash: Some(output),
59
        })
60
0
    }
Unexecuted instantiation: <pbkdf2::simple::Pbkdf2 as password_hash::traits::PasswordHasher>::hash_password_customized::<password_hash::salt::Salt>
Unexecuted instantiation: <pbkdf2::simple::Pbkdf2 as password_hash::traits::PasswordHasher>::hash_password_customized::<&password_hash::salt::SaltString>
Unexecuted instantiation: <pbkdf2::simple::Pbkdf2 as password_hash::traits::PasswordHasher>::hash_password_customized::<_>
61
}
62
63
/// PBKDF2 variants.
64
///
65
/// <https://en.wikipedia.org/wiki/PBKDF2>
66
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
67
#[non_exhaustive]
68
#[cfg_attr(docsrs, doc(cfg(feature = "simple")))]
69
pub enum Algorithm {
70
    /// PBKDF2 SHA1
71
    #[cfg(feature = "sha1")]
72
    #[cfg_attr(docsrs, doc(cfg(feature = "sha1")))]
73
    Pbkdf2Sha1,
74
75
    /// PBKDF2 SHA-256
76
    Pbkdf2Sha256,
77
78
    /// PBKDF2 SHA-512
79
    Pbkdf2Sha512,
80
}
81
82
impl Default for Algorithm {
83
    /// Default suggested by the [OWASP cheat sheet]:
84
    ///
85
    /// > Use PBKDF2 with a work factor of 600,000 or more and set with an
86
    /// > internal hash function of HMAC-SHA-256.
87
    ///
88
    /// [OWASP cheat sheet]: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
89
0
    fn default() -> Self {
90
0
        Self::Pbkdf2Sha256
91
0
    }
92
}
93
94
impl Algorithm {
95
    /// PBKDF2 (SHA-1) algorithm identifier
96
    #[cfg(feature = "sha1")]
97
    pub const PBKDF2_SHA1_IDENT: Ident<'static> = Ident::new_unwrap("pbkdf2");
98
99
    /// PBKDF2 (SHA-256) algorithm identifier
100
    pub const PBKDF2_SHA256_IDENT: Ident<'static> = Ident::new_unwrap("pbkdf2-sha256");
101
102
    /// PBKDF2 (SHA-512) algorithm identifier
103
    pub const PBKDF2_SHA512_IDENT: Ident<'static> = Ident::new_unwrap("pbkdf2-sha512");
104
105
    /// Parse an [`Algorithm`] from the provided string.
106
0
    pub fn new(id: impl AsRef<str>) -> Result<Self> {
107
0
        id.as_ref().parse()
108
0
    }
109
110
    /// Get the [`Ident`] that corresponds to this PBKDF2 [`Algorithm`].
111
0
    pub fn ident(&self) -> Ident<'static> {
112
0
        match self {
113
            #[cfg(feature = "sha1")]
114
            Algorithm::Pbkdf2Sha1 => Self::PBKDF2_SHA1_IDENT,
115
0
            Algorithm::Pbkdf2Sha256 => Self::PBKDF2_SHA256_IDENT,
116
0
            Algorithm::Pbkdf2Sha512 => Self::PBKDF2_SHA512_IDENT,
117
        }
118
0
    }
119
120
    /// Get the identifier string for this PBKDF2 [`Algorithm`].
121
0
    pub fn as_str(&self) -> &str {
122
0
        self.ident().as_str()
123
0
    }
124
}
125
126
impl AsRef<str> for Algorithm {
127
0
    fn as_ref(&self) -> &str {
128
0
        self.as_str()
129
0
    }
130
}
131
132
impl fmt::Display for Algorithm {
133
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134
0
        f.write_str(self.as_str())
135
0
    }
136
}
137
138
impl FromStr for Algorithm {
139
    type Err = Error;
140
141
0
    fn from_str(s: &str) -> Result<Algorithm> {
142
0
        Ident::try_from(s)?.try_into()
143
0
    }
144
}
145
146
impl From<Algorithm> for Ident<'static> {
147
0
    fn from(alg: Algorithm) -> Ident<'static> {
148
0
        alg.ident()
149
0
    }
150
}
151
152
impl<'a> TryFrom<Ident<'a>> for Algorithm {
153
    type Error = Error;
154
155
0
    fn try_from(ident: Ident<'a>) -> Result<Algorithm> {
156
0
        match ident {
157
            #[cfg(feature = "sha1")]
158
            Self::PBKDF2_SHA1_IDENT => Ok(Algorithm::Pbkdf2Sha1),
159
0
            Self::PBKDF2_SHA256_IDENT => Ok(Algorithm::Pbkdf2Sha256),
160
0
            Self::PBKDF2_SHA512_IDENT => Ok(Algorithm::Pbkdf2Sha512),
161
0
            _ => Err(Error::Algorithm),
162
        }
163
0
    }
164
}
165
166
/// PBKDF2 params
167
#[cfg_attr(docsrs, doc(cfg(feature = "simple")))]
168
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
169
pub struct Params {
170
    /// Number of rounds
171
    pub rounds: u32,
172
173
    /// Size of the output (in bytes)
174
    pub output_length: usize,
175
}
176
177
impl Params {
178
    /// Recommended number of PBKDF2 rounds (used by default).
179
    ///
180
    /// This number is adopted from the [OWASP cheat sheet]:
181
    ///
182
    /// > Use PBKDF2 with a work factor of 600,000 or more
183
    ///
184
    /// [OWASP cheat sheet]: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
185
    pub const RECOMMENDED_ROUNDS: usize = 600_000;
186
}
187
188
impl Default for Params {
189
0
    fn default() -> Params {
190
0
        Params {
191
0
            rounds: Self::RECOMMENDED_ROUNDS as u32,
192
0
            output_length: 32,
193
0
        }
194
0
    }
195
}
196
197
impl<'a> TryFrom<&'a PasswordHash<'a>> for Params {
198
    type Error = Error;
199
200
0
    fn try_from(hash: &'a PasswordHash<'a>) -> Result<Self> {
201
0
        let mut params = Params::default();
202
0
        let mut output_length = None;
203
204
0
        if hash.version.is_some() {
205
0
            return Err(Error::Version);
206
0
        }
207
208
0
        for (ident, value) in hash.params.iter() {
209
0
            match ident.as_str() {
210
0
                "i" => params.rounds = value.decimal()?,
211
0
                "l" => {
212
                    output_length = Some(
213
0
                        value
214
0
                            .decimal()?
215
0
                            .try_into()
216
0
                            .map_err(|_| InvalidValue::Malformed.param_error())?,
217
                    )
218
                }
219
0
                _ => return Err(Error::ParamNameInvalid),
220
            }
221
        }
222
223
0
        if let Some(len) = output_length {
224
0
            if let Some(hash) = &hash.hash {
225
0
                match hash.len().cmp(&len) {
226
0
                    Ordering::Less => return Err(InvalidValue::TooShort.param_error()),
227
0
                    Ordering::Greater => return Err(InvalidValue::TooLong.param_error()),
228
0
                    Ordering::Equal => (),
229
                }
230
0
            }
231
232
0
            params.output_length = len;
233
0
        }
234
235
0
        Ok(params)
236
0
    }
237
}
238
239
impl<'a> TryFrom<Params> for ParamsString {
240
    type Error = Error;
241
242
0
    fn try_from(input: Params) -> Result<ParamsString> {
243
0
        let mut output = ParamsString::new();
244
0
        output.add_decimal("i", input.rounds)?;
245
0
        output.add_decimal("l", input.output_length as u32)?;
246
0
        Ok(output)
247
0
    }
248
}