password_hash/
lib.rs

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