Openssl: Advanced BLAKE2 parameters support advice

Created on 14 Nov 2018  路  4Comments  路  Source: openssl/openssl

In #980 it was requested that the blake2 interface was made more flexible to allow users to provide custom parameters such as digest length, hash key, salt and personalization. At the time it looked like a large effort was required to work around the limitations of the EVP_MD API but with the recent work by @levitte on the EVP_MAC API in #7393 this seems worth revisiting.

There is some backend implementation work required to support the key and digest length parameters but most of the issues seem to revolve around exposing these parameters through the EVP interface
and passing them around.

I would be happy to work on this, but since I don't have much experience designing APIs or working on EVP I need some guidance:

The value of blake2 parameters is stored in a BLAKE2_PARAM struct that is part of the actual algorithm (the block is hashed with the IV to initialize the hashing state). Currently the structure is internal to the blake2 init method so there is no way to write setter functions.
All blake2 methods already receive a BLAKE2_CTX parameter, is it acceptable to store custom parameters there or should modifying this structure be avoided? Are there existing EVP apis to access it? (it is stored as _md_data_ in EVP_MD_CTX).

Since using blake2 as a keyed hash makes it essentially equivalent to a MAC, would it make more sense to expose this parameter as part of the EVP_MAC api? Is this what users expect or does it make the api more unintuitive? How then would we allow users to modify the key and other digest parameters at the same time?

7597 uses EVP_MAC_ctrl() to control all instance specific parameters for KMACs, is this a preferred method?

Other non-tree parameters seem easier to handle:

  • Supporting the salt and personalization parameters only requires setting a fixed size byte field in the param block.
  • The digest_length parameter must be set in the param block and somehow passed to the blake2_final method. The EVP_MD returned by EVP_blake2() already hardcodes the digest length, would that be an issue?

Most helpful comment

@paulidale, I'd just like to point out that RFC 7693 is titled "The BLAKE2 Cryptographic Hash and Message Authentication Code (MAC)", so I would say that an EVP_MAC implementation certainly is suitable, even though not the only way to use the algorithm... so rather than "move to", I'd rather see it as "also make available as".

The reference implementation has a second init function for keyed hashing, blake2b_init_key (with certain variance in the name depending on BLAKE2 variant), which would be suitable to import for EVP_MAC purposes. Note that the reference implementation is licensed with the OpenSSL license as well, so it's no problem copying that code.

Something to be noted is that the init functions in the reference implementation take an output size by parameter. Why this was omitted when importing to OpenSSL, I do not know. I suggest adding it back and using it.

As for salt and personalization, I really don't know. My first instinct would be to create setter functions for a parameter block, and pass said parameter block by reference to the BLAKE2 init function instead of it being internal to that function. Sounds reasonable? I'd be firmly against passing parameters through the context structure, as that one is supposed to have the running state of an ongoing digest, and should stay that way (I can see future code becoming confusing if the context structure serves two roles).

For the EVP_MD implementations, I would add _ctrl functions to control the salt and personalization parameters.
For the EVP_MAC implementations, I would add the same controls, and additionally have controls to set the key.

Does this make sense?

All 4 comments

I can answer some of the questions. The _ctrl() functions are the preferred method for algorithm specific parameters. Storing parameters in the BLAKE2_CTX seems like the way to go. I'd cache the various settings in the _CTX and let the _init_ call set everything up -- i.e. between the _new_ and the _init_, the parameters get set.

I'm not sure if this should move to the EVP_MAC, blake2 is generally considered to be a digest even though it can act as a MAC.

@paulidale, I'd just like to point out that RFC 7693 is titled "The BLAKE2 Cryptographic Hash and Message Authentication Code (MAC)", so I would say that an EVP_MAC implementation certainly is suitable, even though not the only way to use the algorithm... so rather than "move to", I'd rather see it as "also make available as".

The reference implementation has a second init function for keyed hashing, blake2b_init_key (with certain variance in the name depending on BLAKE2 variant), which would be suitable to import for EVP_MAC purposes. Note that the reference implementation is licensed with the OpenSSL license as well, so it's no problem copying that code.

Something to be noted is that the init functions in the reference implementation take an output size by parameter. Why this was omitted when importing to OpenSSL, I do not know. I suggest adding it back and using it.

As for salt and personalization, I really don't know. My first instinct would be to create setter functions for a parameter block, and pass said parameter block by reference to the BLAKE2 init function instead of it being internal to that function. Sounds reasonable? I'd be firmly against passing parameters through the context structure, as that one is supposed to have the running state of an ongoing digest, and should stay that way (I can see future code becoming confusing if the context structure serves two roles).

For the EVP_MD implementations, I would add _ctrl functions to control the salt and personalization parameters.
For the EVP_MAC implementations, I would add the same controls, and additionally have controls to set the key.

Does this make sense?

I'm fine with _both_ MD and MAC implementations for this.

I have made some progress on the backend implementation and the EVP_MAC interface, however dealing with the EVP_MD API has been mostly frustrating so far and I have not yet been able to find a way to expose these parameters in a sane way.

It looks like the root of the issue has to do with some differences around context allocation and initialization between EVP_MAC and EVP_MD:

  • In both cases the context holds a pointer to a method structure and another to instance specific data
  • _EVP_MAC_CTX_new()_ allocates memory for the context and the instance data (this is delegated to _EVP_MAC::new()_)
  • _EVP_MAC_init()_ is a one-line redirect to _EVP_MAC::init()_ with the given instance data
  • There is no _EVP_MD::new()_ equivalent in the EVP_MD API so _EVP_MC_CTX_new()_ only allocates the context structure, not the instance specific data
  • The data is zero-allocated by _EVP_DigestInit_ex()_ instead, using _EVP_MD::ctx_size_ to control the allocation size
  • _EVP_DigestInit_ex()_ then calls _EVP_MD::init()_ with the given context

This essentially means it is possible to use _EVP_MAC_ctrl()_ to set custom parameters on a MAC implementation between the new() and init() calls, however this is not true for MD implementations because all the initialization logic happens in _EVP_DigestInit_ex()_. If I am not mistaken this makes it impossible to supply user-defined parameters before calling the Init method.

I noticed a possible workaround with the _EVP_MD_CTX_FLAG_NO_INIT_ flag but this seems like an overly complicated solution. Am I missing anything here or will this require substantial changes in the EVP_MD internals?

Was this page helpful?
0 / 5 - 0 ratings