use core::fmt::{self, Display};
use crate::Algorithm;
#[cfg(feature = "phc")]
use password_hash::phc::{self, Decimal, ParamsString};
#[cfg(feature = "password-hash")]
use {
core::str::FromStr,
password_hash::{Error, Result},
};
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Params {
rounds: u32,
output_len: usize,
}
impl Params {
pub const MIN_OUTPUT_LENGTH: usize = 10;
pub const MAX_OUTPUT_LENGTH: usize = 64;
pub const MIN_ROUNDS: u32 = 1000;
pub const RECOMMENDED_OUTPUT_LENGTH: usize = 32;
pub const RECOMMENDED_ROUNDS: u32 = 600_000;
pub const RECOMMENDED_SHA512_ROUNDS: u32 = 210_000;
pub const RECOMMENDED: Self = Params {
rounds: Self::RECOMMENDED_ROUNDS,
output_len: Self::RECOMMENDED_OUTPUT_LENGTH,
};
#[must_use]
pub const fn recommended_for(algorithm: Algorithm) -> Self {
let rounds = match algorithm {
Algorithm::Pbkdf2Sha256 => Self::RECOMMENDED_ROUNDS,
Algorithm::Pbkdf2Sha512 => Self::RECOMMENDED_SHA512_ROUNDS,
};
Self {
rounds,
output_len: Self::RECOMMENDED_OUTPUT_LENGTH,
}
}
#[cfg(feature = "password-hash")]
pub const fn new(rounds: u32) -> Result<Self> {
Self::new_with_output_len(rounds, Self::RECOMMENDED_OUTPUT_LENGTH)
}
#[cfg(feature = "password-hash")]
pub const fn new_with_output_len(rounds: u32, output_len: usize) -> Result<Self> {
if rounds < Self::MIN_ROUNDS
|| output_len < Self::MIN_OUTPUT_LENGTH
|| output_len > Self::MAX_OUTPUT_LENGTH
{
return Err(Error::ParamsInvalid);
}
Ok(Self { rounds, output_len })
}
#[must_use]
pub const fn rounds(self) -> u32 {
self.rounds
}
#[must_use]
pub const fn output_len(self) -> usize {
self.output_len
}
}
impl Default for Params {
fn default() -> Params {
Params::RECOMMENDED
}
}
impl Display for Params {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.rounds)
}
}
#[cfg(feature = "password-hash")]
impl FromStr for Params {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
u32::from_str(s)
.map_err(|_| Error::EncodingInvalid)
.and_then(Params::new)
}
}
#[cfg(feature = "password-hash")]
impl TryFrom<u32> for Params {
type Error = Error;
fn try_from(value: u32) -> Result<Self> {
Self::new(value)
}
}
#[cfg(feature = "phc")]
impl TryFrom<&ParamsString> for Params {
type Error = Error;
fn try_from(params_string: &ParamsString) -> Result<Self> {
let mut rounds = Params::RECOMMENDED_ROUNDS;
let mut output_len = Params::RECOMMENDED_OUTPUT_LENGTH;
for (ident, value) in params_string.iter() {
match ident.as_str() {
"i" => {
rounds = value
.decimal()
.map_err(|_| Error::ParamInvalid { name: "i" })?;
if rounds < Self::MIN_ROUNDS {
return Err(Error::ParamInvalid { name: "i" });
}
}
"l" => {
output_len = value
.decimal()
.ok()
.and_then(|dec| dec.try_into().ok())
.ok_or(Error::ParamInvalid { name: "l" })?;
if output_len > Self::MAX_OUTPUT_LENGTH {
return Err(Error::ParamInvalid { name: "l" });
}
}
_ => return Err(Error::ParamsInvalid),
}
}
Params::new_with_output_len(rounds, output_len)
}
}
#[cfg(feature = "phc")]
impl TryFrom<&phc::PasswordHash> for Params {
type Error = Error;
fn try_from(hash: &phc::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_len {
return Err(Error::OutputSize);
}
}
Ok(params)
}
}
#[cfg(feature = "phc")]
impl TryFrom<Params> for ParamsString {
type Error = Error;
fn try_from(params: Params) -> Result<ParamsString> {
Self::try_from(¶ms)
}
}
#[cfg(feature = "phc")]
impl TryFrom<&Params> for ParamsString {
type Error = Error;
fn try_from(input: &Params) -> Result<ParamsString> {
let mut output = ParamsString::new();
let output_len = Decimal::try_from(input.output_len).map_err(|_| Error::OutputSize)?;
for (name, value) in [("i", input.rounds), ("l", output_len)] {
output
.add_decimal(name, value)
.map_err(|_| Error::ParamInvalid { name })?;
}
Ok(output)
}
}