/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 | | } |