Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions argon2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ edition = "2018"
readme = "README.md"

[dependencies]
base64ct = "1"
blake2 = { version = "0.9", default-features = false }
password-hash = { version = "=0.3.0-pre.1", optional = true }
rayon = { version = "1", optional = true }
Expand Down
37 changes: 25 additions & 12 deletions argon2/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ use password_hash::errors::InvalidValue;
pub type Result<T> = core::result::Result<T, Error>;

/// Error type.
// TODO(tarcieri): consolidate/replace with `password_hash::Error`
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Error {
/// Associated data is too long.
Expand All @@ -18,6 +17,12 @@ pub enum Error {
/// Algorithm identifier invalid.
AlgorithmInvalid,

/// "B64" encoding is invalid.
B64Encoding(base64ct::Error),

/// Key ID is too long.
KeyIdTooLong,

/// Memory cost is too small.
MemoryTooLittle,

Expand Down Expand Up @@ -60,6 +65,8 @@ impl fmt::Display for Error {
f.write_str(match self {
Error::AdTooLong => "associated data is too long",
Error::AlgorithmInvalid => "algorithm identifier invalid",
Error::B64Encoding(inner) => return write!(f, "B64 encoding invalid: {}", inner),
Error::KeyIdTooLong => "key ID is too long",
Error::MemoryTooLittle => "memory cost is too small",
Error::MemoryTooMuch => "memory cost is too large",
Error::OutputTooShort => "output is too short",
Expand All @@ -76,26 +83,32 @@ impl fmt::Display for Error {
}
}

impl From<base64ct::Error> for Error {
fn from(err: base64ct::Error) -> Error {
Error::B64Encoding(err)
}
}

#[cfg(feature = "password-hash")]
#[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))]
impl From<Error> for password_hash::Error {
fn from(err: Error) -> password_hash::Error {
match err {
Error::AdTooLong => password_hash::Error::ParamValueInvalid(InvalidValue::TooLong),
Error::AdTooLong => InvalidValue::TooLong.param_error(),
Error::AlgorithmInvalid => password_hash::Error::Algorithm,
Error::MemoryTooLittle => {
password_hash::Error::ParamValueInvalid(InvalidValue::TooShort)
}
Error::MemoryTooMuch => password_hash::Error::ParamValueInvalid(InvalidValue::TooLong),
Error::B64Encoding(inner) => password_hash::Error::B64Encoding(inner),
Error::KeyIdTooLong => InvalidValue::TooLong.param_error(),
Error::MemoryTooLittle => InvalidValue::TooShort.param_error(),
Error::MemoryTooMuch => InvalidValue::TooLong.param_error(),
Error::PwdTooLong => password_hash::Error::Password,
Error::OutputTooShort => password_hash::Error::OutputTooShort,
Error::OutputTooLong => password_hash::Error::OutputTooLong,
Error::SaltTooShort => password_hash::Error::SaltInvalid(InvalidValue::TooShort),
Error::SaltTooLong => password_hash::Error::SaltInvalid(InvalidValue::TooLong),
Error::SecretTooLong => password_hash::Error::ParamValueInvalid(InvalidValue::TooLong),
Error::ThreadsTooFew => password_hash::Error::ParamValueInvalid(InvalidValue::TooShort),
Error::ThreadsTooMany => password_hash::Error::ParamValueInvalid(InvalidValue::TooLong),
Error::TimeTooSmall => password_hash::Error::ParamValueInvalid(InvalidValue::TooShort),
Error::SaltTooShort => InvalidValue::TooShort.salt_error(),
Error::SaltTooLong => InvalidValue::TooLong.salt_error(),
Error::SecretTooLong => InvalidValue::TooLong.param_error(),
Error::ThreadsTooFew => InvalidValue::TooShort.param_error(),
Error::ThreadsTooMany => InvalidValue::TooLong.param_error(),
Error::TimeTooSmall => InvalidValue::TooShort.param_error(),
Error::VersionInvalid => password_hash::Error::Version,
}
}
Expand Down
6 changes: 3 additions & 3 deletions argon2/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ struct Position {
index: u32,
}

/// Argon2 instance: memory pointer, number of passes, amount of memory, type,
/// Argon2 instance: memory buffer, number of passes, amount of memory, type,
/// and derived values.
///
/// Used to evaluate the number and location of blocks to construct in each
Expand Down Expand Up @@ -405,11 +405,11 @@ fn next_addresses(address_block: &mut Block, input_block: &mut Block, zero_block

/// BLAKE2b with an extended output, as described in the Argon2 paper
fn blake2b_long(inputs: &[&[u8]], mut out: &mut [u8]) -> Result<()> {
if out.len() < Params::MIN_OUTPUT_LENGTH as usize {
if out.len() < Params::MIN_OUTPUT_LEN as usize {
return Err(Error::OutputTooLong);
}

if out.len() > Params::MAX_OUTPUT_LENGTH as usize {
if out.len() > Params::MAX_OUTPUT_LEN as usize {
return Err(Error::OutputTooLong);
}

Expand Down
103 changes: 27 additions & 76 deletions argon2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,53 +113,30 @@ use blake2::{digest, Blake2b, Digest};

#[cfg(all(feature = "alloc", feature = "password-hash"))]
use {
core::convert::{TryFrom, TryInto},
password_hash::{Decimal, Ident, Salt},
core::convert::TryFrom,
password_hash::{Decimal, Ident, ParamsString, Salt},
};

/// Maximum password length in bytes.
pub const MAX_PWD_LENGTH: usize = 0xFFFFFFFF;

/// Minimum and maximum associated data length in bytes.
pub const MAX_AD_LENGTH: usize = 0xFFFFFFFF;
pub const MAX_PWD_LEN: usize = 0xFFFFFFFF;

/// Minimum and maximum salt length in bytes.
pub const MIN_SALT_LENGTH: usize = 8;
pub const MIN_SALT_LEN: usize = 8;

/// Maximum salt length in bytes.
pub const MAX_SALT_LENGTH: usize = 0xFFFFFFFF;
pub const MAX_SALT_LEN: usize = 0xFFFFFFFF;

/// Maximum secret key length in bytes.
pub const MAX_SECRET_LENGTH: usize = 0xFFFFFFFF;
pub const MAX_SECRET_LEN: usize = 0xFFFFFFFF;

/// Argon2 context.
///
/// Holds the following Argon2 inputs:
///
/// - output array and its length,
/// - password and its length,
/// - salt and its length,
/// - secret and its length,
/// - associated data and its length,
/// - number of passes, amount of used memory (in KBytes, can be rounded up a bit)
/// - number of parallel threads that will be run.
///
/// All the parameters above affect the output hash value.
/// Additionally, two function pointers can be provided to allocate and
/// deallocate the memory (if NULL, memory will be allocated internally).
/// Also, three flags indicate whether to erase password, secret as soon as they
/// are pre-hashed (and thus not needed anymore), and the entire memory
///
/// Simplest situation: you have output array `out[8]`, password is stored in
/// `pwd[32]`, salt is stored in `salt[16]`, you do not have keys nor associated
/// data.
/// This is the primary type of this crate's API, and contains the following:
///
/// You need to spend 1 GB of RAM and you run 5 passes of Argon2d with
/// 4 parallel lanes.
///
/// You want to erase the password, but you're OK with last pass not being
/// erased.
// TODO(tarcieri): replace `Params`-related fields with an internally-stored struct
/// - Argon2 [`Algorithm`] variant to be used
/// - Argon2 [`Version`] to be used
/// - Default set of [`Params`] to be used
/// - (Optional) Secret key a.k.a. "pepper" to be used
#[derive(Clone)]
pub struct Argon2<'key> {
/// Algorithm to use
Expand Down Expand Up @@ -199,7 +176,7 @@ impl<'key> Argon2<'key> {
version: Version,
params: Params,
) -> Result<Self> {
if MAX_SECRET_LENGTH < secret.len() {
if MAX_SECRET_LEN < secret.len() {
return Err(Error::SecretTooLong);
}

Expand All @@ -214,15 +191,9 @@ impl<'key> Argon2<'key> {
/// Hash a password and associated parameters into the provided output buffer.
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub fn hash_password_into(
&self,
pwd: &[u8],
salt: &[u8],
ad: &[u8],
out: &mut [u8],
) -> Result<()> {
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, ad, out, &mut blocks)
self.hash_password_into_with_memory(pwd, salt, out, &mut blocks)
}

/// Hash a password and associated parameters into the provided output buffer.
Expand All @@ -238,49 +209,33 @@ impl<'key> Argon2<'key> {
&self,
pwd: &[u8],
salt: &[u8],
ad: &[u8],
out: &mut [u8],
mut memory_blocks: impl AsMut<[Block]>,
) -> Result<()> {
// Validate output length
if out.len()
< self
.params
.output_len()
.unwrap_or(Params::MIN_OUTPUT_LENGTH)
{
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_LENGTH)
{
if out.len() > self.params.output_len().unwrap_or(Params::MAX_OUTPUT_LEN) {
return Err(Error::OutputTooLong);
}

if pwd.len() > MAX_PWD_LENGTH {
if pwd.len() > MAX_PWD_LEN {
return Err(Error::PwdTooLong);
}

// Validate salt (required param)
if salt.len() < MIN_SALT_LENGTH {
if salt.len() < MIN_SALT_LEN {
return Err(Error::SaltTooShort);
}

if salt.len() > MAX_SALT_LENGTH {
if salt.len() > MAX_SALT_LEN {
return Err(Error::SaltTooLong);
}

// Validate associated data (optional param)
if ad.len() > MAX_AD_LENGTH {
return Err(Error::AdTooLong);
}

// Hashing all inputs
let initial_hash = self.initial_hash(pwd, salt, ad, out);
let initial_hash = self.initial_hash(pwd, salt, out);

let segment_length = self.params.segment_length();
let block_count = self.params.block_count();
Expand All @@ -298,12 +253,11 @@ impl<'key> Argon2<'key> {
&self.params
}

/// Hashes all the inputs into `blockhash[PREHASH_DIGEST_LENGTH]`.
/// Hashes all the inputs into `blockhash[PREHASH_DIGEST_LEN]`.
pub(crate) fn initial_hash(
&self,
pwd: &[u8],
salt: &[u8],
ad: &[u8],
out: &[u8],
) -> digest::Output<Blake2b> {
let mut digest = Blake2b::new();
Expand All @@ -325,8 +279,8 @@ impl<'key> Argon2<'key> {
digest.update(0u32.to_le_bytes());
}

digest.update(&(ad.len() as u32).to_le_bytes());
digest.update(ad);
digest.update(&(self.params.data().len() as u32).to_le_bytes());
digest.update(self.params.data());
digest.finalize()
}
}
Expand All @@ -348,22 +302,19 @@ impl PasswordHasher for Argon2<'_> {
let salt = Salt::try_from(salt.as_ref())?;
let mut salt_arr = [0u8; 64];
let salt_bytes = salt.b64_decode(&mut salt_arr)?;

// TODO(tarcieri): support the `data` parameter (i.e. associated data)
let ad = b"";
let output_len = self
.params
.output_len()
.unwrap_or(Params::DEFAULT_OUTPUT_LENGTH);
.unwrap_or(Params::DEFAULT_OUTPUT_LEN);

let output = password_hash::Output::init_with(output_len, |out| {
Ok(self.hash_password_into(password, salt_bytes, ad, out)?)
Ok(self.hash_password_into(password, salt_bytes, out)?)
})?;

Ok(PasswordHash {
algorithm: self.algorithm.ident(),
version: Some(self.version.into()),
params: self.params.try_into()?,
params: ParamsString::try_from(&self.params)?,
salt: Some(salt),
hash: Some(output),
})
Expand Down Expand Up @@ -407,7 +358,7 @@ impl<'key> From<Params> for Argon2<'key> {

impl<'key> From<&Params> for Argon2<'key> {
fn from(params: &Params) -> Self {
Self::from(*params)
Self::from(params.clone())
}
}

Expand Down
Loading