blob: aa215f0c5558b016b30f566ea63ffbe063c5d3a0 [file] [log] [blame]
//! Implementation of the `password-hash` crate API.
use crate::pbkdf2;
use core::cmp::Ordering;
use core::{
convert::{TryFrom, TryInto},
fmt::{self, Display},
str::FromStr,
};
use hmac::Hmac;
use password_hash::{
errors::InvalidValue, Decimal, Error, Ident, Output, ParamsString, PasswordHash,
PasswordHasher, Result, Salt,
};
use sha2::{Sha256, Sha512};
#[cfg(feature = "sha1")]
use sha1::Sha1;
/// PBKDF2 (SHA-1)
#[cfg(feature = "sha1")]
pub const PBKDF2_SHA1: Ident = Ident::new("pbkdf2");
/// PBKDF2 (SHA-256)
pub const PBKDF2_SHA256: Ident = Ident::new("pbkdf2-sha256");
/// PBKDF2 (SHA-512)
pub const PBKDF2_SHA512: Ident = Ident::new("pbkdf2-sha512");
/// PBKDF2 type for use with [`PasswordHasher`].
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[cfg_attr(docsrs, doc(cfg(feature = "simple")))]
pub struct Pbkdf2;
impl PasswordHasher for Pbkdf2 {
type Params = Params;
fn hash_password_customized<'a>(
&self,
password: &[u8],
alg_id: Option<Ident<'a>>,
version: Option<Decimal>,
params: Params,
salt: impl Into<Salt<'a>>,
) -> Result<PasswordHash<'a>> {
let algorithm = Algorithm::try_from(alg_id.unwrap_or(PBKDF2_SHA256))?;
// Versions unsupported
if version.is_some() {
return Err(Error::Version);
}
let salt = salt.into();
let mut salt_arr = [0u8; 64];
let salt_bytes = salt.b64_decode(&mut salt_arr)?;
let output = Output::init_with(params.output_length, |out| {
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_bytes, params.rounds, out);
Ok(())
})?;
Ok(PasswordHash {
algorithm: algorithm.ident(),
version: None,
params: params.try_into()?,
salt: Some(salt),
hash: Some(output),
})
}
}
/// PBKDF2 variants.
///
/// <https://en.wikipedia.org/wiki/PBKDF2>
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
#[non_exhaustive]
#[cfg_attr(docsrs, doc(cfg(feature = "simple")))]
pub enum Algorithm {
/// PBKDF2 SHA1
#[cfg(feature = "sha1")]
#[cfg_attr(docsrs, doc(cfg(feature = "sha1")))]
Pbkdf2Sha1,
/// PBKDF2 SHA-256
Pbkdf2Sha256,
/// PBKDF2 SHA-512
Pbkdf2Sha512,
}
impl Algorithm {
/// Parse an [`Algorithm`] from the provided string.
pub fn new(id: impl AsRef<str>) -> Result<Self> {
id.as_ref().parse()
}
/// Get the [`Ident`] that corresponds to this PBKDF2 [`Algorithm`].
pub fn ident(&self) -> Ident<'static> {
match self {
#[cfg(feature = "sha1")]
Algorithm::Pbkdf2Sha1 => PBKDF2_SHA1,
Algorithm::Pbkdf2Sha256 => PBKDF2_SHA256,
Algorithm::Pbkdf2Sha512 => PBKDF2_SHA512,
}
}
/// Get the identifier string for this PBKDF2 [`Algorithm`].
pub fn as_str(&self) -> &str {
self.ident().as_str()
}
}
impl AsRef<str> for Algorithm {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl 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> {
Ident::try_from(s)?.try_into()
}
}
impl From<Algorithm> for Ident<'static> {
fn from(alg: Algorithm) -> Ident<'static> {
alg.ident()
}
}
impl<'a> TryFrom<Ident<'a>> for Algorithm {
type Error = Error;
fn try_from(ident: Ident<'a>) -> Result<Algorithm> {
match ident {
#[cfg(feature = "sha1")]
PBKDF2_SHA1 => Ok(Algorithm::Pbkdf2Sha1),
PBKDF2_SHA256 => Ok(Algorithm::Pbkdf2Sha256),
PBKDF2_SHA512 => Ok(Algorithm::Pbkdf2Sha512),
_ => Err(Error::Algorithm),
}
}
}
/// PBKDF2 params
#[cfg_attr(docsrs, doc(cfg(feature = "simple")))]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Params {
/// Number of rounds
pub rounds: u32,
/// Size of the output (in bytes)
pub output_length: usize,
}
impl Default for Params {
fn default() -> Params {
Params {
rounds: 10_000,
output_length: 32,
}
}
}
impl<'a> TryFrom<&'a PasswordHash<'a>> for Params {
type Error = Error;
fn try_from(hash: &'a PasswordHash<'a>) -> Result<Self> {
let mut params = Params::default();
let mut output_length = None;
if hash.version.is_some() {
return Err(Error::Version);
}
for (ident, value) in hash.params.iter() {
match ident.as_str() {
"i" => params.rounds = value.decimal()?,
"l" => {
output_length = Some(
value
.decimal()?
.try_into()
.map_err(|_| InvalidValue::Malformed.param_error())?,
)
}
_ => return Err(Error::ParamNameInvalid),
}
}
if let Some(len) = output_length {
if let Some(hash) = &hash.hash {
match hash.len().cmp(&len) {
Ordering::Less => return Err(InvalidValue::TooShort.param_error()),
Ordering::Greater => return Err(InvalidValue::TooLong.param_error()),
Ordering::Equal => (),
}
}
params.output_length = len;
}
Ok(params)
}
}
impl<'a> TryFrom<Params> for ParamsString {
type Error = Error;
fn try_from(input: Params) -> Result<ParamsString> {
let mut output = ParamsString::new();
output.add_decimal("i", input.rounds)?;
output.add_decimal("l", input.output_length as u32)?;
Ok(output)
}
}