1
$\begingroup$

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.

$\endgroup$
5
  • $\begingroup$ 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? $\endgroup$
    – kelalaka
    Commented Dec 26, 2021 at 19:07
  • $\begingroup$ 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. $\endgroup$
    – eddydee123
    Commented Dec 26, 2021 at 20:19
  • $\begingroup$ In this case you may need DRBG so that a compromise in the current state doesn't leak pre and post stages. $\endgroup$
    – kelalaka
    Commented Dec 26, 2021 at 20:22
  • $\begingroup$ @kelalaka I'm confused - do you mean DRBG instead of HKDF? I do not follow the reasoning $\endgroup$
    – eddydee123
    Commented Dec 26, 2021 at 20:42
  • $\begingroup$ 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), ...)$ $\endgroup$
    – kelalaka
    Commented Dec 26, 2021 at 20:57

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.