Score:1

How to compose (H)KDF, Encryption and (H)MAC

mk flag

For legacy reasons one of my systems doesn't have the option of using an AEAD mode, we are restricted to AES in plain CBC or CTR mode plus a MAC.

A typical task is to transfer data from one node to another while guaranteeing integrity and confidentiality. I find myself repeatedly specifying the following composition:

  • CSPRNG to generate a bootstrap secret
  • KDF to derive keys for encryption and MAC - I use HKDF
  • CSPRNG again the get an iv
  • CTR mode to encrypt the data
  • MAC over bootstrap secret, iv, cipherspec and ciphertext - I use HMAC

So I am doing Encrypt-then-MAC and I am authenticating all the inputs to the ciphertext calculation. But I have kind of blithely assumed this composition is secure. I've never actually seen this full composition including the KDF described as a reusable primitive.

TLS does something very similar, but it's not quite the same (e.g. it uses HKDF differently).

IES schemes like ECIES and DLIES look conceptually similar, but differ in the details, especially in the way the inputs to the KDF are derived.

So my question is: is this problem insufficiently general to warrant a cookbook solution? Or maybe something already exists that I've overlooked? Otherwise how can I gain confidence in the solution? (When it comes to crypto I'm always cautious).

In case the details are useful, the flow is:

The sending node performs the following:

  1. Obtain 256 secret bits seed from a CSPRNG
  2. Encrypt seed for the other node using its public key as encrypted_seed
  3. Split seed into 128 bits salt and 128 bits key_material
  4. Derive 384 secret bits by calling HKDF-HMAC-SHA-256(length=384b, ikm=key_material, salt=salt, info=<source node id || dest node id>)
  5. Split up into 128 bit encryption_key, 256 bit HMAC_key

For each message to be sent:

  1. Obtain 128 bits from a CSPRNG as encryption_iv
  2. Encrypt the plaintext using AES-128-CTR(iv=encryption_iv, key=encryption_key)
  3. Calculate tag as HMAC-SHA-256(key=HMAC_key, data=encrypted_seed || encryption_iv || cipherspec=AES-128-CTR || ciphertext)
  4. Send to the other node: encrypted_seed || encryption_iv || cipherspec || ciphertext || tag

The receiving node performs the following:

  1. Parse received message into its components encrypted_seed etc.
  2. Decrypt encrypted_seed using the receiving node's private key, obtaining seed
  3. Split seed into 128 bits salt and 128 bits key_material
  4. Derive 384 secret bits by calling HKDF-HMAC-SHA-256(length=384b, ikm=key_material, salt=salt, info=<source node id || dest node id>)
  5. Split up into 128 bit encryption_key, 256 bit HMAC_key
  6. Calculate tag as HMAC-SHA-256(key=HMAC_key, data=<message as received from sending node without tag>)
  7. Assign tag_valid := true if tag matches the one on the received message, false otherwise
  8. Assign k := encryption_key if tag_valid, otherwise assign k := <some random constant>
  9. Decrypt the ciphertext as [cipherspec](iv=encryption_iv, key=k)
  10. Output a tuple (tag_valid, plaintext) - the caller is responsible to check tag_valid before using plaintext

So what could go wrong? Well, for one the seed value is used before the MAC tag is checked. I could sign it using the sending node's private key, but that doesn't actually bind seed to the MAC tag. Also this is starting to get messy, hence my uneasiness.

kelalaka avatar
in flag
If you have a reliable public key system, why don't you just generate 512-bit uniform random key material and use is 256-bit AES-256 and 256-bit for HMAC and send via public-key mechanism to simplify the protocol?
eddydee123 avatar
mk flag
Indeed I simplified the actual situation - in reality there are more keys for other purposes that need to be sent. That's why I am sending a KDF seed instead of the keys themselves. My question attempts to generalize to a generic composition of KDF+Enc+MAC.
kelalaka avatar
in flag
In this case you may need DRBG so that a compromise in the current state doesn't leak pre and post stages.
eddydee123 avatar
mk flag
@kelalaka I'm confused - do you mean DRBG instead of HKDF? I do not follow the reasoning
kelalaka avatar
in flag
Not exactly, depending on your use case of the seed, you may need call it to not compromise anything at all, e.g. $seed_1 = DRGB(seed_0),HKDF(seed_1), seed_2 = DRGB(seed_1),HKDF(seed_2), ...)$
mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.