RustCrypto / hashes

Collection of cryptographic hash functions written in pure Rust
1.75k stars 238 forks source link

HMAC/HKDF Support for Blake2 #592

Closed conradludgate closed 3 weeks ago

conradludgate commented 3 weeks ago

WireGuard uses Blake2s as the hash function of choice for the NoiseIK protocol. NoiseIK then heavily suggests to use HKDF after all diffie-hellman exchanges. Thus, WireGuard uses HKDF-Blake2s.

https://www.wireguard.com/papers/wireguard.pdf

Screenshot 2024-06-15 at 18 25 57

Trying to perform a HKDF using Blake2s256 with the RustCrypto crates leads to this error.

error[E0599]: the function or associated item `from_prk` exists for struct `Hkdf<CoreWrapper<CtVariableCoreWrapper<Blake2sVarCore, UInt<UInt<UInt<UInt<UInt<UInt<UTerm, B1>, B0>, B0>, B0>, B0>, B0>>>>`, but its trait bounds were not satisfied
   --> src/lib.rs:100:58
    |
100 |                     let hkdf = hkdf::Hkdf::<Blake2s256>::from_prk(prk);
    |                                                          ^^^^^^^^ function or associated item cannot be called due to unsatisfied trait bounds
    |
   ::: /Users/conrad/.cargo/registry/src/index.crates.io-6f17d22bba15001f/digest-0.10.7/src/core_api/ct_variable.rs:25:1
    |
25  | pub struct CtVariableCoreWrapper<T, OutSize, O = NoOid>
    | ------------------------------------------------------- doesn't satisfy `<_ as BufferKindUser>::BufferKind = Eager`
    |
   ::: /Users/conrad/.cargo/registry/src/index.crates.io-6f17d22bba15001f/digest-0.10.7/src/core_api/wrapper.rs:24:1
    |
24  | pub struct CoreWrapper<T>
    | ------------------------- doesn't satisfy `_: HmacImpl<CoreWrapper<CtVariableCoreWrapper<Blake2sVarCore, UInt<UInt<UInt<UInt<UInt<UInt<UTerm, B1>, B0>, B0>, B0>, B0>, B0>>>>`
    |
note: if you're trying to build a new `Hkdf<CoreWrapper<CtVariableCoreWrapper<Blake2sVarCore, UInt<UInt<UInt<UInt<UInt<UInt<UTerm, B1>, B0>, B0>, B0>, B0>, B0>>>>` consider using one of the following associated functions:
      Hkdf::<H, I>::new
      Hkdf::<H, I>::from_prk
   --> /Users/conrad/.cargo/registry/src/index.crates.io-6f17d22bba15001f/hkdf-0.12.4/src/lib.rs:199:5
    |
199 |     pub fn new(salt: Option<&[u8]>, ikm: &[u8]) -> Self {
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...
206 |     pub fn from_prk(prk: &[u8]) -> Result<Self, InvalidPrkLength> {
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    = note: the following trait bounds were not satisfied:
            `<CtVariableCoreWrapper<Blake2sVarCore, UInt<UInt<UInt<UInt<UInt<UInt<UTerm, B1>, B0>, B0>, B0>, B0>, B0>> as BufferKindUser>::BufferKind = Eager`
            which is required by `CoreWrapper<HmacCore<CoreWrapper<CtVariableCoreWrapper<Blake2sVarCore, UInt<UInt<UInt<UInt<UInt<UInt<UTerm, B1>, B0>, B0>, B0>, B0>, B0>>>>>: HmacImpl<CoreWrapper<CtVariableCoreWrapper<Blake2sVarCore, UInt<UInt<UInt<UInt<UInt<UInt<UTerm, B1>, B0>, B0>, B0>, B0>, B0>>>>`

error[E0271]: type mismatch resolving `<CtVariableCoreWrapper<Blake2sVarCore, UInt<UInt<UInt<UInt<UInt<UInt<UTerm, B1>, B0>, ...>, ...>, ...>, ...>> as BufferKindUser>::BufferKind == Eager`
   --> src/lib.rs:100:32
    |
100 |                     let hkdf = hkdf::Hkdf::<Blake2s256>::from_prk(prk);
    |                                ^^^^^^^^^^^^^^^^^^^^^^^^ expected `Lazy`, found `Eager`
    |
    = note: required for `HmacCore<CoreWrapper<CtVariableCoreWrapper<Blake2sVarCore, UInt<UInt<UInt<UInt<UInt<UInt<UTerm, B1>, B0>, B0>, B0>, B0>, B0>>>>` to implement `BlockSizeUser`
newpavlov commented 3 weeks ago

You need to change HMAC provider from Hmac to SimpleHmac. You can do it by using this type: Hkdf<Blake2s256, SimpleHmac<Blake2s256>>.

conradludgate commented 3 weeks ago

Thanks, that seems to work. On a separate note, am I correct in thinking the algorithm defined in the paper is written as

let mut hkdf = SimpleHkdf::extract(Some(key), &input).1;
hkdf.expand(&[], out);

That's the only way that makes sense to me

tarcieri commented 3 weeks ago

@newpavlov perhaps hkdf should be changed to always use SimpleHmac and instead be generic around just the digest function rather than the Hmac implementation?

newpavlov commented 3 weeks ago

@tarcieri SimpleHmac is a bit suboptimal for "eager" hash functions like SHA-256. And HMAC/HKDF are overwhelmingly used with such functions. I would even say that the HMAC algorithm implicitly assumes that hash functions are "eager". Arguably, HMAC should not be used with hashes like BLAKE2, so it's somewhat unfortunate that Wireguard specified in such way.

It may be possible to somehow select between Hmac/SimpleHmac based on implementation of BufferKindUser, but it would require a bit of experimentation.

newpavlov commented 3 weeks ago

@conradludgate

am I correct in thinking the algorithm defined in the paper is written as

I think it will be more straightforward for you to use HMAC directly for implementing this without relying on the hkdf crate.

tarcieri commented 3 weeks ago

SimpleHmac is a bit suboptimal for "eager" hash functions like SHA-256

@newpavlov but for HKDF specifically, the inputs are typically short enough does it really matter versus the API ergonomics?

newpavlov commented 3 weeks ago

In most practical cases the ergonomics are fine. But we probably should make SimpleHkdf more noticeable in the docs. Also, before discussing potential change of defaults we should see whether it's possible to switch between HMAC providers automatically as mentioned above.