Skip to content

Commit

Permalink
argon2: add support for keyid and data to Params(Builder) (Rust…
Browse files Browse the repository at this point in the history
…Crypto#216)

Adds support for optional parameters for identifying a specific Argon2
secret key ID to use when computing a password hash, and optional
associated data.
  • Loading branch information
tarcieri committed Aug 27, 2021
1 parent 4f1f645 commit 02f7f2a
Show file tree
Hide file tree
Showing 10 changed files with 297 additions and 199 deletions.
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
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
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
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
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

0 comments on commit 02f7f2a

Please sign in to comment.