Skip to content

Commit 02f7f2a

Browse files
authored
argon2: add support for keyid and data to Params(Builder) (RustCrypto#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.
1 parent 4f1f645 commit 02f7f2a

File tree

10 files changed

+297
-199
lines changed

10 files changed

+297
-199
lines changed

Cargo.lock

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

argon2/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ edition = "2018"
1515
readme = "README.md"
1616

1717
[dependencies]
18+
base64ct = "1"
1819
blake2 = { version = "0.9", default-features = false }
1920
password-hash = { version = "=0.3.0-pre.1", optional = true }
2021
rayon = { version = "1", optional = true }

argon2/src/error.rs

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ use password_hash::errors::InvalidValue;
99
pub type Result<T> = core::result::Result<T, Error>;
1010

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

20+
/// "B64" encoding is invalid.
21+
B64Encoding(base64ct::Error),
22+
23+
/// Key ID is too long.
24+
KeyIdTooLong,
25+
2126
/// Memory cost is too small.
2227
MemoryTooLittle,
2328

@@ -60,6 +65,8 @@ impl fmt::Display for Error {
6065
f.write_str(match self {
6166
Error::AdTooLong => "associated data is too long",
6267
Error::AlgorithmInvalid => "algorithm identifier invalid",
68+
Error::B64Encoding(inner) => return write!(f, "B64 encoding invalid: {}", inner),
69+
Error::KeyIdTooLong => "key ID is too long",
6370
Error::MemoryTooLittle => "memory cost is too small",
6471
Error::MemoryTooMuch => "memory cost is too large",
6572
Error::OutputTooShort => "output is too short",
@@ -76,26 +83,32 @@ impl fmt::Display for Error {
7683
}
7784
}
7885

86+
impl From<base64ct::Error> for Error {
87+
fn from(err: base64ct::Error) -> Error {
88+
Error::B64Encoding(err)
89+
}
90+
}
91+
7992
#[cfg(feature = "password-hash")]
8093
#[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))]
8194
impl From<Error> for password_hash::Error {
8295
fn from(err: Error) -> password_hash::Error {
8396
match err {
84-
Error::AdTooLong => password_hash::Error::ParamValueInvalid(InvalidValue::TooLong),
97+
Error::AdTooLong => InvalidValue::TooLong.param_error(),
8598
Error::AlgorithmInvalid => password_hash::Error::Algorithm,
86-
Error::MemoryTooLittle => {
87-
password_hash::Error::ParamValueInvalid(InvalidValue::TooShort)
88-
}
89-
Error::MemoryTooMuch => password_hash::Error::ParamValueInvalid(InvalidValue::TooLong),
99+
Error::B64Encoding(inner) => password_hash::Error::B64Encoding(inner),
100+
Error::KeyIdTooLong => InvalidValue::TooLong.param_error(),
101+
Error::MemoryTooLittle => InvalidValue::TooShort.param_error(),
102+
Error::MemoryTooMuch => InvalidValue::TooLong.param_error(),
90103
Error::PwdTooLong => password_hash::Error::Password,
91104
Error::OutputTooShort => password_hash::Error::OutputTooShort,
92105
Error::OutputTooLong => password_hash::Error::OutputTooLong,
93-
Error::SaltTooShort => password_hash::Error::SaltInvalid(InvalidValue::TooShort),
94-
Error::SaltTooLong => password_hash::Error::SaltInvalid(InvalidValue::TooLong),
95-
Error::SecretTooLong => password_hash::Error::ParamValueInvalid(InvalidValue::TooLong),
96-
Error::ThreadsTooFew => password_hash::Error::ParamValueInvalid(InvalidValue::TooShort),
97-
Error::ThreadsTooMany => password_hash::Error::ParamValueInvalid(InvalidValue::TooLong),
98-
Error::TimeTooSmall => password_hash::Error::ParamValueInvalid(InvalidValue::TooShort),
106+
Error::SaltTooShort => InvalidValue::TooShort.salt_error(),
107+
Error::SaltTooLong => InvalidValue::TooLong.salt_error(),
108+
Error::SecretTooLong => InvalidValue::TooLong.param_error(),
109+
Error::ThreadsTooFew => InvalidValue::TooShort.param_error(),
110+
Error::ThreadsTooMany => InvalidValue::TooLong.param_error(),
111+
Error::TimeTooSmall => InvalidValue::TooShort.param_error(),
99112
Error::VersionInvalid => password_hash::Error::Version,
100113
}
101114
}

argon2/src/instance.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ struct Position {
3434
index: u32,
3535
}
3636

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

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

412-
if out.len() > Params::MAX_OUTPUT_LENGTH as usize {
412+
if out.len() > Params::MAX_OUTPUT_LEN as usize {
413413
return Err(Error::OutputTooLong);
414414
}
415415

argon2/src/lib.rs

Lines changed: 27 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -113,53 +113,30 @@ use blake2::{digest, Blake2b, Digest};
113113

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

120120
/// Maximum password length in bytes.
121-
pub const MAX_PWD_LENGTH: usize = 0xFFFFFFFF;
122-
123-
/// Minimum and maximum associated data length in bytes.
124-
pub const MAX_AD_LENGTH: usize = 0xFFFFFFFF;
121+
pub const MAX_PWD_LEN: usize = 0xFFFFFFFF;
125122

126123
/// Minimum and maximum salt length in bytes.
127-
pub const MIN_SALT_LENGTH: usize = 8;
124+
pub const MIN_SALT_LEN: usize = 8;
128125

129126
/// Maximum salt length in bytes.
130-
pub const MAX_SALT_LENGTH: usize = 0xFFFFFFFF;
127+
pub const MAX_SALT_LEN: usize = 0xFFFFFFFF;
131128

132129
/// Maximum secret key length in bytes.
133-
pub const MAX_SECRET_LENGTH: usize = 0xFFFFFFFF;
130+
pub const MAX_SECRET_LEN: usize = 0xFFFFFFFF;
134131

135132
/// Argon2 context.
136133
///
137-
/// Holds the following Argon2 inputs:
138-
///
139-
/// - output array and its length,
140-
/// - password and its length,
141-
/// - salt and its length,
142-
/// - secret and its length,
143-
/// - associated data and its length,
144-
/// - number of passes, amount of used memory (in KBytes, can be rounded up a bit)
145-
/// - number of parallel threads that will be run.
146-
///
147-
/// All the parameters above affect the output hash value.
148-
/// Additionally, two function pointers can be provided to allocate and
149-
/// deallocate the memory (if NULL, memory will be allocated internally).
150-
/// Also, three flags indicate whether to erase password, secret as soon as they
151-
/// are pre-hashed (and thus not needed anymore), and the entire memory
152-
///
153-
/// Simplest situation: you have output array `out[8]`, password is stored in
154-
/// `pwd[32]`, salt is stored in `salt[16]`, you do not have keys nor associated
155-
/// data.
134+
/// This is the primary type of this crate's API, and contains the following:
156135
///
157-
/// You need to spend 1 GB of RAM and you run 5 passes of Argon2d with
158-
/// 4 parallel lanes.
159-
///
160-
/// You want to erase the password, but you're OK with last pass not being
161-
/// erased.
162-
// TODO(tarcieri): replace `Params`-related fields with an internally-stored struct
136+
/// - Argon2 [`Algorithm`] variant to be used
137+
/// - Argon2 [`Version`] to be used
138+
/// - Default set of [`Params`] to be used
139+
/// - (Optional) Secret key a.k.a. "pepper" to be used
163140
#[derive(Clone)]
164141
pub struct Argon2<'key> {
165142
/// Algorithm to use
@@ -199,7 +176,7 @@ impl<'key> Argon2<'key> {
199176
version: Version,
200177
params: Params,
201178
) -> Result<Self> {
202-
if MAX_SECRET_LENGTH < secret.len() {
179+
if MAX_SECRET_LEN < secret.len() {
203180
return Err(Error::SecretTooLong);
204181
}
205182

@@ -214,15 +191,9 @@ impl<'key> Argon2<'key> {
214191
/// Hash a password and associated parameters into the provided output buffer.
215192
#[cfg(feature = "alloc")]
216193
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
217-
pub fn hash_password_into(
218-
&self,
219-
pwd: &[u8],
220-
salt: &[u8],
221-
ad: &[u8],
222-
out: &mut [u8],
223-
) -> Result<()> {
194+
pub fn hash_password_into(&self, pwd: &[u8], salt: &[u8], out: &mut [u8]) -> Result<()> {
224195
let mut blocks = vec![Block::default(); self.params.block_count()];
225-
self.hash_password_into_with_memory(pwd, salt, ad, out, &mut blocks)
196+
self.hash_password_into_with_memory(pwd, salt, out, &mut blocks)
226197
}
227198

228199
/// Hash a password and associated parameters into the provided output buffer.
@@ -238,49 +209,33 @@ impl<'key> Argon2<'key> {
238209
&self,
239210
pwd: &[u8],
240211
salt: &[u8],
241-
ad: &[u8],
242212
out: &mut [u8],
243213
mut memory_blocks: impl AsMut<[Block]>,
244214
) -> Result<()> {
245215
// Validate output length
246-
if out.len()
247-
< self
248-
.params
249-
.output_len()
250-
.unwrap_or(Params::MIN_OUTPUT_LENGTH)
251-
{
216+
if out.len() < self.params.output_len().unwrap_or(Params::MIN_OUTPUT_LEN) {
252217
return Err(Error::OutputTooShort);
253218
}
254219

255-
if out.len()
256-
> self
257-
.params
258-
.output_len()
259-
.unwrap_or(Params::MAX_OUTPUT_LENGTH)
260-
{
220+
if out.len() > self.params.output_len().unwrap_or(Params::MAX_OUTPUT_LEN) {
261221
return Err(Error::OutputTooLong);
262222
}
263223

264-
if pwd.len() > MAX_PWD_LENGTH {
224+
if pwd.len() > MAX_PWD_LEN {
265225
return Err(Error::PwdTooLong);
266226
}
267227

268228
// Validate salt (required param)
269-
if salt.len() < MIN_SALT_LENGTH {
229+
if salt.len() < MIN_SALT_LEN {
270230
return Err(Error::SaltTooShort);
271231
}
272232

273-
if salt.len() > MAX_SALT_LENGTH {
233+
if salt.len() > MAX_SALT_LEN {
274234
return Err(Error::SaltTooLong);
275235
}
276236

277-
// Validate associated data (optional param)
278-
if ad.len() > MAX_AD_LENGTH {
279-
return Err(Error::AdTooLong);
280-
}
281-
282237
// Hashing all inputs
283-
let initial_hash = self.initial_hash(pwd, salt, ad, out);
238+
let initial_hash = self.initial_hash(pwd, salt, out);
284239

285240
let segment_length = self.params.segment_length();
286241
let block_count = self.params.block_count();
@@ -298,12 +253,11 @@ impl<'key> Argon2<'key> {
298253
&self.params
299254
}
300255

301-
/// Hashes all the inputs into `blockhash[PREHASH_DIGEST_LENGTH]`.
256+
/// Hashes all the inputs into `blockhash[PREHASH_DIGEST_LEN]`.
302257
pub(crate) fn initial_hash(
303258
&self,
304259
pwd: &[u8],
305260
salt: &[u8],
306-
ad: &[u8],
307261
out: &[u8],
308262
) -> digest::Output<Blake2b> {
309263
let mut digest = Blake2b::new();
@@ -325,8 +279,8 @@ impl<'key> Argon2<'key> {
325279
digest.update(0u32.to_le_bytes());
326280
}
327281

328-
digest.update(&(ad.len() as u32).to_le_bytes());
329-
digest.update(ad);
282+
digest.update(&(self.params.data().len() as u32).to_le_bytes());
283+
digest.update(self.params.data());
330284
digest.finalize()
331285
}
332286
}
@@ -348,22 +302,19 @@ impl PasswordHasher for Argon2<'_> {
348302
let salt = Salt::try_from(salt.as_ref())?;
349303
let mut salt_arr = [0u8; 64];
350304
let salt_bytes = salt.b64_decode(&mut salt_arr)?;
351-
352-
// TODO(tarcieri): support the `data` parameter (i.e. associated data)
353-
let ad = b"";
354305
let output_len = self
355306
.params
356307
.output_len()
357-
.unwrap_or(Params::DEFAULT_OUTPUT_LENGTH);
308+
.unwrap_or(Params::DEFAULT_OUTPUT_LEN);
358309

359310
let output = password_hash::Output::init_with(output_len, |out| {
360-
Ok(self.hash_password_into(password, salt_bytes, ad, out)?)
311+
Ok(self.hash_password_into(password, salt_bytes, out)?)
361312
})?;
362313

363314
Ok(PasswordHash {
364315
algorithm: self.algorithm.ident(),
365316
version: Some(self.version.into()),
366-
params: self.params.try_into()?,
317+
params: ParamsString::try_from(&self.params)?,
367318
salt: Some(salt),
368319
hash: Some(output),
369320
})
@@ -407,7 +358,7 @@ impl<'key> From<Params> for Argon2<'key> {
407358

408359
impl<'key> From<&Params> for Argon2<'key> {
409360
fn from(params: &Params) -> Self {
410-
Self::from(*params)
361+
Self::from(params.clone())
411362
}
412363
}
413364

0 commit comments

Comments
 (0)