#![no_std]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg",
html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg"
)]
#![forbid(unsafe_code)]
#![warn(rust_2018_idioms, missing_docs)]
#[macro_use]
extern crate alloc;
mod block;
mod error;
mod instance;
pub use crate::error::Error;
#[cfg(feature = "password-hash")]
#[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))]
pub use password_hash::{self, PasswordHash, PasswordHasher, PasswordVerifier};
use crate::{block::Block, instance::Instance};
use blake2::{digest, Blake2b, Digest};
use core::{
convert::TryFrom,
fmt::{self, Display},
str::FromStr,
};
#[cfg(feature = "password-hash")]
use {
core::convert::TryInto,
password_hash::{Decimal, HasherError, Ident, ParamsError, ParamsString, Salt},
};
pub const MIN_LANES: u32 = 1;
pub const MAX_LANES: u32 = 0xFFFFFF;
pub const MIN_THREADS: u32 = 1;
pub const MAX_THREADS: u32 = 0xFFFFFF;
pub const MIN_OUTLEN: usize = 4;
pub const MAX_OUTLEN: usize = 0xFFFFFFFF;
pub const MIN_MEMORY: u32 = 2 * SYNC_POINTS;
pub const MAX_MEMORY: u32 = 0x0FFFFFFF;
pub const MIN_TIME: u32 = 1;
pub const MAX_TIME: u32 = 0xFFFFFFFF;
pub const MAX_PWD_LENGTH: usize = 0xFFFFFFFF;
pub const MAX_AD_LENGTH: usize = 0xFFFFFFFF;
pub const MIN_SALT_LENGTH: usize = 8;
pub const MAX_SALT_LENGTH: usize = 0xFFFFFFFF;
pub const MAX_SECRET: usize = 0xFFFFFFFF;
pub const BLOCK_SIZE: usize = 1024;
const SYNC_POINTS: u32 = 4;
#[cfg(feature = "password-hash")]
#[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))]
pub const ARGON2D_IDENT: Ident<'_> = Ident::new("argon2d");
#[cfg(feature = "password-hash")]
#[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))]
pub const ARGON2I_IDENT: Ident<'_> = Ident::new("argon2i");
#[cfg(feature = "password-hash")]
#[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))]
pub const ARGON2ID_IDENT: Ident<'_> = Ident::new("argon2id");
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub enum Algorithm {
Argon2d = 0,
Argon2i = 1,
Argon2id = 2,
}
impl Default for Algorithm {
fn default() -> Algorithm {
Algorithm::Argon2id
}
}
impl Algorithm {
pub fn new(id: impl AsRef<str>) -> Result<Self, Error> {
id.as_ref().parse()
}
pub fn as_str(&self) -> &str {
match self {
Algorithm::Argon2d => "argon2d",
Algorithm::Argon2i => "argon2i",
Algorithm::Argon2id => "argon2id",
}
}
#[cfg(feature = "password-hash")]
#[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))]
pub fn ident(&self) -> Ident<'static> {
match self {
Algorithm::Argon2d => ARGON2D_IDENT,
Algorithm::Argon2i => ARGON2I_IDENT,
Algorithm::Argon2id => ARGON2ID_IDENT,
}
}
fn to_le_bytes(self) -> [u8; 4] {
(self as u32).to_le_bytes()
}
}
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, Error> {
match s {
"argon2d" => Ok(Algorithm::Argon2d),
"argon2i" => Ok(Algorithm::Argon2i),
"argon2id" => Ok(Algorithm::Argon2id),
_ => Err(Error::AlgorithmInvalid),
}
}
}
#[cfg(feature = "password-hash")]
#[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))]
impl From<Algorithm> for Ident<'static> {
fn from(alg: Algorithm) -> Ident<'static> {
alg.ident()
}
}
#[cfg(feature = "password-hash")]
#[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))]
impl<'a> TryFrom<Ident<'a>> for Algorithm {
type Error = HasherError;
fn try_from(ident: Ident<'a>) -> Result<Algorithm, HasherError> {
match ident {
ARGON2D_IDENT => Ok(Algorithm::Argon2d),
ARGON2I_IDENT => Ok(Algorithm::Argon2i),
ARGON2ID_IDENT => Ok(Algorithm::Argon2id),
_ => Err(HasherError::Algorithm),
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
#[repr(u32)]
pub enum Version {
V0x10 = 0x10,
V0x13 = 0x13,
}
impl Version {
fn to_le_bytes(self) -> [u8; 4] {
(self as u32).to_le_bytes()
}
}
impl Default for Version {
fn default() -> Self {
Self::V0x13
}
}
impl From<Version> for u32 {
fn from(version: Version) -> u32 {
version as u32
}
}
impl TryFrom<u32> for Version {
type Error = Error;
fn try_from(version_id: u32) -> Result<Version, Error> {
match version_id {
0x10 => Ok(Version::V0x10),
0x13 => Ok(Version::V0x13),
_ => Err(Error::VersionInvalid),
}
}
}
#[derive(Clone)]
pub struct Argon2<'key> {
secret: Option<&'key [u8]>,
t_cost: u32,
m_cost: u32,
lanes: u32,
threads: u32,
version: Version,
}
impl Default for Argon2<'_> {
fn default() -> Self {
Self::new(None, 3, 4096, 1, Version::default()).expect("invalid default Argon2 params")
}
}
impl<'key> Argon2<'key> {
pub fn new(
secret: Option<&'key [u8]>,
t_cost: u32,
m_cost: u32,
parallelism: u32,
version: Version,
) -> Result<Self, Error> {
let lanes = parallelism;
if let Some(secret) = &secret {
if MAX_SECRET < secret.len() {
return Err(Error::SecretTooLong);
}
}
if MIN_MEMORY > m_cost {
return Err(Error::MemoryTooLittle);
}
if MAX_MEMORY < m_cost {
return Err(Error::MemoryTooMuch);
}
if m_cost < 8 * lanes {
return Err(Error::MemoryTooLittle);
}
if t_cost < MIN_TIME {
return Err(Error::TimeTooSmall);
}
if MIN_LANES > lanes {
return Err(Error::LanesTooFew);
}
if MAX_LANES < parallelism {
return Err(Error::LanesTooMany);
}
if MIN_THREADS > lanes {
return Err(Error::ThreadsTooFew);
}
if MAX_THREADS < parallelism {
return Err(Error::ThreadsTooMany);
}
Ok(Self {
secret,
t_cost,
m_cost,
lanes,
threads: parallelism,
version,
})
}
pub fn hash_password_into(
&self,
alg: Algorithm,
pwd: &[u8],
salt: &[u8],
ad: &[u8],
out: &mut [u8],
) -> Result<(), Error> {
if MIN_OUTLEN > out.len() {
return Err(Error::OutputTooShort);
}
if MAX_OUTLEN < out.len() {
return Err(Error::OutputTooLong);
}
if MAX_PWD_LENGTH < pwd.len() {
return Err(Error::PwdTooLong);
}
if MIN_SALT_LENGTH > salt.len() {
return Err(Error::SaltTooShort);
}
if MAX_SALT_LENGTH < salt.len() {
return Err(Error::SaltTooLong);
}
if MAX_AD_LENGTH < ad.len() {
return Err(Error::AdTooLong);
}
let memory_blocks = (self.segment_length() * self.lanes * SYNC_POINTS) as usize;
let initial_hash = self.initial_hash(alg, pwd, salt, ad, out);
let mut memory = vec![Block::default(); memory_blocks];
Instance::hash(self, alg, initial_hash, &mut memory, out)
}
pub(crate) fn initial_hash(
&self,
alg: Algorithm,
pwd: &[u8],
salt: &[u8],
ad: &[u8],
out: &[u8],
) -> digest::Output<Blake2b> {
let mut digest = Blake2b::new();
digest.update(&self.lanes.to_le_bytes());
digest.update(&(out.len() as u32).to_le_bytes());
digest.update(&self.m_cost.to_le_bytes());
digest.update(&self.t_cost.to_le_bytes());
digest.update(&self.version.to_le_bytes());
digest.update(&alg.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(&(ad.len() as u32).to_le_bytes());
digest.update(ad);
digest.finalize()
}
pub(crate) fn segment_length(&self) -> u32 {
let memory_blocks = if self.m_cost < 2 * SYNC_POINTS * self.lanes {
2 * SYNC_POINTS * self.lanes
} else {
self.m_cost
};
memory_blocks / (self.lanes * SYNC_POINTS)
}
}
#[cfg(feature = "password-hash")]
#[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))]
impl PasswordHasher for Argon2<'_> {
type Params = Params;
fn hash_password<'a>(
&self,
password: &[u8],
alg_id: Option<Ident<'a>>,
version_id: Option<Decimal>,
params: Params,
salt: Salt<'a>,
) -> Result<PasswordHash<'a>, HasherError> {
let algorithm = alg_id
.map(Algorithm::try_from)
.transpose()?
.unwrap_or_default();
let version = version_id
.map(Version::try_from)
.transpose()
.map_err(|_| HasherError::Version)?
.unwrap_or(self.version);
let mut salt_arr = [0u8; 64];
let salt_bytes = salt.b64_decode(&mut salt_arr)?;
let ad = b"";
let hasher = Self::new(
self.secret,
params.t_cost,
params.m_cost,
params.p_cost,
version,
)
.map_err(|_| HasherError::Params(ParamsError::InvalidValue))?;
if MAX_PWD_LENGTH < password.len() {
return Err(HasherError::Password);
}
if !(MIN_SALT_LENGTH..=MAX_SALT_LENGTH).contains(&salt_bytes.len()) {
return Err(HasherError::Crypto);
}
if MAX_AD_LENGTH < ad.len() {
return Err(HasherError::Crypto);
}
let output = password_hash::Output::init_with(params.output_length, |out| {
hasher
.hash_password_into(algorithm, password, salt_bytes, ad, out)
.map_err(|e| {
match e {
Error::OutputTooShort => password_hash::OutputError::TooShort,
Error::OutputTooLong => password_hash::OutputError::TooLong,
_ => panic!("unhandled error type: {}", e),
}
})
})?;
let res = Ok(PasswordHash {
algorithm: algorithm.ident(),
version: Some(version.into()),
params: params.try_into()?,
salt: Some(salt),
hash: Some(output),
});
res
}
}
#[cfg(feature = "password-hash")]
#[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Params {
pub m_cost: u32,
pub t_cost: u32,
pub p_cost: u32,
pub output_length: usize,
}
#[cfg(feature = "password-hash")]
impl Default for Params {
fn default() -> Params {
let ctx = Argon2::default();
Params {
m_cost: ctx.m_cost,
t_cost: ctx.t_cost,
p_cost: ctx.threads,
output_length: 32,
}
}
}
#[cfg(feature = "password-hash")]
impl TryFrom<&ParamsString> for Params {
type Error = HasherError;
fn try_from(input: &ParamsString) -> Result<Self, HasherError> {
let mut params = Params::default();
for (ident, value) in input.iter() {
match ident.as_str() {
"m" => params.m_cost = value.decimal()?,
"t" => params.t_cost = value.decimal()?,
"p" => params.p_cost = value.decimal()?,
"keyid" => (), _ => return Err(ParamsError::InvalidName.into()),
}
}
Ok(params)
}
}
#[cfg(feature = "password-hash")]
impl<'a> TryFrom<Params> for ParamsString {
type Error = HasherError;
fn try_from(params: Params) -> Result<ParamsString, HasherError> {
let mut output = ParamsString::new();
output.add_decimal("m", params.m_cost)?;
output.add_decimal("t", params.t_cost)?;
output.add_decimal("p", params.p_cost)?;
Ok(output)
}
}
#[cfg(all(test, feature = "password-hash"))]
mod tests {
use super::{Argon2, HasherError, Params, PasswordHasher, Salt};
const EXAMPLE_PASSWORD: &[u8] = b"hunter42";
#[test]
fn decoded_salt_too_short() {
let argon2 = Argon2::default();
let salt = Salt::new("somesalt").unwrap();
let res = argon2.hash_password(EXAMPLE_PASSWORD, None, None, Params::default(), salt);
assert_eq!(res, Err(HasherError::Crypto));
}
}