password_hash/
lib.rs

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(
10    missing_docs,
11    rust_2018_idioms,
12    unused_lifetimes,
13    missing_debug_implementations
14)]
15
16//!
17//! # Usage
18//!
19//! This crate represents password hashes using the [`PasswordHash`] type, which
20//! represents a parsed "PHC string" with the following format:
21//!
22//! ```text
23//! $<id>[$v=<version>][$<param>=<value>(,<param>=<value>)*][$<salt>[$<hash>]]
24//! ```
25//!
26//! For more information, please see the documentation for [`PasswordHash`].
27
28#[cfg(feature = "alloc")]
29#[allow(unused_extern_crates)]
30extern crate alloc;
31
32mod error;
33
34pub use crate::error::{Error, Result};
35
36#[cfg(feature = "phc")]
37pub use phc;
38
39#[cfg(feature = "rand_core")]
40pub use rand_core;
41
42/// DEPRECATED: import this as `password_hash::phc::PasswordHash`.
43#[cfg(feature = "phc")]
44#[deprecated(
45    since = "0.6.0",
46    note = "import as `password_hash::phc::PasswordHash` instead"
47)]
48pub type PasswordHash = phc::PasswordHash;
49
50/// DEPRECATED: use `password_hash::phc::PasswordHash` or `String`
51#[cfg(all(feature = "alloc", feature = "phc"))]
52#[deprecated(
53    since = "0.6.0",
54    note = "use `password_hash::phc::PasswordHash` or `String`"
55)]
56#[allow(deprecated)]
57pub type PasswordHashString = phc::PasswordHashString;
58
59use core::{
60    fmt::{Debug, Display},
61    str::FromStr,
62};
63
64#[cfg(feature = "rand_core")]
65use rand_core::TryCryptoRng;
66
67/// Numeric version identifier for password hashing algorithms.
68pub type Version = u32;
69
70/// Recommended length of a salt: 16-bytes.
71///
72/// This recommendation comes from the [PHC string format specification]:
73///
74/// > The role of salts is to achieve uniqueness. A *random* salt is fine
75/// > for that as long as its length is sufficient; a 16-byte salt would
76/// > work well (by definition, UUID are very good salts, and they encode
77/// > over exactly 16 bytes). 16 bytes encode as 22 characters in B64.
78///
79/// [PHC string format specification]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#function-duties
80#[cfg(any(feature = "getrandom", feature = "rand_core"))]
81const RECOMMENDED_SALT_LEN: usize = 16;
82
83/// High-level trait for password hashing functions.
84///
85/// Generic around a password hash to be returned (typically [`phc::PasswordHash`])
86pub trait PasswordHasher<H> {
87    /// Compute the hash `H` from the given password and salt, potentially using configuration
88    /// stored in `&self` for the parameters, or otherwise the recommended defaults.
89    ///
90    /// The salt should be unique per password. When in doubt, use [`PasswordHasher::hash_password`]
91    /// which will choose the salt for you.
92    fn hash_password_with_salt(&self, password: &[u8], salt: &[u8]) -> Result<H>;
93
94    /// Compute the hash `H` from the given password, potentially using configuration stored in
95    /// `&self` for the parameters, or otherwise the recommended defaults.
96    ///
97    /// A large random salt will be generated automatically.
98    #[cfg(feature = "getrandom")]
99    fn hash_password(&self, password: &[u8]) -> Result<H> {
100        let salt = try_generate_salt()?;
101        self.hash_password_with_salt(password, &salt)
102    }
103
104    /// Compute the hash `H` from the given password, potentially using configuration stored in
105    /// `&self` for the parameters, or otherwise the recommended defaults.
106    ///
107    /// A large random salt will be generated automatically from the provided RNG.
108    #[cfg(feature = "rand_core")]
109    fn hash_password_with_rng<R: TryCryptoRng + ?Sized>(
110        &self,
111        rng: &mut R,
112        password: &[u8],
113    ) -> Result<H> {
114        let mut salt = [0u8; RECOMMENDED_SALT_LEN];
115        rng.try_fill_bytes(&mut salt).map_err(|_| Error::Crypto)?;
116        self.hash_password_with_salt(password, &salt)
117    }
118}
119
120/// Trait for password hashing functions which support customization.
121///
122/// Generic around a password hash to be returned (typically [`PasswordHash`])
123pub trait CustomizedPasswordHasher<H> {
124    /// Algorithm-specific parameters.
125    type Params: Clone + Debug + Default + Display + FromStr;
126
127    /// Compute a [`PasswordHash`] from the provided password using an
128    /// explicit set of customized algorithm parameters as opposed to the
129    /// defaults.
130    ///
131    /// When in doubt, use [`PasswordHasher::hash_password`] instead.
132    fn hash_password_customized(
133        &self,
134        password: &[u8],
135        salt: &[u8],
136        algorithm: Option<&str>,
137        version: Option<Version>,
138        params: Self::Params,
139    ) -> Result<H>;
140
141    /// Compute a [`PasswordHash`] using customized parameters only, using the default
142    /// algorithm and version.
143    fn hash_password_with_params(
144        &self,
145        password: &[u8],
146        salt: &[u8],
147        params: Self::Params,
148    ) -> Result<H> {
149        self.hash_password_customized(password, salt, None, None, params)
150    }
151}
152
153/// Trait for password verification.
154///
155/// Generic around a password hash to be returned (typically [`phc::PasswordHash`])
156///
157/// Automatically impl'd for type that impl [`PasswordHasher`] with [`phc::PasswordHash`] as `H`.
158///
159/// This trait is object safe and can be used to implement abstractions over
160/// multiple password hashing algorithms.
161pub trait PasswordVerifier<H: ?Sized> {
162    /// Compute this password hashing function against the provided password
163    /// using the parameters from the provided password hash and see if the
164    /// computed output matches.
165    fn verify_password(&self, password: &[u8], hash: &H) -> Result<()>;
166}
167
168#[cfg(feature = "phc")]
169impl<T: CustomizedPasswordHasher<phc::PasswordHash>, E> PasswordVerifier<phc::PasswordHash> for T
170where
171    T::Params: FromStr<Err = E>,
172    Error: From<E>,
173{
174    fn verify_password(&self, password: &[u8], hash: &phc::PasswordHash) -> Result<()> {
175        #[allow(clippy::single_match)]
176        match (&hash.salt, &hash.hash) {
177            (Some(salt), Some(expected_output)) => {
178                let computed_hash = self.hash_password_customized(
179                    password,
180                    salt,
181                    Some(hash.algorithm.as_str()),
182                    hash.version,
183                    T::Params::from_str(hash.params.as_str())?,
184                )?;
185
186                if let Some(computed_output) = &computed_hash.hash {
187                    // See notes on `Output` about the use of a constant-time comparison
188                    if expected_output == computed_output {
189                        return Ok(());
190                    }
191                }
192            }
193            _ => (),
194        }
195
196        Err(Error::PasswordInvalid)
197    }
198}
199
200/// Trait for password hashing algorithms which support the legacy
201/// [Modular Crypt Format (MCF)][MCF].
202///
203/// [MCF]: https://passlib.readthedocs.io/en/stable/modular_crypt_format.html
204#[cfg(feature = "phc")]
205pub trait McfHasher {
206    /// Upgrade an MCF hash to a PHC hash. MCF follow this rough format:
207    ///
208    /// ```text
209    /// $<id>$<content>
210    /// ```
211    ///
212    /// MCF hashes are otherwise largely unstructured and parsed according to
213    /// algorithm-specific rules so hashers must parse a raw string themselves.
214    fn upgrade_mcf_hash(&self, hash: &str) -> Result<phc::PasswordHash>;
215}
216
217/// Generate a random salt value of the recommended length using the system's secure RNG.
218#[cfg(feature = "getrandom")]
219pub fn generate_salt() -> [u8; RECOMMENDED_SALT_LEN] {
220    try_generate_salt().expect("RNG failure")
221}
222
223/// Try generating a random salt value of the recommended length using the system's secure RNG,
224/// returning errors if they occur.
225#[cfg(feature = "getrandom")]
226pub fn try_generate_salt() -> core::result::Result<[u8; RECOMMENDED_SALT_LEN], getrandom::Error> {
227    let mut salt = [0u8; RECOMMENDED_SALT_LEN];
228    getrandom::fill(&mut salt)?;
229    Ok(salt)
230}