#![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"
)]
#![warn(rust_2018_idioms, missing_docs)]
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
mod algorithm;
mod balloon;
mod error;
mod params;
pub use crate::{
algorithm::Algorithm,
error::{Error, Result},
params::Params,
};
#[cfg(feature = "password-hash")]
#[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))]
pub use password_hash::{self, PasswordHash, PasswordHasher, PasswordVerifier};
use core::marker::PhantomData;
use crypto_bigint::ArrayDecoding;
use digest::generic_array::GenericArray;
use digest::typenum::Unsigned;
use digest::{Digest, FixedOutputReset};
#[cfg(all(feature = "alloc", feature = "password-hash"))]
pub use password_hash::Salt;
#[cfg(all(feature = "alloc", feature = "password-hash"))]
use password_hash::{Decimal, Ident, ParamsString};
#[cfg(feature = "zeroize")]
use zeroize::Zeroize;
#[derive(Clone, Default)]
pub struct Balloon<'key, D: Digest + FixedOutputReset>
where
GenericArray<u8, D::OutputSize>: ArrayDecoding,
{
pub digest: PhantomData<D>,
pub algorithm: Algorithm,
pub params: Params,
pub secret: Option<&'key [u8]>,
}
impl<'key, D: Digest + FixedOutputReset> Balloon<'key, D>
where
GenericArray<u8, D::OutputSize>: ArrayDecoding,
{
pub fn new(algorithm: Algorithm, params: Params, secret: Option<&'key [u8]>) -> Self {
Self {
digest: PhantomData,
algorithm,
params,
secret,
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub fn hash(&self, pwd: &[u8], salt: &[u8]) -> Result<GenericArray<u8, D::OutputSize>> {
let mut output = GenericArray::default();
self.hash_into(pwd, salt, &mut output)?;
Ok(output)
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub fn hash_into(&self, pwd: &[u8], salt: &[u8], output: &mut [u8]) -> Result<()> {
#[cfg(not(feature = "parallel"))]
let mut memory = alloc::vec![GenericArray::default(); self.params.s_cost.get() as usize];
#[cfg(feature = "parallel")]
let mut memory = alloc::vec![GenericArray::default(); (self.params.s_cost.get() * self.params.p_cost.get()) as usize];
self.hash_into_with_memory(pwd, salt, &mut memory, output)?;
#[cfg(feature = "zeroize")]
memory.iter_mut().for_each(|block| block.zeroize());
Ok(())
}
pub fn hash_with_memory(
&self,
pwd: &[u8],
salt: &[u8],
memory_blocks: &mut [GenericArray<u8, D::OutputSize>],
) -> Result<GenericArray<u8, D::OutputSize>> {
let mut output = GenericArray::default();
self.hash_into_with_memory(pwd, salt, memory_blocks, &mut output)?;
Ok(output)
}
pub fn hash_into_with_memory(
&self,
pwd: &[u8],
salt: &[u8],
memory_blocks: &mut [GenericArray<u8, D::OutputSize>],
output: &mut [u8],
) -> Result<()> {
let output = if output.len() == D::OutputSize::USIZE {
GenericArray::from_mut_slice(output)
} else {
return Err(Error::OutputSize {
actual: output.len(),
expected: D::OutputSize::USIZE,
});
};
match self.algorithm {
Algorithm::Balloon => {
balloon::balloon::<D>(pwd, salt, self.secret, self.params, memory_blocks).map(
|hash| {
output.copy_from_slice(&hash);
},
)
}
Algorithm::BalloonM => {
balloon::balloon_m::<D>(pwd, salt, self.secret, self.params, memory_blocks, output)
}
}
}
}
#[cfg(all(feature = "alloc", feature = "password-hash"))]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
#[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))]
impl<D: Digest + FixedOutputReset> PasswordHasher for Balloon<'_, D>
where
GenericArray<u8, D::OutputSize>: ArrayDecoding,
{
type Params = Params;
fn hash_password<'a>(
&self,
password: &[u8],
salt: impl Into<Salt<'a>>,
) -> password_hash::Result<PasswordHash<'a>> {
let salt = salt.into();
let mut salt_arr = [0u8; 64];
let salt_bytes = salt.decode_b64(&mut salt_arr)?;
let output = password_hash::Output::new(&self.hash(password, salt_bytes)?)?;
Ok(PasswordHash {
algorithm: self.algorithm.ident(),
version: Some(1),
params: ParamsString::try_from(&self.params)?,
salt: Some(salt),
hash: Some(output),
})
}
fn hash_password_customized<'a>(
&self,
password: &[u8],
alg_id: Option<Ident<'a>>,
version: Option<Decimal>,
params: Params,
salt: impl Into<Salt<'a>>,
) -> password_hash::Result<PasswordHash<'a>> {
let algorithm = alg_id
.map(Algorithm::try_from)
.transpose()?
.unwrap_or_default();
if let Some(version) = version {
if version != 1 {
return Err(password_hash::Error::Version);
}
}
let salt = salt.into();
Self::new(algorithm, params, self.secret).hash_password(password, salt)
}
}
impl<'key, D: Digest + FixedOutputReset> From<Params> for Balloon<'key, D>
where
GenericArray<u8, D::OutputSize>: ArrayDecoding,
{
fn from(params: Params) -> Self {
Self::new(Algorithm::default(), params, None)
}
}