1use crate::pbkdf2_hmac;
4use core::{cmp::Ordering, fmt, str::FromStr};
5use password_hash::{
6 errors::InvalidValue, Decimal, Error, Ident, Output, ParamsString, PasswordHash,
7 PasswordHasher, Result, Salt,
8};
9use sha2::{Sha256, Sha512};
10
11#[cfg(feature = "sha1")]
12use sha1::Sha1;
13
14#[derive(Copy, Clone, Debug, Eq, PartialEq)]
16#[cfg_attr(docsrs, doc(cfg(feature = "simple")))]
17pub struct Pbkdf2;
18
19impl PasswordHasher for Pbkdf2 {
20 type Params = Params;
21
22 fn hash_password_customized<'a>(
23 &self,
24 password: &[u8],
25 alg_id: Option<Ident<'a>>,
26 version: Option<Decimal>,
27 params: Params,
28 salt: impl Into<Salt<'a>>,
29 ) -> Result<PasswordHash<'a>> {
30 let algorithm = Algorithm::try_from(alg_id.unwrap_or(Algorithm::default().ident()))?;
31
32 if version.is_some() {
34 return Err(Error::Version);
35 }
36
37 let salt = salt.into();
38 let mut salt_arr = [0u8; 64];
39 let salt_bytes = salt.decode_b64(&mut salt_arr)?;
40
41 let output = Output::init_with(params.output_length, |out| {
42 let f = match algorithm {
43 #[cfg(feature = "sha1")]
44 Algorithm::Pbkdf2Sha1 => pbkdf2_hmac::<Sha1>,
45 Algorithm::Pbkdf2Sha256 => pbkdf2_hmac::<Sha256>,
46 Algorithm::Pbkdf2Sha512 => pbkdf2_hmac::<Sha512>,
47 };
48
49 f(password, salt_bytes, params.rounds, out);
50 Ok(())
51 })?;
52
53 Ok(PasswordHash {
54 algorithm: algorithm.ident(),
55 version: None,
56 params: params.try_into()?,
57 salt: Some(salt),
58 hash: Some(output),
59 })
60 }
61}
62
63#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
67#[non_exhaustive]
68#[cfg_attr(docsrs, doc(cfg(feature = "simple")))]
69pub enum Algorithm {
70 #[cfg(feature = "sha1")]
72 #[cfg_attr(docsrs, doc(cfg(feature = "sha1")))]
73 Pbkdf2Sha1,
74
75 Pbkdf2Sha256,
77
78 Pbkdf2Sha512,
80}
81
82impl Default for Algorithm {
83 fn default() -> Self {
90 Self::Pbkdf2Sha256
91 }
92}
93
94impl Algorithm {
95 #[cfg(feature = "sha1")]
97 pub const PBKDF2_SHA1_IDENT: Ident<'static> = Ident::new_unwrap("pbkdf2");
98
99 pub const PBKDF2_SHA256_IDENT: Ident<'static> = Ident::new_unwrap("pbkdf2-sha256");
101
102 pub const PBKDF2_SHA512_IDENT: Ident<'static> = Ident::new_unwrap("pbkdf2-sha512");
104
105 pub fn new(id: impl AsRef<str>) -> Result<Self> {
107 id.as_ref().parse()
108 }
109
110 pub fn ident(&self) -> Ident<'static> {
112 match self {
113 #[cfg(feature = "sha1")]
114 Algorithm::Pbkdf2Sha1 => Self::PBKDF2_SHA1_IDENT,
115 Algorithm::Pbkdf2Sha256 => Self::PBKDF2_SHA256_IDENT,
116 Algorithm::Pbkdf2Sha512 => Self::PBKDF2_SHA512_IDENT,
117 }
118 }
119
120 pub fn as_str(&self) -> &str {
122 self.ident().as_str()
123 }
124}
125
126impl AsRef<str> for Algorithm {
127 fn as_ref(&self) -> &str {
128 self.as_str()
129 }
130}
131
132impl fmt::Display for Algorithm {
133 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134 f.write_str(self.as_str())
135 }
136}
137
138impl FromStr for Algorithm {
139 type Err = Error;
140
141 fn from_str(s: &str) -> Result<Algorithm> {
142 Ident::try_from(s)?.try_into()
143 }
144}
145
146impl From<Algorithm> for Ident<'static> {
147 fn from(alg: Algorithm) -> Ident<'static> {
148 alg.ident()
149 }
150}
151
152impl<'a> TryFrom<Ident<'a>> for Algorithm {
153 type Error = Error;
154
155 fn try_from(ident: Ident<'a>) -> Result<Algorithm> {
156 match ident {
157 #[cfg(feature = "sha1")]
158 Self::PBKDF2_SHA1_IDENT => Ok(Algorithm::Pbkdf2Sha1),
159 Self::PBKDF2_SHA256_IDENT => Ok(Algorithm::Pbkdf2Sha256),
160 Self::PBKDF2_SHA512_IDENT => Ok(Algorithm::Pbkdf2Sha512),
161 _ => Err(Error::Algorithm),
162 }
163 }
164}
165
166#[cfg_attr(docsrs, doc(cfg(feature = "simple")))]
168#[derive(Copy, Clone, Debug, Eq, PartialEq)]
169pub struct Params {
170 pub rounds: u32,
172
173 pub output_length: usize,
175}
176
177impl Params {
178 pub const RECOMMENDED_ROUNDS: usize = 600_000;
186}
187
188impl Default for Params {
189 fn default() -> Params {
190 Params {
191 rounds: Self::RECOMMENDED_ROUNDS as u32,
192 output_length: 32,
193 }
194 }
195}
196
197impl<'a> TryFrom<&'a PasswordHash<'a>> for Params {
198 type Error = Error;
199
200 fn try_from(hash: &'a PasswordHash<'a>) -> Result<Self> {
201 let mut params = Params::default();
202 let mut output_length = None;
203
204 if hash.version.is_some() {
205 return Err(Error::Version);
206 }
207
208 for (ident, value) in hash.params.iter() {
209 match ident.as_str() {
210 "i" => params.rounds = value.decimal()?,
211 "l" => {
212 output_length = Some(
213 value
214 .decimal()?
215 .try_into()
216 .map_err(|_| InvalidValue::Malformed.param_error())?,
217 )
218 }
219 _ => return Err(Error::ParamNameInvalid),
220 }
221 }
222
223 if let Some(len) = output_length {
224 if let Some(hash) = &hash.hash {
225 match hash.len().cmp(&len) {
226 Ordering::Less => return Err(InvalidValue::TooShort.param_error()),
227 Ordering::Greater => return Err(InvalidValue::TooLong.param_error()),
228 Ordering::Equal => (),
229 }
230 }
231
232 params.output_length = len;
233 }
234
235 Ok(params)
236 }
237}
238
239impl<'a> TryFrom<Params> for ParamsString {
240 type Error = Error;
241
242 fn try_from(input: Params) -> Result<ParamsString> {
243 let mut output = ParamsString::new();
244 output.add_decimal("i", input.rounds)?;
245 output.add_decimal("l", input.output_length as u32)?;
246 Ok(output)
247 }
248}