Skip to content

feat(hmac): Add generate_with_length method to hmac::Key #1218

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
58 changes: 58 additions & 0 deletions src/hmac.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
//! https://github.com/briansmith/ring/blob/main/src/hkdf.rs

use crate::{constant_time, digest, error, hkdf, rand};
use core::num::NonZeroUsize;

/// An HMAC algorithm.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
Expand Down Expand Up @@ -178,6 +179,26 @@ impl Key {
Self::construct(algorithm, |buf| rng.fill(buf))
}

/// Generate an HMAC singing key using a custom length for the given
/// digest algorithm.
///
/// The key will be `length` bytes long.
#[cfg(feature = "alloc")]
pub fn generate_with_length(
algorithm: Algorithm,
length: NonZeroUsize,
rng: &dyn rand::SecureRandom,
) -> Result<Self, error::Unspecified> {
let key_size = length.get();
let mut key_bytes = [0; digest::MAX_OUTPUT_LEN].to_vec();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the long run, we really should avoid heap allocation here, especially just to support huge key sizes. This makes the function much harder to implement. OTOH, if the caller is willing to do a heap allocation then you can just construct a random array using ring::rand and then call ::new(algorithm, ...) instead.

if key_size > digest::MAX_OUTPUT_LEN {
key_bytes.resize(key_size, 0);
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why we would truncate the length of the key to MAX_OUTPUT_LEN. The user asked for a key of length length and so I guess they should get a key of that length; i.e. without this truncation. Or if there's no need for such large keys then we'd return an error. But I don't think we'd ever silently truncate the key length.

let key_bytes = &mut key_bytes[..key_size];
rng.fill(key_bytes)?;
Ok(Self::new(algorithm, key_bytes))
}

fn construct<F>(algorithm: Algorithm, fill: F) -> Result<Self, error::Unspecified>
where
F: FnOnce(&mut [u8]) -> Result<(), error::Unspecified>,
Expand Down Expand Up @@ -342,6 +363,7 @@ pub fn verify(key: &Key, data: &[u8], tag: &[u8]) -> Result<(), error::Unspecifi
#[cfg(test)]
mod tests {
use crate::{hmac, rand};
use core::num::NonZeroUsize;

// Make sure that `Key::generate` and `verify_with_own_key` aren't
// completely wacky.
Expand All @@ -364,4 +386,40 @@ mod tests {
assert!(hmac::verify(&key, HELLO_WORLD_BAD, tag.as_ref()).is_err())
}
}

#[test]
pub fn hmac_generate_with_length() {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests of the public HMAC API should go in hmac_tests.rs.

I will write a test for the original generate() that you can emulate.

let rng = rand::SystemRandom::new();

for algorithm in &[
hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY,
hmac::HMAC_SHA256,
hmac::HMAC_SHA384,
hmac::HMAC_SHA512,
] {
let output_len = algorithm.0.output_len;
let block_len = algorithm.0.block_len;

for length in &[
1,
2,
output_len - 1,
output_len,
output_len + 1,
block_len - 1,
block_len,
block_len + 1,
(block_len * 2) - 1,
block_len * 2,
(block_len * 2) + 1,
] {
let _key = hmac::Key::generate_with_length(
*algorithm,
NonZeroUsize::new(*length).unwrap(),
&rng,
)
.unwrap();
Comment on lines +416 to +421
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how to assert the key length with the expected one. 🤔

}
}
}
}