use crate::pbkdf2_hmac;
use core::{
fmt::{self, Display, Formatter},
str::FromStr,
};
use password_hash::{
CustomizedPasswordHasher, Error, PasswordHasher, Result,
phc::{Decimal, Ident, Output, ParamsString, PasswordHash, Salt},
};
use sha2::{Sha256, Sha512};
#[cfg(feature = "sha1")]
use sha1::Sha1;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Pbkdf2;
impl CustomizedPasswordHasher<PasswordHash> for Pbkdf2 {
type Params = Params;
fn hash_password_customized(
&self,
password: &[u8],
salt: &[u8],
alg_id: Option<&str>,
version: Option<password_hash::Version>,
params: Params,
) -> Result<PasswordHash> {
let algorithm = alg_id
.map(Algorithm::try_from)
.transpose()?
.unwrap_or_default();
if version.is_some() {
return Err(Error::Version);
}
let salt = Salt::new(salt).map_err(|_| Error::SaltInvalid)?;
let mut buffer = [0u8; Output::MAX_LENGTH];
let out = buffer
.get_mut(..params.output_length)
.ok_or(Error::OutputSize)?;
let f = match algorithm {
#[cfg(feature = "sha1")]
Algorithm::Pbkdf2Sha1 => pbkdf2_hmac::<Sha1>,
Algorithm::Pbkdf2Sha256 => pbkdf2_hmac::<Sha256>,
Algorithm::Pbkdf2Sha512 => pbkdf2_hmac::<Sha512>,
};
f(password, &salt, params.rounds, out);
let output = Output::new(out).map_err(|_| Error::OutputSize)?;
Ok(PasswordHash {
algorithm: *algorithm.ident(),
version: None,
params: params.try_into()?,
salt: Some(salt),
hash: Some(output),
})
}
}
impl PasswordHasher<PasswordHash> for Pbkdf2 {
fn hash_password_with_salt(&self, password: &[u8], salt: &[u8]) -> Result<PasswordHash> {
self.hash_password_customized(password, salt, None, None, Params::default())
}
}
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum Algorithm {
#[cfg(feature = "sha1")]
Pbkdf2Sha1,
Pbkdf2Sha256,
Pbkdf2Sha512,
}
impl Default for Algorithm {
fn default() -> Self {
Self::Pbkdf2Sha256
}
}
impl Algorithm {
#[cfg(feature = "sha1")]
pub const PBKDF2_SHA1_IDENT: Ident = Ident::new_unwrap("pbkdf2");
pub const PBKDF2_SHA256_IDENT: Ident = Ident::new_unwrap("pbkdf2-sha256");
pub const PBKDF2_SHA512_IDENT: Ident = Ident::new_unwrap("pbkdf2-sha512");
pub fn new(id: impl AsRef<str>) -> Result<Self> {
id.as_ref().parse()
}
pub fn ident(&self) -> &'static Ident {
match self {
#[cfg(feature = "sha1")]
Algorithm::Pbkdf2Sha1 => &Self::PBKDF2_SHA1_IDENT,
Algorithm::Pbkdf2Sha256 => &Self::PBKDF2_SHA256_IDENT,
Algorithm::Pbkdf2Sha512 => &Self::PBKDF2_SHA512_IDENT,
}
}
pub fn as_str(&self) -> &str {
self.ident().as_str()
}
}
impl AsRef<str> for Algorithm {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl fmt::Display for Algorithm {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for Algorithm {
type Err = Error;
fn from_str(s: &str) -> Result<Algorithm> {
s.try_into()
}
}
impl From<Algorithm> for Ident {
fn from(alg: Algorithm) -> Ident {
*alg.ident()
}
}
impl<'a> TryFrom<&'a str> for Algorithm {
type Error = Error;
fn try_from(name: &'a str) -> Result<Algorithm> {
match name.try_into() {
#[cfg(feature = "sha1")]
Ok(Self::PBKDF2_SHA1_IDENT) => Ok(Algorithm::Pbkdf2Sha1),
Ok(Self::PBKDF2_SHA256_IDENT) => Ok(Algorithm::Pbkdf2Sha256),
Ok(Self::PBKDF2_SHA512_IDENT) => Ok(Algorithm::Pbkdf2Sha512),
_ => Err(Error::Algorithm),
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Params {
pub rounds: u32,
pub output_length: usize,
}
impl Params {
pub const RECOMMENDED_ROUNDS: usize = 600_000;
pub const RECOMMENDED: Self = Params {
rounds: Self::RECOMMENDED_ROUNDS as u32,
output_length: 32,
};
}
impl Default for Params {
fn default() -> Params {
Params::RECOMMENDED
}
}
impl Display for Params {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
ParamsString::try_from(self).map_err(|_| fmt::Error)?.fmt(f)
}
}
impl FromStr for Params {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
let params_string = ParamsString::from_str(s).map_err(|_| Error::ParamsInvalid)?;
Self::try_from(¶ms_string)
}
}
impl TryFrom<&ParamsString> for Params {
type Error = Error;
fn try_from(params_string: &ParamsString) -> Result<Self> {
let mut params = Params::default();
for (ident, value) in params_string.iter() {
match ident.as_str() {
"i" => {
params.rounds = value
.decimal()
.map_err(|_| Error::ParamInvalid { name: "i" })?
}
"l" => {
params.output_length = value
.decimal()
.ok()
.and_then(|dec| dec.try_into().ok())
.ok_or(Error::ParamInvalid { name: "l" })?;
}
_ => return Err(Error::ParamsInvalid),
}
}
Ok(params)
}
}
impl TryFrom<&PasswordHash> for Params {
type Error = Error;
fn try_from(hash: &PasswordHash) -> Result<Self> {
if hash.version.is_some() {
return Err(Error::Version);
}
let params = Self::try_from(&hash.params)?;
if let Some(hash) = &hash.hash {
if hash.len() != params.output_length {
return Err(Error::OutputSize);
}
}
Ok(params)
}
}
impl TryFrom<Params> for ParamsString {
type Error = Error;
fn try_from(params: Params) -> Result<ParamsString> {
Self::try_from(¶ms)
}
}
impl TryFrom<&Params> for ParamsString {
type Error = Error;
fn try_from(input: &Params) -> Result<ParamsString> {
let mut output = ParamsString::new();
for (name, value) in [("i", input.rounds), ("l", input.output_length as Decimal)] {
output
.add_decimal(name, value)
.map_err(|_| Error::ParamInvalid { name })?;
}
Ok(output)
}
}