#![no_std]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![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",
html_root_url = "https://docs.rs/password-hash/0.2.1"
)]
#![forbid(unsafe_code)]
#![warn(missing_docs, rust_2018_idioms)]
#[cfg(all(feature = "alloc", test))]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
mod encoding;
mod errors;
mod ident;
mod output;
mod params;
mod salt;
mod value;
pub use crate::{
encoding::Encoding,
errors::{B64Error, Error, Result},
ident::Ident,
output::Output,
params::ParamsString,
salt::{Salt, SaltString},
value::{Decimal, Value},
};
use core::{
convert::{TryFrom, TryInto},
fmt::{self, Debug},
};
const PASSWORD_HASH_SEPARATOR: char = '$';
pub trait PasswordHasher {
type Params: Clone
+ Debug
+ Default
+ for<'a> TryFrom<&'a PasswordHash<'a>, Error = Error>
+ for<'a> TryInto<ParamsString, Error = Error>;
fn hash_password_simple<'a, S>(&self, password: &[u8], salt: &'a S) -> Result<PasswordHash<'a>>
where
S: AsRef<str> + ?Sized,
{
self.hash_password(
password,
None,
Self::Params::default(),
Salt::try_from(salt.as_ref())?,
)
}
fn hash_password<'a>(
&self,
password: &[u8],
algorithm: Option<Ident<'a>>,
params: Self::Params,
salt: impl Into<Salt<'a>>,
) -> Result<PasswordHash<'a>>;
}
pub trait PasswordVerifier {
fn verify_password(&self, password: &[u8], hash: &PasswordHash<'_>) -> Result<()>;
}
impl<T: PasswordHasher> PasswordVerifier for T {
fn verify_password(&self, password: &[u8], hash: &PasswordHash<'_>) -> Result<()> {
if let (Some(salt), Some(expected_output)) = (&hash.salt, &hash.hash) {
let computed_hash = self.hash_password(
password,
Some(hash.algorithm),
T::Params::try_from(&hash)?,
*salt,
)?;
if let Some(computed_output) = &computed_hash.hash {
if expected_output == computed_output {
return Ok(());
}
}
}
Err(Error::Password)
}
}
pub trait McfHasher {
fn upgrade_mcf_hash<'a>(&self, hash: &'a str) -> Result<PasswordHash<'a>>;
fn verify_mcf_hash(&self, password: &[u8], mcf_hash: &str) -> Result<()>
where
Self: PasswordVerifier,
{
self.verify_password(password, &self.upgrade_mcf_hash(mcf_hash)?)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PasswordHash<'a> {
pub algorithm: Ident<'a>,
pub version: Option<Decimal>,
pub params: ParamsString,
pub salt: Option<Salt<'a>>,
pub hash: Option<Output>,
}
impl<'a> PasswordHash<'a> {
pub fn new(s: &'a str) -> Result<Self> {
Self::parse(s, Encoding::B64)
}
pub fn parse(s: &'a str, encoding: Encoding) -> Result<Self> {
if s.is_empty() {
return Err(Error::PhcStringTooShort);
}
let mut fields = s.split(PASSWORD_HASH_SEPARATOR);
let beginning = fields.next().expect("no first field");
if beginning.chars().next().is_some() {
return Err(Error::PhcStringInvalid);
}
let algorithm = fields
.next()
.ok_or(Error::PhcStringTooShort)
.and_then(Ident::try_from)?;
let mut version = None;
let mut params = ParamsString::new();
let mut salt = None;
let mut hash = None;
let mut next_field = fields.next();
if let Some(field) = next_field {
if field.starts_with("v=") && !field.contains(params::PARAMS_DELIMITER) {
version = Some(Value::new(&field[2..]).and_then(|value| value.decimal())?);
next_field = None;
}
}
if next_field.is_none() {
next_field = fields.next();
}
if let Some(field) = next_field {
if field.contains(params::PAIR_DELIMITER) {
params = field.parse()?;
next_field = None;
}
}
if next_field.is_none() {
next_field = fields.next();
}
if let Some(s) = next_field {
salt = Some(s.try_into()?);
}
if let Some(field) = fields.next() {
hash = Some(Output::decode(field, encoding)?);
}
if fields.next().is_some() {
return Err(Error::PhcStringTooLong);
}
Ok(Self {
algorithm,
version,
params,
salt,
hash,
})
}
pub fn generate(
phf: impl PasswordHasher,
password: impl AsRef<[u8]>,
salt: &'a str,
) -> Result<Self> {
phf.hash_password_simple(password.as_ref(), salt)
}
pub fn verify_password(
&self,
phfs: &[&dyn PasswordVerifier],
password: impl AsRef<[u8]>,
) -> Result<()> {
for &phf in phfs {
if phf.verify_password(password.as_ref(), self).is_ok() {
return Ok(());
}
}
Err(Error::Password)
}
pub fn encoding(&self) -> Encoding {
self.hash.map(|h| h.encoding()).unwrap_or_default()
}
}
impl<'a> TryFrom<&'a str> for PasswordHash<'a> {
type Error = Error;
fn try_from(s: &'a str) -> Result<Self> {
Self::new(s)
}
}
impl<'a> fmt::Display for PasswordHash<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}{}", PASSWORD_HASH_SEPARATOR, self.algorithm)?;
if let Some(version) = self.version {
write!(f, "{}v={}", PASSWORD_HASH_SEPARATOR, version)?;
}
if !self.params.is_empty() {
write!(f, "{}{}", PASSWORD_HASH_SEPARATOR, self.params)?;
}
if let Some(salt) = &self.salt {
write!(f, "{}{}", PASSWORD_HASH_SEPARATOR, salt)?;
}
if let Some(hash) = &self.hash {
write!(f, "{}{}", PASSWORD_HASH_SEPARATOR, hash)?;
}
Ok(())
}
}