#![no_std]
#![cfg_attr(not(feature = "parallel"), forbid(unsafe_code))]
#![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")]
#[macro_use]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
mod algorithm;
mod block;
mod error;
mod instance;
mod memory;
mod params;
mod version;
pub use crate::{
algorithm::Algorithm,
block::Block,
error::{Error, Result},
params::{Params, ParamsBuilder},
version::Version,
};
#[cfg(feature = "password-hash")]
#[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))]
pub use {
crate::algorithm::{ARGON2D_IDENT, ARGON2ID_IDENT, ARGON2I_IDENT},
password_hash::{self, PasswordHash, PasswordHasher, PasswordVerifier},
};
use crate::{
instance::Instance,
memory::{Memory, SYNC_POINTS},
};
use blake2::{digest::Output, Blake2b512, Digest};
#[cfg(all(feature = "alloc", feature = "password-hash"))]
use password_hash::{Decimal, Ident, ParamsString, Salt};
pub const MAX_PWD_LEN: usize = 0xFFFFFFFF;
pub const MIN_SALT_LEN: usize = 8;
pub const MAX_SALT_LEN: usize = 0xFFFFFFFF;
pub const RECOMMENDED_SALT_LEN: usize = 16;
pub const MAX_SECRET_LEN: usize = 0xFFFFFFFF;
#[derive(Clone)]
pub struct Argon2<'key> {
algorithm: Algorithm,
version: Version,
params: Params,
secret: Option<&'key [u8]>,
}
impl Default for Argon2<'_> {
fn default() -> Self {
Self::new(Algorithm::default(), Version::default(), Params::default())
}
}
impl<'key> Argon2<'key> {
pub fn new(algorithm: Algorithm, version: Version, params: Params) -> Self {
Self {
algorithm,
version,
params,
secret: None,
}
}
pub fn new_with_secret(
secret: &'key [u8],
algorithm: Algorithm,
version: Version,
params: Params,
) -> Result<Self> {
if MAX_SECRET_LEN < secret.len() {
return Err(Error::SecretTooLong);
}
Ok(Self {
algorithm,
version,
params,
secret: Some(secret),
})
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub fn hash_password_into(&self, pwd: &[u8], salt: &[u8], out: &mut [u8]) -> Result<()> {
let mut blocks = vec![Block::default(); self.params.block_count()];
self.hash_password_into_with_memory(pwd, salt, out, &mut blocks)
}
pub fn hash_password_into_with_memory(
&self,
pwd: &[u8],
salt: &[u8],
out: &mut [u8],
mut memory_blocks: impl AsMut<[Block]>,
) -> Result<()> {
if out.len() < self.params.output_len().unwrap_or(Params::MIN_OUTPUT_LEN) {
return Err(Error::OutputTooShort);
}
if out.len() > self.params.output_len().unwrap_or(Params::MAX_OUTPUT_LEN) {
return Err(Error::OutputTooLong);
}
if pwd.len() > MAX_PWD_LEN {
return Err(Error::PwdTooLong);
}
if salt.len() < MIN_SALT_LEN {
return Err(Error::SaltTooShort);
}
if salt.len() > MAX_SALT_LEN {
return Err(Error::SaltTooLong);
}
let initial_hash = self.initial_hash(pwd, salt, out);
let segment_length = self.params.segment_length();
let block_count = self.params.block_count();
let memory_blocks = memory_blocks
.as_mut()
.get_mut(..block_count)
.ok_or(Error::MemoryTooLittle)?;
let memory = Memory::new(memory_blocks, segment_length);
Instance::hash(self, self.algorithm, initial_hash, memory, out)
}
pub fn params(&self) -> &Params {
&self.params
}
pub(crate) fn initial_hash(&self, pwd: &[u8], salt: &[u8], out: &[u8]) -> Output<Blake2b512> {
let mut digest = Blake2b512::new();
digest.update(&self.params.lanes().to_le_bytes());
digest.update(&(out.len() as u32).to_le_bytes());
digest.update(&self.params.m_cost().to_le_bytes());
digest.update(&self.params.t_cost().to_le_bytes());
digest.update(&self.version.to_le_bytes());
digest.update(&self.algorithm.to_le_bytes());
digest.update(&(pwd.len() as u32).to_le_bytes());
digest.update(pwd);
digest.update(&(salt.len() as u32).to_le_bytes());
digest.update(salt);
if let Some(secret) = &self.secret {
digest.update(&(secret.len() as u32).to_le_bytes());
digest.update(secret);
} else {
digest.update(0u32.to_le_bytes());
}
digest.update(&(self.params.data().len() as u32).to_le_bytes());
digest.update(self.params.data());
digest.finalize()
}
}
#[cfg(all(feature = "alloc", feature = "password-hash"))]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
#[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))]
impl PasswordHasher for Argon2<'_> {
type Params = Params;
fn hash_password<'a, S>(
&self,
password: &[u8],
salt: &'a S,
) -> password_hash::Result<PasswordHash<'a>>
where
S: AsRef<str> + ?Sized,
{
let salt = Salt::try_from(salt.as_ref())?;
let mut salt_arr = [0u8; 64];
let salt_bytes = salt.b64_decode(&mut salt_arr)?;
let output_len = self
.params
.output_len()
.unwrap_or(Params::DEFAULT_OUTPUT_LEN);
let output = password_hash::Output::init_with(output_len, |out| {
Ok(self.hash_password_into(password, salt_bytes, out)?)
})?;
Ok(PasswordHash {
algorithm: self.algorithm.ident(),
version: Some(self.version.into()),
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();
let version = version
.map(Version::try_from)
.transpose()?
.unwrap_or_default();
let salt = salt.into();
Self {
secret: self.secret,
algorithm,
version,
params,
}
.hash_password(password, salt.as_str())
}
}
impl<'key> From<Params> for Argon2<'key> {
fn from(params: Params) -> Self {
Self::new(Algorithm::default(), Version::default(), params)
}
}
impl<'key> From<&Params> for Argon2<'key> {
fn from(params: &Params) -> Self {
Self::from(params.clone())
}
}
#[cfg(all(test, feature = "alloc", feature = "password-hash"))]
mod tests {
use crate::{Algorithm, Argon2, Params, PasswordHasher, Salt, Version};
const EXAMPLE_PASSWORD: &[u8] = b"hunter42";
const EXAMPLE_SALT: &str = "examplesaltvalue";
#[test]
fn decoded_salt_too_short() {
let argon2 = Argon2::default();
let salt = Salt::new("somesalt").unwrap();
let res =
argon2.hash_password_customized(EXAMPLE_PASSWORD, None, None, Params::default(), salt);
assert_eq!(
res,
Err(password_hash::Error::SaltInvalid(
password_hash::errors::InvalidValue::TooShort
))
);
}
#[test]
fn hash_simple_retains_configured_params() {
let t_cost = 4;
let m_cost = 2048;
let p_cost = 2;
let version = Version::V0x10;
let params = Params::new(m_cost, t_cost, p_cost, None).unwrap();
let hasher = Argon2::new(Algorithm::default(), version, params);
let hash = hasher
.hash_password(EXAMPLE_PASSWORD, EXAMPLE_SALT)
.unwrap();
assert_eq!(hash.version.unwrap(), version.into());
for &(param, value) in &[("t", t_cost), ("m", m_cost), ("p", p_cost)] {
assert_eq!(
hash.params
.get(param)
.and_then(|p| p.decimal().ok())
.unwrap(),
value
);
}
}
}