Skip to main content

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::fmt::Debug;
60
61#[cfg(feature = "rand_core")]
62use rand_core::TryCryptoRng;
63
64/// Numeric version identifier for password hashing algorithms.
65pub type Version = u32;
66
67/// Recommended length of a salt: 16-bytes.
68///
69/// This recommendation comes from the [PHC string format specification]:
70///
71/// > The role of salts is to achieve uniqueness. A *random* salt is fine
72/// > for that as long as its length is sufficient; a 16-byte salt would
73/// > work well (by definition, UUID are very good salts, and they encode
74/// > over exactly 16 bytes). 16 bytes encode as 22 characters in B64.
75///
76/// [PHC string format specification]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#function-duties
77#[cfg(any(feature = "getrandom", feature = "rand_core"))]
78const RECOMMENDED_SALT_LEN: usize = 16;
79
80/// High-level trait for password hashing functions.
81///
82/// Generic around a password hash to be returned (typically [`phc::PasswordHash`])
83pub trait PasswordHasher<H> {
84    /// Compute the hash `H` from the given password and salt, potentially using configuration
85    /// stored in `&self` for the parameters, or otherwise the recommended defaults.
86    ///
87    /// The salt should be unique per password. When in doubt, use [`PasswordHasher::hash_password`]
88    /// which will choose the salt for you.
89    ///
90    /// # Errors
91    /// These will vary by algorithm/implementation of this trait, but may be due to:
92    /// - length restrictions on the password and/or salt
93    /// - algorithm-specific internal error
94    fn hash_password_with_salt(&self, password: &[u8], salt: &[u8]) -> Result<H>;
95
96    /// Compute the hash `H` from the given password, potentially using configuration stored in
97    /// `&self` for the parameters, or otherwise the recommended defaults.
98    ///
99    /// A large random salt will be generated automatically.
100    ///
101    /// # Errors
102    /// These will vary by algorithm/implementation of this trait, but may be due to:
103    /// - length restrictions on the password
104    /// - algorithm-specific internal error
105    /// - RNG internal error
106    #[cfg(feature = "getrandom")]
107    fn hash_password(&self, password: &[u8]) -> Result<H> {
108        let salt = try_generate_salt()?;
109        self.hash_password_with_salt(password, &salt)
110    }
111
112    /// Compute the hash `H` from the given password, potentially using configuration stored in
113    /// `&self` for the parameters, or otherwise the recommended defaults.
114    ///
115    /// A large random salt will be generated automatically from the provided RNG.
116    ///
117    /// # Errors
118    /// These will vary by algorithm/implementation of this trait, but may be due to:
119    /// - length restrictions on the password
120    /// - algorithm-specific internal error
121    /// - RNG internal error
122    #[cfg(feature = "rand_core")]
123    fn hash_password_with_rng<R: TryCryptoRng + ?Sized>(
124        &self,
125        rng: &mut R,
126        password: &[u8],
127    ) -> Result<H> {
128        let mut salt = [0u8; RECOMMENDED_SALT_LEN];
129        rng.try_fill_bytes(&mut salt).map_err(|_| Error::Crypto)?;
130        self.hash_password_with_salt(password, &salt)
131    }
132}
133
134/// Trait for password hashing functions which support customization.
135///
136/// Generic around a password hash to be returned (typically [`phc::PasswordHash`]).
137pub trait CustomizedPasswordHasher<H> {
138    /// Algorithm-specific parameters.
139    type Params: Clone + Debug + Default;
140
141    /// Compute a password hash from the provided password using an explicit set of customized
142    /// algorithm parameters as opposed to the defaults.
143    ///
144    /// When in doubt, use [`PasswordHasher::hash_password`] instead.
145    ///
146    /// # Errors
147    /// These will vary by algorithm/implementation of this trait, but may be due to:
148    /// - length restrictions on the password and/or salt
149    /// - algorithm-specific params or internal error
150    fn hash_password_customized(
151        &self,
152        password: &[u8],
153        salt: &[u8],
154        algorithm: Option<&str>,
155        version: Option<Version>,
156        params: Self::Params,
157    ) -> Result<H>;
158
159    /// Compute a password hash using customized parameters only, using the default algorithm and
160    /// version.
161    ///
162    /// # Errors
163    /// These will vary by algorithm/implementation of this trait, but may be due to:
164    /// - length restrictions on the password and/or salt
165    /// - algorithm-specific params or internal error
166    fn hash_password_with_params(
167        &self,
168        password: &[u8],
169        salt: &[u8],
170        params: Self::Params,
171    ) -> Result<H> {
172        self.hash_password_customized(password, salt, None, None, params)
173    }
174}
175
176/// Trait for password verification.
177///
178/// Generic around a password hash to be returned (typically [`phc::PasswordHash`]).
179///
180/// Automatically impl'd for type that impl [`PasswordHasher`] with [`phc::PasswordHash`] as `H`.
181///
182/// This trait is object safe and can be used to implement abstractions over multiple password
183/// hashing algorithms.
184pub trait PasswordVerifier<H: ?Sized> {
185    /// Compute this password hashing function against the provided password using the parameters
186    /// from the provided password hash and see if the computed output matches.
187    ///
188    /// # Errors
189    /// - Returns [`Error::Algorithm`] if the algorithm requested by the hash `H` is unsupported
190    /// - Returns [`Error::PasswordInvalid`] if the computed hash for the supplied password does not
191    ///   match the expected hash
192    /// - May return other algorithm-specific errors
193    fn verify_password(&self, password: &[u8], hash: &H) -> Result<()>;
194}
195
196#[cfg(feature = "phc")]
197impl<T: CustomizedPasswordHasher<phc::PasswordHash>> PasswordVerifier<phc::PasswordHash> for T
198where
199    T::Params: for<'a> TryFrom<&'a phc::ParamsString, Error = Error>,
200{
201    fn verify_password(&self, password: &[u8], hash: &phc::PasswordHash) -> Result<()> {
202        #[allow(clippy::single_match)]
203        match (&hash.salt, &hash.hash) {
204            (Some(salt), Some(expected_output)) => {
205                let computed_hash = self.hash_password_customized(
206                    password,
207                    salt,
208                    Some(hash.algorithm.as_str()),
209                    hash.version,
210                    T::Params::try_from(&hash.params)?,
211                )?;
212
213                if let Some(computed_output) = &computed_hash.hash {
214                    // See notes on `Output` about the use of a constant-time comparison
215                    if expected_output == computed_output {
216                        return Ok(());
217                    }
218                }
219            }
220            _ => (),
221        }
222
223        Err(Error::PasswordInvalid)
224    }
225}
226
227/// Generate a random salt value of the recommended length using the system's secure RNG.
228///
229/// # Panics
230/// If the system's secure RNG experiences an internal failure.
231#[cfg(feature = "getrandom")]
232#[must_use]
233pub fn generate_salt() -> [u8; RECOMMENDED_SALT_LEN] {
234    try_generate_salt().expect("RNG failure")
235}
236
237/// Try generating a random salt value of the recommended length using the system's secure RNG,
238/// returning errors if they occur.
239///
240/// # Errors
241/// If the system's secure RNG experiences an internal failure.
242#[cfg(feature = "getrandom")]
243pub fn try_generate_salt() -> core::result::Result<[u8; RECOMMENDED_SALT_LEN], getrandom::Error> {
244    let mut salt = [0u8; RECOMMENDED_SALT_LEN];
245    getrandom::fill(&mut salt)?;
246    Ok(salt)
247}