/rust/registry/src/index.crates.io-1949cf8c6b5b557f/password-hash-0.4.2/src/lib.rs
Line | Count | Source |
1 | | #![no_std] |
2 | | #![cfg_attr(docsrs, feature(doc_cfg))] |
3 | | #![doc = include_str!("../README.md")] |
4 | | #![doc( |
5 | | html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", |
6 | | html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg" |
7 | | )] |
8 | | #![forbid(unsafe_code)] |
9 | | #![warn(missing_docs, rust_2018_idioms, unused_lifetimes)] |
10 | | |
11 | | //! |
12 | | //! # Usage |
13 | | //! |
14 | | //! This crate represents password hashes using the [`PasswordHash`] type, which |
15 | | //! represents a parsed "PHC string" with the following format: |
16 | | //! |
17 | | //! ```text |
18 | | //! $<id>[$v=<version>][$<param>=<value>(,<param>=<value>)*][$<salt>[$<hash>]] |
19 | | //! ``` |
20 | | //! |
21 | | //! For more information, please see the documentation for [`PasswordHash`]. |
22 | | |
23 | | #[cfg(feature = "alloc")] |
24 | | extern crate alloc; |
25 | | #[cfg(feature = "std")] |
26 | | extern crate std; |
27 | | |
28 | | #[cfg(feature = "rand_core")] |
29 | | #[cfg_attr(docsrs, doc(cfg(feature = "rand_core")))] |
30 | | pub use rand_core; |
31 | | |
32 | | pub mod errors; |
33 | | |
34 | | mod encoding; |
35 | | mod ident; |
36 | | mod output; |
37 | | mod params; |
38 | | mod salt; |
39 | | mod traits; |
40 | | mod value; |
41 | | |
42 | | pub use crate::{ |
43 | | encoding::Encoding, |
44 | | errors::{Error, Result}, |
45 | | ident::Ident, |
46 | | output::Output, |
47 | | params::ParamsString, |
48 | | salt::{Salt, SaltString}, |
49 | | traits::{McfHasher, PasswordHasher, PasswordVerifier}, |
50 | | value::{Decimal, Value}, |
51 | | }; |
52 | | |
53 | | use core::fmt::{self, Debug}; |
54 | | |
55 | | #[cfg(feature = "alloc")] |
56 | | use alloc::{ |
57 | | str::FromStr, |
58 | | string::{String, ToString}, |
59 | | }; |
60 | | |
61 | | /// Separator character used in password hashes (e.g. `$6$...`). |
62 | | const PASSWORD_HASH_SEPARATOR: char = '$'; |
63 | | |
64 | | /// Password hash. |
65 | | /// |
66 | | /// This type corresponds to the parsed representation of a PHC string as |
67 | | /// described in the [PHC string format specification][1]. |
68 | | /// |
69 | | /// PHC strings have the following format: |
70 | | /// |
71 | | /// ```text |
72 | | /// $<id>[$v=<version>][$<param>=<value>(,<param>=<value>)*][$<salt>[$<hash>]] |
73 | | /// ``` |
74 | | /// |
75 | | /// where: |
76 | | /// |
77 | | /// - `<id>` is the symbolic name for the function |
78 | | /// - `<version>` is the algorithm version |
79 | | /// - `<param>` is a parameter name |
80 | | /// - `<value>` is a parameter value |
81 | | /// - `<salt>` is an encoding of the salt |
82 | | /// - `<hash>` is an encoding of the hash output |
83 | | /// |
84 | | /// The string is then the concatenation, in that order, of: |
85 | | /// |
86 | | /// - a `$` sign; |
87 | | /// - the function symbolic name; |
88 | | /// - optionally, a `$` sign followed by the algorithm version with a `v=version` format; |
89 | | /// - optionally, a `$` sign followed by one or several parameters, each with a `name=value` format; |
90 | | /// the parameters are separated by commas; |
91 | | /// - optionally, a `$` sign followed by the (encoded) salt value; |
92 | | /// - optionally, a `$` sign followed by the (encoded) hash output (the hash output may be present |
93 | | /// only if the salt is present). |
94 | | /// |
95 | | /// [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#specification |
96 | | #[derive(Clone, Debug, Eq, PartialEq)] |
97 | | pub struct PasswordHash<'a> { |
98 | | /// Password hashing algorithm identifier. |
99 | | /// |
100 | | /// This corresponds to the `<id>` field in a PHC string, a.k.a. the |
101 | | /// symbolic name for the function. |
102 | | pub algorithm: Ident<'a>, |
103 | | |
104 | | /// Optional version field. |
105 | | /// |
106 | | /// This corresponds to the `<version>` field in a PHC string. |
107 | | pub version: Option<Decimal>, |
108 | | |
109 | | /// Algorithm-specific parameters. |
110 | | /// |
111 | | /// This corresponds to the set of `$<param>=<value>(,<param>=<value>)*` |
112 | | /// name/value pairs in a PHC string. |
113 | | pub params: ParamsString, |
114 | | |
115 | | /// [`Salt`] string for personalizing a password hash output. |
116 | | /// |
117 | | /// This corresponds to the `<salt>` value in a PHC string. |
118 | | pub salt: Option<Salt<'a>>, |
119 | | |
120 | | /// Password hashing function [`Output`], a.k.a. hash/digest. |
121 | | /// |
122 | | /// This corresponds to the `<hash>` output in a PHC string. |
123 | | pub hash: Option<Output>, |
124 | | } |
125 | | |
126 | | impl<'a> PasswordHash<'a> { |
127 | | /// Parse a password hash from a string in the PHC string format. |
128 | 0 | pub fn new(s: &'a str) -> Result<Self> { |
129 | 0 | Self::parse(s, Encoding::default()) |
130 | 0 | } |
131 | | |
132 | | /// Parse a password hash from the given [`Encoding`]. |
133 | 0 | pub fn parse(s: &'a str, encoding: Encoding) -> Result<Self> { |
134 | 0 | if s.is_empty() { |
135 | 0 | return Err(Error::PhcStringTooShort); |
136 | 0 | } |
137 | | |
138 | 0 | let mut fields = s.split(PASSWORD_HASH_SEPARATOR); |
139 | 0 | let beginning = fields.next().expect("no first field"); |
140 | | |
141 | 0 | if beginning.chars().next().is_some() { |
142 | 0 | return Err(Error::PhcStringInvalid); |
143 | 0 | } |
144 | | |
145 | 0 | let algorithm = fields |
146 | 0 | .next() |
147 | 0 | .ok_or(Error::PhcStringTooShort) |
148 | 0 | .and_then(Ident::try_from)?; |
149 | | |
150 | 0 | let mut version = None; |
151 | 0 | let mut params = ParamsString::new(); |
152 | 0 | let mut salt = None; |
153 | 0 | let mut hash = None; |
154 | | |
155 | 0 | let mut next_field = fields.next(); |
156 | | |
157 | 0 | if let Some(field) = next_field { |
158 | | // v=<version> |
159 | 0 | if field.starts_with("v=") && !field.contains(params::PARAMS_DELIMITER) { |
160 | 0 | version = Some(Value::new(&field[2..]).and_then(|value| value.decimal())?); |
161 | 0 | next_field = None; |
162 | 0 | } |
163 | 0 | } |
164 | | |
165 | 0 | if next_field.is_none() { |
166 | 0 | next_field = fields.next(); |
167 | 0 | } |
168 | | |
169 | 0 | if let Some(field) = next_field { |
170 | | // <param>=<value> |
171 | 0 | if field.contains(params::PAIR_DELIMITER) { |
172 | 0 | params = field.parse()?; |
173 | 0 | next_field = None; |
174 | 0 | } |
175 | 0 | } |
176 | | |
177 | 0 | if next_field.is_none() { |
178 | 0 | next_field = fields.next(); |
179 | 0 | } |
180 | | |
181 | 0 | if let Some(s) = next_field { |
182 | 0 | salt = Some(s.try_into()?); |
183 | 0 | } |
184 | | |
185 | 0 | if let Some(field) = fields.next() { |
186 | 0 | hash = Some(Output::decode(field, encoding)?); |
187 | 0 | } |
188 | | |
189 | 0 | if fields.next().is_some() { |
190 | 0 | return Err(Error::PhcStringTooLong); |
191 | 0 | } |
192 | | |
193 | 0 | Ok(Self { |
194 | 0 | algorithm, |
195 | 0 | version, |
196 | 0 | params, |
197 | 0 | salt, |
198 | 0 | hash, |
199 | 0 | }) |
200 | 0 | } |
201 | | |
202 | | /// Generate a password hash using the supplied algorithm. |
203 | 0 | pub fn generate( |
204 | 0 | phf: impl PasswordHasher, |
205 | 0 | password: impl AsRef<[u8]>, |
206 | 0 | salt: &'a str, |
207 | 0 | ) -> Result<Self> { |
208 | 0 | phf.hash_password(password.as_ref(), salt) |
209 | 0 | } |
210 | | |
211 | | /// Verify this password hash using the specified set of supported |
212 | | /// [`PasswordHasher`] trait objects. |
213 | 0 | pub fn verify_password( |
214 | 0 | &self, |
215 | 0 | phfs: &[&dyn PasswordVerifier], |
216 | 0 | password: impl AsRef<[u8]>, |
217 | 0 | ) -> Result<()> { |
218 | 0 | for &phf in phfs { |
219 | 0 | if phf.verify_password(password.as_ref(), self).is_ok() { |
220 | 0 | return Ok(()); |
221 | 0 | } |
222 | | } |
223 | | |
224 | 0 | Err(Error::Password) |
225 | 0 | } |
226 | | |
227 | | /// Get the [`Encoding`] that this [`PasswordHash`] is serialized with. |
228 | 0 | pub fn encoding(&self) -> Encoding { |
229 | 0 | self.hash.map(|h| h.encoding()).unwrap_or_default() |
230 | 0 | } |
231 | | |
232 | | /// Serialize this [`PasswordHash`] as a [`PasswordHashString`]. |
233 | | #[cfg(feature = "alloc")] |
234 | | #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] |
235 | | pub fn serialize(&self) -> PasswordHashString { |
236 | | self.into() |
237 | | } |
238 | | } |
239 | | |
240 | | // Note: this uses `TryFrom` instead of `FromStr` to support a lifetime on |
241 | | // the `str` the value is being parsed from. |
242 | | impl<'a> TryFrom<&'a str> for PasswordHash<'a> { |
243 | | type Error = Error; |
244 | | |
245 | 0 | fn try_from(s: &'a str) -> Result<Self> { |
246 | 0 | Self::new(s) |
247 | 0 | } |
248 | | } |
249 | | |
250 | | impl<'a> fmt::Display for PasswordHash<'a> { |
251 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
252 | 0 | write!(f, "{}{}", PASSWORD_HASH_SEPARATOR, self.algorithm)?; |
253 | | |
254 | 0 | if let Some(version) = self.version { |
255 | 0 | write!(f, "{}v={}", PASSWORD_HASH_SEPARATOR, version)?; |
256 | 0 | } |
257 | | |
258 | 0 | if !self.params.is_empty() { |
259 | 0 | write!(f, "{}{}", PASSWORD_HASH_SEPARATOR, self.params)?; |
260 | 0 | } |
261 | | |
262 | 0 | if let Some(salt) = &self.salt { |
263 | 0 | write!(f, "{}{}", PASSWORD_HASH_SEPARATOR, salt)?; |
264 | | |
265 | 0 | if let Some(hash) = &self.hash { |
266 | 0 | write!(f, "{}{}", PASSWORD_HASH_SEPARATOR, hash)?; |
267 | 0 | } |
268 | 0 | } |
269 | | |
270 | 0 | Ok(()) |
271 | 0 | } |
272 | | } |
273 | | |
274 | | /// Serialized [`PasswordHash`]. |
275 | | /// |
276 | | /// This type contains a serialized password hash string which is ensured to |
277 | | /// parse successfully. |
278 | | // TODO(tarcieri): cached parsed representations? or at least structural data |
279 | | #[cfg(feature = "alloc")] |
280 | | #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] |
281 | | #[derive(Clone, Debug, Eq, PartialEq)] |
282 | | pub struct PasswordHashString { |
283 | | /// String value |
284 | | string: String, |
285 | | |
286 | | /// String encoding |
287 | | encoding: Encoding, |
288 | | } |
289 | | |
290 | | #[cfg(feature = "alloc")] |
291 | | #[allow(clippy::len_without_is_empty)] |
292 | | impl PasswordHashString { |
293 | | /// Parse a password hash from a string in the PHC string format. |
294 | | pub fn new(s: &str) -> Result<Self> { |
295 | | Self::parse(s, Encoding::default()) |
296 | | } |
297 | | |
298 | | /// Parse a password hash from the given [`Encoding`]. |
299 | | pub fn parse(s: &str, encoding: Encoding) -> Result<Self> { |
300 | | Ok(PasswordHash::parse(s, encoding)?.into()) |
301 | | } |
302 | | |
303 | | /// Parse this owned string as a [`PasswordHash`]. |
304 | | pub fn password_hash(&self) -> PasswordHash<'_> { |
305 | | PasswordHash::parse(&self.string, self.encoding).expect("malformed password hash") |
306 | | } |
307 | | |
308 | | /// Get the [`Encoding`] that this [`PasswordHashString`] is serialized with. |
309 | | pub fn encoding(&self) -> Encoding { |
310 | | self.encoding |
311 | | } |
312 | | |
313 | | /// Borrow this value as a `str`. |
314 | | pub fn as_str(&self) -> &str { |
315 | | self.string.as_str() |
316 | | } |
317 | | |
318 | | /// Borrow this value as bytes. |
319 | | pub fn as_bytes(&self) -> &[u8] { |
320 | | self.as_str().as_bytes() |
321 | | } |
322 | | |
323 | | /// Get the length of this value in ASCII characters. |
324 | | pub fn len(&self) -> usize { |
325 | | self.as_str().len() |
326 | | } |
327 | | |
328 | | /// Password hashing algorithm identifier. |
329 | | pub fn algorithm(&self) -> Ident<'_> { |
330 | | self.password_hash().algorithm |
331 | | } |
332 | | |
333 | | /// Optional version field. |
334 | | pub fn version(&self) -> Option<Decimal> { |
335 | | self.password_hash().version |
336 | | } |
337 | | |
338 | | /// Algorithm-specific parameters. |
339 | | pub fn params(&self) -> ParamsString { |
340 | | self.password_hash().params |
341 | | } |
342 | | |
343 | | /// [`Salt`] string for personalizing a password hash output. |
344 | | pub fn salt(&self) -> Option<Salt<'_>> { |
345 | | self.password_hash().salt |
346 | | } |
347 | | |
348 | | /// Password hashing function [`Output`], a.k.a. hash/digest. |
349 | | pub fn hash(&self) -> Option<Output> { |
350 | | self.password_hash().hash |
351 | | } |
352 | | } |
353 | | |
354 | | #[cfg(feature = "alloc")] |
355 | | impl AsRef<str> for PasswordHashString { |
356 | | fn as_ref(&self) -> &str { |
357 | | self.as_str() |
358 | | } |
359 | | } |
360 | | |
361 | | #[cfg(feature = "alloc")] |
362 | | impl From<PasswordHash<'_>> for PasswordHashString { |
363 | | fn from(hash: PasswordHash<'_>) -> PasswordHashString { |
364 | | PasswordHashString::from(&hash) |
365 | | } |
366 | | } |
367 | | |
368 | | #[cfg(feature = "alloc")] |
369 | | impl From<&PasswordHash<'_>> for PasswordHashString { |
370 | | fn from(hash: &PasswordHash<'_>) -> PasswordHashString { |
371 | | PasswordHashString { |
372 | | string: hash.to_string(), |
373 | | encoding: hash.encoding(), |
374 | | } |
375 | | } |
376 | | } |
377 | | |
378 | | #[cfg(feature = "alloc")] |
379 | | impl FromStr for PasswordHashString { |
380 | | type Err = Error; |
381 | | |
382 | | fn from_str(s: &str) -> Result<Self> { |
383 | | Self::new(s) |
384 | | } |
385 | | } |
386 | | |
387 | | #[cfg(feature = "alloc")] |
388 | | impl fmt::Display for PasswordHashString { |
389 | | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
390 | | f.write_str(self.as_str()) |
391 | | } |
392 | | } |