Skip to content
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

hkdf: automatically switch between Hmac and SimpleHmac #96

Closed
wants to merge 2 commits into from
Closed
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
26 changes: 18 additions & 8 deletions 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 hkdf/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ blobby = "0.3"
hex-literal = "0.4"
sha1 = { version = "=0.11.0-pre.3", default-features = false }
sha2 = { version = "=0.11.0-pre.3", default-features = false }
blake2 = { version = "=0.11.0-pre.3", default-features = false }

[features]
std = ["hmac/std"]
Expand Down
90 changes: 25 additions & 65 deletions hkdf/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,25 +100,14 @@ extern crate std;

pub use hmac;

use core::fmt;
use core::marker::PhantomData;
use hmac::digest::{
array::typenum::Unsigned, crypto_common::AlgorithmName, Output, OutputSizeUser,
};
use hmac::{Hmac, SimpleHmac};
use hmac::digest::{array::typenum::Unsigned, FixedOutput, Output, OutputSizeUser, Update};

mod errors;
mod sealed;

pub use errors::{InvalidLength, InvalidPrkLength};

/// [`HkdfExtract`] variant which uses [`SimpleHmac`] for underlying HMAC
/// implementation.
pub type SimpleHkdfExtract<H> = HkdfExtract<H, SimpleHmac<H>>;
/// [`Hkdf`] variant which uses [`SimpleHmac`] for underlying HMAC
/// implementation.
pub type SimpleHkdf<H> = Hkdf<H, SimpleHmac<H>>;

/// Structure representing the streaming context of an HKDF-Extract operation
/// ```rust
/// # use hkdf::{Hkdf, HkdfExtract};
Expand All @@ -131,27 +120,19 @@ pub type SimpleHkdf<H> = Hkdf<H, SimpleHmac<H>>;
/// let (oneshot_res, _) = Hkdf::<Sha256>::extract(Some(b"mysalt"), b"hello world");
/// assert_eq!(streamed_res, oneshot_res);
/// ```
#[derive(Clone)]
pub struct HkdfExtract<H, I = Hmac<H>>
where
H: OutputSizeUser,
I: HmacImpl<H>,
{
hmac: I,
#[derive(Clone, Debug)]
pub struct HkdfExtract<H: HmacHash> {
hmac: H::FullHmac,
_pd: PhantomData<H>,
}

impl<H, I> HkdfExtract<H, I>
where
H: OutputSizeUser,
I: HmacImpl<H>,
{
impl<H: HmacHash> HkdfExtract<H> {
/// Initiates the HKDF-Extract context with the given optional salt
pub fn new(salt: Option<&[u8]>) -> Self {
let default_salt = Output::<H>::default();
let salt = salt.unwrap_or(&default_salt);
Self {
hmac: I::new_from_slice(salt),
hmac: H::new_full(salt),
_pd: PhantomData,
}
}
Expand All @@ -163,36 +144,25 @@ where

/// Completes the HKDF-Extract operation, returning both the generated pseudorandom key and
/// `Hkdf` struct for expanding.
pub fn finalize(self) -> (Output<H>, Hkdf<H, I>) {
let prk = self.hmac.finalize();
pub fn finalize(self) -> (Output<H>, Hkdf<H>) {
let prk = self.hmac.finalize_fixed();
let hkdf = Hkdf::from_prk(&prk).expect("PRK size is correct");
// Output size of HMAC is always equal to hash size
let prk = Output::<H>::clone_from_slice(&prk);
(prk, hkdf)
}
}

impl<H, I> fmt::Debug for HkdfExtract<H, I>
where
H: OutputSizeUser,
I: HmacImpl<H>,
I::Core: AlgorithmName,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("HkdfExtract<")?;
<I::Core as AlgorithmName>::write_alg_name(f)?;
f.write_str("> { ... }")
}
}

/// Structure representing the HKDF, capable of HKDF-Expand and HKDF-Extract operations.
/// Recommendations for the correct usage of the parameters can be found in the
/// [crate root](index.html#usage).
#[derive(Clone)]
pub struct Hkdf<H: OutputSizeUser, I: HmacImpl<H> = Hmac<H>> {
hmac: I::Core,
#[derive(Clone, Debug)]
pub struct Hkdf<H: HmacHash> {
hmac: H::CoreHmac,
_pd: PhantomData<H>,
}

impl<H: OutputSizeUser, I: HmacImpl<H>> Hkdf<H, I> {
impl<H: HmacHash> Hkdf<H> {
/// Convenience method for [`extract`][Hkdf::extract] when the generated
/// pseudorandom key can be ignored and only HKDF-Expand operation is needed. This is the most
/// common constructor.
Expand All @@ -209,7 +179,7 @@ impl<H: OutputSizeUser, I: HmacImpl<H>> Hkdf<H, I> {
return Err(InvalidPrkLength);
}
Ok(Self {
hmac: I::new_core(prk),
hmac: H::new_core(prk),
_pd: PhantomData,
})
}
Expand All @@ -230,18 +200,18 @@ impl<H: OutputSizeUser, I: HmacImpl<H>> Hkdf<H, I> {
info_components: &[&[u8]],
okm: &mut [u8],
) -> Result<(), InvalidLength> {
let mut prev: Option<Output<H>> = None;
let mut prev: Option<Output<H::FullHmac>> = None;

let chunk_len = <H as OutputSizeUser>::OutputSize::USIZE;
if okm.len() > chunk_len * 255 {
return Err(InvalidLength);
}

for (block_n, block) in okm.chunks_mut(chunk_len).enumerate() {
let mut hmac = I::from_core(&self.hmac);
let mut hmac = H::core_to_full(&self.hmac);

if let Some(ref prev) = prev {
hmac.update(prev)
hmac.update(prev);
};

// Feed in the info components in sequence. This is equivalent to feeding in the
Expand All @@ -252,7 +222,7 @@ impl<H: OutputSizeUser, I: HmacImpl<H>> Hkdf<H, I> {

hmac.update(&[block_n as u8 + 1]);

let output = hmac.finalize();
let output = hmac.finalize_fixed();

let block_len = block.len();
block.copy_from_slice(&output[..block_len]);
Expand All @@ -271,20 +241,10 @@ impl<H: OutputSizeUser, I: HmacImpl<H>> Hkdf<H, I> {
}
}

impl<H, I> fmt::Debug for Hkdf<H, I>
where
H: OutputSizeUser,
I: HmacImpl<H>,
I::Core: AlgorithmName,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Hkdf<")?;
<I::Core as AlgorithmName>::write_alg_name(f)?;
f.write_str("> { ... }")
}
}

/// Sealed trait implemented for [`Hmac`] and [`SimpleHmac`].
pub trait HmacImpl<H: OutputSizeUser>: sealed::Sealed<H> {}
/// Sealed trait implemented for hash functions
/// in the [RustCrypto/hashes] repository.
///
/// [RustCrypto/hashes]: https://github.com/RustCrypto/hashes
pub trait HmacHash: sealed::Sealed {}

impl<H: OutputSizeUser, T: sealed::Sealed<H>> HmacImpl<H> for T {}
impl<T: sealed::Sealed> HmacHash for T {}
95 changes: 36 additions & 59 deletions hkdf/src/sealed.rs
Original file line number Diff line number Diff line change
@@ -1,84 +1,61 @@
use hmac::digest::{
core_api::{BlockSizeUser, CoreWrapper, OutputSizeUser},
Digest, FixedOutput, KeyInit, Output, Update,
block_buffer::{BufferKind, Eager, Lazy},
core_api::{BlockSizeUser, BufferKindUser, CoreWrapper, OutputSizeUser},
Digest, FixedOutput, KeyInit, Update,
};
use hmac::{EagerHash, Hmac, HmacCore, SimpleHmac};

pub trait Sealed<H: OutputSizeUser> {
type Core: Clone;
static EXPECT_MSG: &str = "HMAC can take a key of any size";

fn new_from_slice(key: &[u8]) -> Self;
pub trait Sealed: OutputSizeUser + Sized {
type FullHmac: KeyInit + Update + FixedOutput;
type CoreHmac: KeyInit;

fn new_core(key: &[u8]) -> Self::Core;
fn core_to_full(core: &Self::CoreHmac) -> Self::FullHmac;

fn from_core(core: &Self::Core) -> Self;

fn update(&mut self, data: &[u8]);
fn new_core(key: &[u8]) -> Self::CoreHmac {
Self::CoreHmac::new_from_slice(key).expect(EXPECT_MSG)
}

fn finalize(self) -> Output<H>;
fn new_full(key: &[u8]) -> Self::FullHmac {
Self::FullHmac::new_from_slice(key).expect(EXPECT_MSG)
}
}

impl<H> Sealed<H> for Hmac<H>
impl<C, K> Sealed for CoreWrapper<C>
where
H: EagerHash + OutputSizeUser,
K: HmacKind<Self> + BufferKind,
C: BufferKindUser<BufferKind = K> + OutputSizeUser,
Comment on lines +25 to +28
Copy link
Member

Choose a reason for hiding this comment

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

Seems like this would preclude switching to newtypes of CoreWrapper for the hash crates.

Could this blanket impl be based around the requisite traits instead?

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't think it's possible to do automatically without specialization. Alternatively, we could introduce yet another trait which would regulate switching between those two HMAC implementations, i.e. we would move this burden to all hash implementation crates.

Copy link
Member

Choose a reason for hiding this comment

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

Then I would personally rather have newtypes than this automatic switching.

Have you benchmarked how much SimpleHmac actually degrades performance for the short inputs in HKDF usage?

Copy link
Member Author

Choose a reason for hiding this comment

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

We already have the SimpleHkdf type aliases, which do the job just fine.

It's more about structure size, depending on hash, SimpleHmac can be twice bigger, especially if the reset feature is enabled for hmac.

{
type Core = HmacCore<H>;

#[inline(always)]
fn new_from_slice(key: &[u8]) -> Self {
KeyInit::new_from_slice(key).expect("HMAC can take a key of any size")
}
type FullHmac = K::FullHmac;
type CoreHmac = K::CoreHmac;

#[inline(always)]
fn new_core(key: &[u8]) -> Self::Core {
HmacCore::new_from_slice(key).expect("HMAC can take a key of any size")
}

#[inline(always)]
fn from_core(core: &Self::Core) -> Self {
CoreWrapper::from_core(core.clone())
fn core_to_full(core: &Self::CoreHmac) -> Self::FullHmac {
K::core_to_full(core)
}
}

#[inline(always)]
fn update(&mut self, data: &[u8]) {
Update::update(self, data);
}
pub trait HmacKind<H> {
type FullHmac: OutputSizeUser + KeyInit + Update + FixedOutput;
type CoreHmac: KeyInit;

#[inline(always)]
fn finalize(self) -> Output<H> {
// Output<H> and Output<H::Core> are always equal to each other,
// but we can not prove it at type level
Output::<H>::clone_from_slice(&self.finalize_fixed())
}
fn core_to_full(core: &Self::CoreHmac) -> Self::FullHmac;
}

impl<H: Digest + BlockSizeUser + Clone> Sealed<H> for SimpleHmac<H> {
type Core = Self;
impl<H: EagerHash> HmacKind<H> for Eager {
type FullHmac = Hmac<H>;
type CoreHmac = HmacCore<H>;

#[inline(always)]
fn new_from_slice(key: &[u8]) -> Self {
KeyInit::new_from_slice(key).expect("HMAC can take a key of any size")
fn core_to_full(core: &Self::CoreHmac) -> Self::FullHmac {
CoreWrapper::from_core(core.clone())
}
}

#[inline(always)]
fn new_core(key: &[u8]) -> Self::Core {
KeyInit::new_from_slice(key).expect("HMAC can take a key of any size")
}
impl<H: Digest + Clone + BlockSizeUser> HmacKind<H> for Lazy {
type FullHmac = SimpleHmac<H>;
type CoreHmac = SimpleHmac<H>;

#[inline(always)]
fn from_core(core: &Self::Core) -> Self {
fn core_to_full(core: &Self::CoreHmac) -> Self::FullHmac {
core.clone()
}

#[inline(always)]
fn update(&mut self, data: &[u8]) {
Update::update(self, data);
}

#[inline(always)]
fn finalize(self) -> Output<H> {
// Output<H> and Output<H::Core> are always equal to each other,
// but we can not prove it at type level
Output::<H>::clone_from_slice(&self.finalize_fixed())
}
}
Loading
Loading