#![no_std]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc = include_str!("../README.md")]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg",
html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg"
)]
#![forbid(unsafe_code)]
#![warn(
missing_docs,
rust_2018_idioms,
unused_lifetimes,
missing_debug_implementations
)]
#[cfg(feature = "alloc")]
#[allow(unused_extern_crates)]
extern crate alloc;
mod error;
pub use crate::error::{Error, Result};
#[cfg(feature = "phc")]
pub use phc;
#[cfg(feature = "rand_core")]
pub use rand_core;
#[cfg(feature = "phc")]
#[deprecated(
since = "0.6.0",
note = "import as `password_hash::phc::PasswordHash` instead"
)]
pub type PasswordHash = phc::PasswordHash;
#[cfg(all(feature = "alloc", feature = "phc"))]
#[deprecated(
since = "0.6.0",
note = "use `password_hash::phc::PasswordHash` or `String`"
)]
#[allow(deprecated)]
pub type PasswordHashString = phc::PasswordHashString;
use core::fmt::Debug;
#[cfg(feature = "rand_core")]
use rand_core::TryCryptoRng;
pub type Version = u32;
#[cfg(any(feature = "getrandom", feature = "rand_core"))]
const RECOMMENDED_SALT_LEN: usize = 16;
pub trait PasswordHasher<H> {
fn hash_password_with_salt(&self, password: &[u8], salt: &[u8]) -> Result<H>;
#[cfg(feature = "getrandom")]
fn hash_password(&self, password: &[u8]) -> Result<H> {
let salt = try_generate_salt()?;
self.hash_password_with_salt(password, &salt)
}
#[cfg(feature = "rand_core")]
fn hash_password_with_rng<R: TryCryptoRng + ?Sized>(
&self,
rng: &mut R,
password: &[u8],
) -> Result<H> {
let mut salt = [0u8; RECOMMENDED_SALT_LEN];
rng.try_fill_bytes(&mut salt).map_err(|_| Error::Crypto)?;
self.hash_password_with_salt(password, &salt)
}
}
pub trait CustomizedPasswordHasher<H> {
type Params: Clone + Debug + Default;
fn hash_password_customized(
&self,
password: &[u8],
salt: &[u8],
algorithm: Option<&str>,
version: Option<Version>,
params: Self::Params,
) -> Result<H>;
fn hash_password_with_params(
&self,
password: &[u8],
salt: &[u8],
params: Self::Params,
) -> Result<H> {
self.hash_password_customized(password, salt, None, None, params)
}
}
pub trait PasswordVerifier<H: ?Sized> {
fn verify_password(&self, password: &[u8], hash: &H) -> Result<()>;
}
#[cfg(feature = "phc")]
impl<T: CustomizedPasswordHasher<phc::PasswordHash>> PasswordVerifier<phc::PasswordHash> for T
where
T::Params: for<'a> TryFrom<&'a phc::PasswordHash, Error = Error>,
{
fn verify_password(&self, password: &[u8], hash: &phc::PasswordHash) -> Result<()> {
#[allow(clippy::single_match)]
match (&hash.salt, &hash.hash) {
(Some(salt), Some(expected_output)) => {
let computed_hash = self.hash_password_customized(
password,
salt,
Some(hash.algorithm.as_str()),
hash.version,
T::Params::try_from(hash)?,
)?;
if let Some(computed_output) = &computed_hash.hash {
if expected_output == computed_output {
return Ok(());
}
}
}
_ => (),
}
Err(Error::PasswordInvalid)
}
}
#[cfg(feature = "getrandom")]
#[must_use]
pub fn generate_salt() -> [u8; RECOMMENDED_SALT_LEN] {
try_generate_salt().expect("RNG failure")
}
#[cfg(feature = "getrandom")]
pub fn try_generate_salt() -> core::result::Result<[u8; RECOMMENDED_SALT_LEN], getrandom::Error> {
let mut salt = [0u8; RECOMMENDED_SALT_LEN];
getrandom::fill(&mut salt)?;
Ok(salt)
}