1 //! This crate defines a set of traits which describe the functionality of
2 //! [password hashing algorithms].
3 //!
4 //! Provides a `no_std`-friendly implementation of the
5 //! [Password Hashing Competition (PHC) string format specification][PHC]
6 //! (a well-defined subset of the [Modular Crypt Format a.k.a. MCF][MCF]) which
7 //! works in conjunction with the traits this crate defines.
8 //!
9 //! # Supported Crates
10 //!
11 //! See [RustCrypto/password-hashes] for algorithm implementations which use
12 //! this crate for interoperability:
13 //!
14 //! - [`argon2`] - Argon2 memory hard key derivation function
15 //! - [`pbkdf2`] - Password-Based Key Derivation Function v2
16 //! - [`scrypt`] - scrypt key derivation function
17 //!
18 //! # Usage
19 //!
20 //! This crate represents password hashes using the [`PasswordHash`] type, which
21 //! represents a parsed "PHC string" with the following format:
22 //!
23 //! ```text
24 //! $<id>[$v=<version>][$<param>=<value>(,<param>=<value>)*][$<salt>[$<hash>]]
25 //! ```
26 //!
27 //! For more information, please see the documentation for [`PasswordHash`].
28 //!
29 //! [password hashing algorithms]: https://en.wikipedia.org/wiki/Cryptographic_hash_function#Password_verification
30 //! [PHC]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md
31 //! [MCF]: https://passlib.readthedocs.io/en/stable/modular_crypt_format.html
32 //! [RustCrypto/password-hashes]: https://github.com/RustCrypto/password-hashes
33 //! [`argon2`]: https://docs.rs/argon2
34 //! [`pbkdf2`]: https://docs.rs/pbkdf2
35 //! [`scrypt`]: https://docs.rs/scrypt
36 
37 #![no_std]
38 #![cfg_attr(docsrs, feature(doc_cfg))]
39 #![doc(
40     html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg",
41     html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg",
42     html_root_url = "https://docs.rs/password-hash/0.2.3"
43 )]
44 #![forbid(unsafe_code)]
45 #![warn(missing_docs, rust_2018_idioms)]
46 
47 #[cfg(all(feature = "alloc", test))]
48 extern crate alloc;
49 
50 #[cfg(feature = "std")]
51 extern crate std;
52 
53 #[cfg(feature = "rand_core")]
54 #[cfg_attr(docsrs, doc(cfg(feature = "rand_core")))]
55 pub use rand_core;
56 
57 mod encoding;
58 mod errors;
59 mod ident;
60 mod output;
61 mod params;
62 mod salt;
63 mod value;
64 
65 pub use crate::{
66     encoding::Encoding,
67     errors::{B64Error, Error, Result},
68     ident::Ident,
69     output::Output,
70     params::ParamsString,
71     salt::{Salt, SaltString},
72     value::{Decimal, Value},
73 };
74 
75 use core::{
76     convert::{TryFrom, TryInto},
77     fmt::{self, Debug},
78 };
79 
80 /// Separator character used in password hashes (e.g. `$6$...`).
81 const PASSWORD_HASH_SEPARATOR: char = '$';
82 
83 /// Trait for password hashing functions.
84 pub trait PasswordHasher {
85     /// Algorithm-specific parameters.
86     type Params: Clone
87         + Debug
88         + Default
89         + for<'a> TryFrom<&'a PasswordHash<'a>, Error = Error>
90         + for<'a> TryInto<ParamsString, Error = Error>;
91 
92     /// Simple API for computing a [`PasswordHash`] from a password and
93     /// [`Salt`] value.
94     ///
95     /// Uses the default recommended parameters for a given algorithm.
hash_password_simple<'a, S>(&self, password: &[u8], salt: &'a S) -> Result<PasswordHash<'a>> where S: AsRef<str> + ?Sized,96     fn hash_password_simple<'a, S>(&self, password: &[u8], salt: &'a S) -> Result<PasswordHash<'a>>
97     where
98         S: AsRef<str> + ?Sized,
99     {
100         self.hash_password(
101             password,
102             None,
103             Self::Params::default(),
104             Salt::try_from(salt.as_ref())?,
105         )
106     }
107 
108     /// Compute a [`PasswordHash`] with the given algorithm [`Ident`]
109     /// (or `None` for the recommended default), password, salt, and
110     /// parameters.
hash_password<'a>( &self, password: &[u8], algorithm: Option<Ident<'a>>, params: Self::Params, salt: impl Into<Salt<'a>>, ) -> Result<PasswordHash<'a>>111     fn hash_password<'a>(
112         &self,
113         password: &[u8],
114         algorithm: Option<Ident<'a>>,
115         params: Self::Params,
116         salt: impl Into<Salt<'a>>,
117     ) -> Result<PasswordHash<'a>>;
118 }
119 
120 /// Trait for password verification.
121 ///
122 /// Automatically impl'd for any type that impls [`PasswordHasher`].
123 ///
124 /// This trait is object safe and can be used to implement abstractions over
125 /// multiple password hashing algorithms. One such abstraction is provided by
126 /// the [`PasswordHash::verify_password`] method.
127 pub trait PasswordVerifier {
128     /// Compute this password hashing function against the provided password
129     /// using the parameters from the provided password hash and see if the
130     /// computed output matches.
verify_password(&self, password: &[u8], hash: &PasswordHash<'_>) -> Result<()>131     fn verify_password(&self, password: &[u8], hash: &PasswordHash<'_>) -> Result<()>;
132 }
133 
134 impl<T: PasswordHasher> PasswordVerifier for T {
verify_password(&self, password: &[u8], hash: &PasswordHash<'_>) -> Result<()>135     fn verify_password(&self, password: &[u8], hash: &PasswordHash<'_>) -> Result<()> {
136         if let (Some(salt), Some(expected_output)) = (&hash.salt, &hash.hash) {
137             let computed_hash = self.hash_password(
138                 password,
139                 Some(hash.algorithm),
140                 T::Params::try_from(&hash)?,
141                 *salt,
142             )?;
143 
144             if let Some(computed_output) = &computed_hash.hash {
145                 // See notes on `Output` about the use of a constant-time comparison
146                 if expected_output == computed_output {
147                     return Ok(());
148                 }
149             }
150         }
151 
152         Err(Error::Password)
153     }
154 }
155 
156 /// Trait for password hashing algorithms which support the legacy
157 /// [Modular Crypt Format (MCF)][MCF].
158 ///
159 /// [MCF]: https://passlib.readthedocs.io/en/stable/modular_crypt_format.html
160 pub trait McfHasher {
161     /// Upgrade an MCF hash to a PHC hash. MCF follow this rough format:
162     ///
163     /// ```text
164     /// $<id>$<content>
165     /// ```
166     ///
167     /// MCF hashes are otherwise largely unstructured and parsed according to
168     /// algorithm-specific rules so hashers must parse a raw string themselves.
upgrade_mcf_hash<'a>(&self, hash: &'a str) -> Result<PasswordHash<'a>>169     fn upgrade_mcf_hash<'a>(&self, hash: &'a str) -> Result<PasswordHash<'a>>;
170 
171     /// Verify a password hash in MCF format against the provided password.
verify_mcf_hash(&self, password: &[u8], mcf_hash: &str) -> Result<()> where Self: PasswordVerifier,172     fn verify_mcf_hash(&self, password: &[u8], mcf_hash: &str) -> Result<()>
173     where
174         Self: PasswordVerifier,
175     {
176         self.verify_password(password, &self.upgrade_mcf_hash(mcf_hash)?)
177     }
178 }
179 
180 /// Password hash.
181 ///
182 /// This type corresponds to the parsed representation of a PHC string as
183 /// described in the [PHC string format specification][1].
184 ///
185 /// PHC strings have the following format:
186 ///
187 /// ```text
188 /// $<id>[$v=<version>][$<param>=<value>(,<param>=<value>)*][$<salt>[$<hash>]]
189 /// ```
190 ///
191 /// where:
192 ///
193 /// - `<id>` is the symbolic name for the function
194 /// - `<version>` is the algorithm version
195 /// - `<param>` is a parameter name
196 /// - `<value>` is a parameter value
197 /// - `<salt>` is an encoding of the salt
198 /// - `<hash>` is an encoding of the hash output
199 ///
200 /// The string is then the concatenation, in that order, of:
201 ///
202 /// - a `$` sign;
203 /// - the function symbolic name;
204 /// - optionally, a `$` sign followed by the algorithm version with a `v=version` format;
205 /// - optionally, a `$` sign followed by one or several parameters, each with a `name=value` format;
206 ///   the parameters are separated by commas;
207 /// - optionally, a `$` sign followed by the (encoded) salt value;
208 /// - optionally, a `$` sign followed by the (encoded) hash output (the hash output may be present
209 ///   only if the salt is present).
210 ///
211 /// [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#specification
212 #[derive(Clone, Debug, Eq, PartialEq)]
213 pub struct PasswordHash<'a> {
214     /// Password hashing algorithm identifier.
215     ///
216     /// This corresponds to the `<id>` field in a PHC string, a.k.a. the
217     /// symbolic name for the function.
218     pub algorithm: Ident<'a>,
219 
220     /// Optional version field.
221     ///
222     /// This corresponds to the `<version>` field in a PHC string.
223     pub version: Option<Decimal>,
224 
225     /// Algorithm-specific parameters.
226     ///
227     /// This corresponds to the set of `$<param>=<value>(,<param>=<value>)*`
228     /// name/value pairs in a PHC string.
229     pub params: ParamsString,
230 
231     /// [`Salt`] string for personalizing a password hash output.
232     ///
233     /// This corresponds to the `<salt>` value in a PHC string.
234     pub salt: Option<Salt<'a>>,
235 
236     /// Password hashing function [`Output`], a.k.a. hash/digest.
237     ///
238     /// This corresponds to the `<hash>` output in a PHC string.
239     pub hash: Option<Output>,
240 }
241 
242 impl<'a> PasswordHash<'a> {
243     /// Parse a password hash from a string in the PHC string format.
new(s: &'a str) -> Result<Self>244     pub fn new(s: &'a str) -> Result<Self> {
245         Self::parse(s, Encoding::B64)
246     }
247 
248     /// Parse a password hash from the given [`Encoding`].
parse(s: &'a str, encoding: Encoding) -> Result<Self>249     pub fn parse(s: &'a str, encoding: Encoding) -> Result<Self> {
250         if s.is_empty() {
251             return Err(Error::PhcStringTooShort);
252         }
253 
254         let mut fields = s.split(PASSWORD_HASH_SEPARATOR);
255         let beginning = fields.next().expect("no first field");
256 
257         if beginning.chars().next().is_some() {
258             return Err(Error::PhcStringInvalid);
259         }
260 
261         let algorithm = fields
262             .next()
263             .ok_or(Error::PhcStringTooShort)
264             .and_then(Ident::try_from)?;
265 
266         let mut version = None;
267         let mut params = ParamsString::new();
268         let mut salt = None;
269         let mut hash = None;
270 
271         let mut next_field = fields.next();
272 
273         if let Some(field) = next_field {
274             // v=<version>
275             if field.starts_with("v=") && !field.contains(params::PARAMS_DELIMITER) {
276                 version = Some(Value::new(&field[2..]).and_then(|value| value.decimal())?);
277                 next_field = None;
278             }
279         }
280 
281         if next_field.is_none() {
282             next_field = fields.next();
283         }
284 
285         if let Some(field) = next_field {
286             // <param>=<value>
287             if field.contains(params::PAIR_DELIMITER) {
288                 params = field.parse()?;
289                 next_field = None;
290             }
291         }
292 
293         if next_field.is_none() {
294             next_field = fields.next();
295         }
296 
297         if let Some(s) = next_field {
298             salt = Some(s.try_into()?);
299         }
300 
301         if let Some(field) = fields.next() {
302             hash = Some(Output::decode(field, encoding)?);
303         }
304 
305         if fields.next().is_some() {
306             return Err(Error::PhcStringTooLong);
307         }
308 
309         Ok(Self {
310             algorithm,
311             version,
312             params,
313             salt,
314             hash,
315         })
316     }
317 
318     /// Generate a password hash using the supplied algorithm.
generate( phf: impl PasswordHasher, password: impl AsRef<[u8]>, salt: &'a str, ) -> Result<Self>319     pub fn generate(
320         phf: impl PasswordHasher,
321         password: impl AsRef<[u8]>,
322         salt: &'a str,
323     ) -> Result<Self> {
324         phf.hash_password_simple(password.as_ref(), salt)
325     }
326 
327     /// Verify this password hash using the specified set of supported
328     /// [`PasswordHasher`] trait objects.
verify_password( &self, phfs: &[&dyn PasswordVerifier], password: impl AsRef<[u8]>, ) -> Result<()>329     pub fn verify_password(
330         &self,
331         phfs: &[&dyn PasswordVerifier],
332         password: impl AsRef<[u8]>,
333     ) -> Result<()> {
334         for &phf in phfs {
335             if phf.verify_password(password.as_ref(), self).is_ok() {
336                 return Ok(());
337             }
338         }
339 
340         Err(Error::Password)
341     }
342 
343     /// Get the [`Encoding`] that this [`PasswordHash`] is serialized with.
encoding(&self) -> Encoding344     pub fn encoding(&self) -> Encoding {
345         self.hash.map(|h| h.encoding()).unwrap_or_default()
346     }
347 }
348 
349 // Note: this uses `TryFrom` instead of `FromStr` to support a lifetime on
350 // the `str` the value is being parsed from.
351 impl<'a> TryFrom<&'a str> for PasswordHash<'a> {
352     type Error = Error;
353 
try_from(s: &'a str) -> Result<Self>354     fn try_from(s: &'a str) -> Result<Self> {
355         Self::new(s)
356     }
357 }
358 
359 impl<'a> fmt::Display for PasswordHash<'a> {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result360     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
361         write!(f, "{}{}", PASSWORD_HASH_SEPARATOR, self.algorithm)?;
362 
363         if let Some(version) = self.version {
364             write!(f, "{}v={}", PASSWORD_HASH_SEPARATOR, version)?;
365         }
366 
367         if !self.params.is_empty() {
368             write!(f, "{}{}", PASSWORD_HASH_SEPARATOR, self.params)?;
369         }
370 
371         if let Some(salt) = &self.salt {
372             write!(f, "{}{}", PASSWORD_HASH_SEPARATOR, salt)?;
373         }
374 
375         if let Some(hash) = &self.hash {
376             write!(f, "{}{}", PASSWORD_HASH_SEPARATOR, hash)?;
377         }
378 
379         Ok(())
380     }
381 }
382