From 7cbe26af425c1486d9a020a857702b8aed864f86 Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Thu, 16 Jun 2022 17:48:39 +0200 Subject: [PATCH 1/6] Implement `Balloon::hash_into` --- balloon-hash/src/balloon.rs | 13 ++++++----- balloon-hash/src/lib.rs | 44 ++++++++++++++++++++++++++++++++----- 2 files changed, 47 insertions(+), 10 deletions(-) diff --git a/balloon-hash/src/balloon.rs b/balloon-hash/src/balloon.rs index e86bc570..7c59935f 100644 --- a/balloon-hash/src/balloon.rs +++ b/balloon-hash/src/balloon.rs @@ -28,12 +28,13 @@ pub fn balloon_m( secret: Option<&[u8]>, params: Params, memory_blocks: &mut [GenericArray], -) -> Result> + output: &mut GenericArray, +) -> Result<()> where GenericArray: ArrayDecoding, { #[cfg(not(feature = "parallel"))] - let output = { + let output_xor = { let mut output = GenericArray::<_, D::OutputSize>::default(); for thread in 1..=u64::from(params.p_cost.get()) { @@ -45,7 +46,7 @@ where }; #[cfg(feature = "parallel")] - let output = { + let output_xor = { use rayon::iter::{ParallelBridge, ParallelIterator}; if memory_blocks.len() < (params.s_cost.get() * params.p_cost.get()) as usize { @@ -76,8 +77,10 @@ where Digest::update(&mut digest, secret); } - Digest::update(&mut digest, output); - Ok(digest.finalize_reset()) + Digest::update(&mut digest, output_xor); + Digest::finalize_into(digest, output); + + Ok(()) } fn hash_internal( diff --git a/balloon-hash/src/lib.rs b/balloon-hash/src/lib.rs index f63e746b..1f2fa5b6 100644 --- a/balloon-hash/src/lib.rs +++ b/balloon-hash/src/lib.rs @@ -131,16 +131,30 @@ where #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] pub fn hash(&self, pwd: &[u8], salt: &[u8]) -> Result> { + let mut output = GenericArray::default(); + self.hash_into(pwd, salt, &mut output)?; + + Ok(output) + } + + /// Hash a password and associated parameters. + #[cfg(feature = "alloc")] + #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] + pub fn hash_into( + &self, + pwd: &[u8], + salt: &[u8], + output: &mut GenericArray, + ) -> Result<()> { #[cfg(not(feature = "parallel"))] let mut memory = alloc::vec![GenericArray::default(); self.params.s_cost.get() as usize]; #[cfg(feature = "parallel")] let mut memory = alloc::vec![GenericArray::default(); (self.params.s_cost.get() * self.params.p_cost.get()) as usize]; - #[cfg_attr(not(feature = "zeroize"), allow(clippy::let_and_return))] - let output = self.hash_with_memory(pwd, salt, &mut memory); + self.hash_into_with_memory(pwd, salt, &mut memory, output)?; #[cfg(feature = "zeroize")] memory.iter_mut().for_each(|block| block.zeroize()); - output + Ok(()) } /// Hash a password and associated parameters. @@ -159,12 +173,32 @@ where salt: &[u8], memory_blocks: &mut [GenericArray], ) -> Result> { + let mut output = GenericArray::default(); + self.hash_into_with_memory(pwd, salt, memory_blocks, &mut output)?; + + Ok(output) + } + + /// Hash a password and associated parameters into the provided `output` buffer. + /// + /// See [`Balloon::hash_with_memory`] for more details. + pub fn hash_into_with_memory( + &self, + pwd: &[u8], + salt: &[u8], + memory_blocks: &mut [GenericArray], + output: &mut GenericArray, + ) -> Result<()> { match self.algorithm { Algorithm::Balloon => { - balloon::balloon::(pwd, salt, self.secret, self.params, memory_blocks) + balloon::balloon::(pwd, salt, self.secret, self.params, memory_blocks).map( + |hash| { + output.copy_from_slice(&hash); + }, + ) } Algorithm::BalloonM => { - balloon::balloon_m::(pwd, salt, self.secret, self.params, memory_blocks) + balloon::balloon_m::(pwd, salt, self.secret, self.params, memory_blocks, output) } } } From 05fbac17a39b7f34c8fdba7f0bbbb20860ec9ca8 Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Thu, 16 Jun 2022 18:08:58 +0200 Subject: [PATCH 2/6] Change `output` type to `[u8]` --- balloon-hash/src/error.rs | 4 ++++ balloon-hash/src/lib.rs | 16 +++++++++------- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/balloon-hash/src/error.rs b/balloon-hash/src/error.rs index 771996e6..3161fe16 100644 --- a/balloon-hash/src/error.rs +++ b/balloon-hash/src/error.rs @@ -21,6 +21,8 @@ pub enum Error { ThreadsTooMany, /// Time cost is too small. TimeTooSmall, + /// Output size not correct. + OutputSize, } impl fmt::Display for Error { @@ -31,6 +33,7 @@ impl fmt::Display for Error { Error::ThreadsTooFew => "not enough threads", Error::ThreadsTooMany => "too many threads", Error::TimeTooSmall => "time cost is too small", + Error::OutputSize => "output size not correct", }) } } @@ -45,6 +48,7 @@ impl From for password_hash::Error { Error::ThreadsTooFew => InvalidValue::TooShort.param_error(), Error::ThreadsTooMany => InvalidValue::TooLong.param_error(), Error::TimeTooSmall => InvalidValue::TooShort.param_error(), + Error::OutputSize => password_hash::Error::OutputTooShort, } } } diff --git a/balloon-hash/src/lib.rs b/balloon-hash/src/lib.rs index 1f2fa5b6..9d781c41 100644 --- a/balloon-hash/src/lib.rs +++ b/balloon-hash/src/lib.rs @@ -84,6 +84,7 @@ pub use password_hash::{self, PasswordHash, PasswordHasher, PasswordVerifier}; use core::marker::PhantomData; use crypto_bigint::ArrayDecoding; use digest::generic_array::GenericArray; +use digest::typenum::Unsigned; use digest::{Digest, FixedOutputReset}; #[cfg(all(feature = "alloc", feature = "password-hash"))] @@ -140,12 +141,7 @@ where /// Hash a password and associated parameters. #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] - pub fn hash_into( - &self, - pwd: &[u8], - salt: &[u8], - output: &mut GenericArray, - ) -> Result<()> { + pub fn hash_into(&self, pwd: &[u8], salt: &[u8], output: &mut [u8]) -> Result<()> { #[cfg(not(feature = "parallel"))] let mut memory = alloc::vec![GenericArray::default(); self.params.s_cost.get() as usize]; #[cfg(feature = "parallel")] @@ -187,8 +183,14 @@ where pwd: &[u8], salt: &[u8], memory_blocks: &mut [GenericArray], - output: &mut GenericArray, + output: &mut [u8], ) -> Result<()> { + let output = if output.len() == D::OutputSize::USIZE { + GenericArray::from_mut_slice(output) + } else { + return Err(Error::OutputSize); + }; + match self.algorithm { Algorithm::Balloon => { balloon::balloon::(pwd, salt, self.secret, self.params, memory_blocks).map( From 2b22c59ee9378ec9e6b4e35e91a76394795da780 Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Thu, 16 Jun 2022 20:41:40 +0200 Subject: [PATCH 3/6] Make `Balloon::Error` `non_exhaustive` --- balloon-hash/src/error.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/balloon-hash/src/error.rs b/balloon-hash/src/error.rs index 3161fe16..4e22e48a 100644 --- a/balloon-hash/src/error.rs +++ b/balloon-hash/src/error.rs @@ -10,6 +10,7 @@ pub type Result = core::result::Result; /// Error type. #[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[non_exhaustive] pub enum Error { /// Algorithm identifier invalid. AlgorithmInvalid, From ef1f851c42b9346802e2b5d03a2be229947f7c6c Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Thu, 16 Jun 2022 20:43:56 +0200 Subject: [PATCH 4/6] Document `output` size requirements --- balloon-hash/src/lib.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/balloon-hash/src/lib.rs b/balloon-hash/src/lib.rs index 9d781c41..127c2917 100644 --- a/balloon-hash/src/lib.rs +++ b/balloon-hash/src/lib.rs @@ -139,6 +139,8 @@ where } /// Hash a password and associated parameters. + /// + /// The `output` has to have the same size as the hash output size: `D::OutputSize`. #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] pub fn hash_into(&self, pwd: &[u8], salt: &[u8], output: &mut [u8]) -> Result<()> { @@ -162,7 +164,7 @@ where /// to have it allocated for them. /// - `no_std` users on "heapless" targets can use an array of the [`GenericArray`] type /// to stack allocate this buffer. It needs a minimum size of `s_cost` or `s_cost * p_cost` - /// with the `parallel` feature enabled. + /// with the `parallel` crate feature enabled. pub fn hash_with_memory( &self, pwd: &[u8], @@ -177,6 +179,8 @@ where /// Hash a password and associated parameters into the provided `output` buffer. /// + /// The `output` has to have the same size as the hash output size: `D::OutputSize`. + /// /// See [`Balloon::hash_with_memory`] for more details. pub fn hash_into_with_memory( &self, From a1170c6fca8f4cfe1a2bf8beaa6cd6d4a1888d58 Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Fri, 17 Jun 2022 13:41:15 +0200 Subject: [PATCH 5/6] Correct error type --- balloon-hash/src/error.rs | 33 ++++++++++++++++++++++----------- balloon-hash/src/lib.rs | 5 ++++- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/balloon-hash/src/error.rs b/balloon-hash/src/error.rs index 4e22e48a..7532b8c4 100644 --- a/balloon-hash/src/error.rs +++ b/balloon-hash/src/error.rs @@ -3,7 +3,7 @@ use core::fmt; #[cfg(feature = "password-hash")] -use password_hash::errors::InvalidValue; +use ::{core::cmp::Ordering, password_hash::errors::InvalidValue}; /// Result with balloon's [`Error`] type. pub type Result = core::result::Result; @@ -23,19 +23,26 @@ pub enum Error { /// Time cost is too small. TimeTooSmall, /// Output size not correct. - OutputSize, + OutputSize { + /// Output size provided. + actual: usize, + /// Output size expected. + expected: usize, + }, } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match self { - Error::AlgorithmInvalid => "algorithm identifier invalid", - Error::MemoryTooLittle => "memory cost is too small", - Error::ThreadsTooFew => "not enough threads", - Error::ThreadsTooMany => "too many threads", - Error::TimeTooSmall => "time cost is too small", - Error::OutputSize => "output size not correct", - }) + match self { + Error::AlgorithmInvalid => f.write_str("algorithm identifier invalid"), + Error::MemoryTooLittle => f.write_str("memory cost is too small"), + Error::ThreadsTooFew => f.write_str("not enough threads"), + Error::ThreadsTooMany => f.write_str("too many threads"), + Error::TimeTooSmall => f.write_str("time cost is too small"), + Error::OutputSize { expected, .. } => { + write!(f, "unexpected output size, expected {} bytes", expected) + } + } } } @@ -49,7 +56,11 @@ impl From for password_hash::Error { Error::ThreadsTooFew => InvalidValue::TooShort.param_error(), Error::ThreadsTooMany => InvalidValue::TooLong.param_error(), Error::TimeTooSmall => InvalidValue::TooShort.param_error(), - Error::OutputSize => password_hash::Error::OutputTooShort, + Error::OutputSize { actual, expected } => match actual.cmp(&expected) { + Ordering::Less => password_hash::Error::OutputTooShort, + Ordering::Greater => password_hash::Error::OutputTooLong, + Ordering::Equal => unreachable!("unexpected correct output size"), + }, } } } diff --git a/balloon-hash/src/lib.rs b/balloon-hash/src/lib.rs index 127c2917..d607c0b8 100644 --- a/balloon-hash/src/lib.rs +++ b/balloon-hash/src/lib.rs @@ -192,7 +192,10 @@ where let output = if output.len() == D::OutputSize::USIZE { GenericArray::from_mut_slice(output) } else { - return Err(Error::OutputSize); + return Err(Error::OutputSize { + actual: output.len(), + expected: D::OutputSize::USIZE, + }); }; match self.algorithm { From 920019ea3447b7cd8a88dc6d35df85e6d6ce8113 Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Tue, 21 Jun 2022 12:20:29 +0200 Subject: [PATCH 6/6] Address review --- balloon-hash/src/error.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/balloon-hash/src/error.rs b/balloon-hash/src/error.rs index 7532b8c4..7c9d5675 100644 --- a/balloon-hash/src/error.rs +++ b/balloon-hash/src/error.rs @@ -3,7 +3,7 @@ use core::fmt; #[cfg(feature = "password-hash")] -use ::{core::cmp::Ordering, password_hash::errors::InvalidValue}; +use {core::cmp::Ordering, password_hash::errors::InvalidValue}; /// Result with balloon's [`Error`] type. pub type Result = core::result::Result; @@ -56,6 +56,7 @@ impl From for password_hash::Error { Error::ThreadsTooFew => InvalidValue::TooShort.param_error(), Error::ThreadsTooMany => InvalidValue::TooLong.param_error(), Error::TimeTooSmall => InvalidValue::TooShort.param_error(), + // TODO: Update after RustCrypto/traits#1026. Error::OutputSize { actual, expected } => match actual.cmp(&expected) { Ordering::Less => password_hash::Error::OutputTooShort, Ordering::Greater => password_hash::Error::OutputTooLong,