4
$\begingroup$

I've a doubt about the Encrypt-then-MAC approach when using HMAC-SHA-256 with AES-128 in CBC mode, as seen in https://en.wikipedia.org/wiki/Authenticated_encryption.

I need to encode the resulting ciphertext in base 64 in order to send along with the HMAC. Should I generate the HMAC before or after encoding the ciphertext in base 64? Does it even matters?

For example: 1 - Encrypt-then-encode-then-MAC, or 2 - Encrypt-then-MAC-then-encode.

Thanks in advance.


EDIT: I'm using JSON to separate the message and the HMAC, like:

{
  "payload":"base64-encoded-ciphertext-goes-here",
  "hmac":"base64-encoded-HMAC-goes-here"
}
$\endgroup$

3 Answers 3

3
$\begingroup$

In the end it doesn't really matter. The encode step doesn't add anything to the security and doesn't take anything away.

The one benefit of encode-then-MAC is that you can check the MAC before doing the decode. If the MAC doesn't check out, you don't have to decode. If you do it the other way around, you would always have to decode the entire ciphertext, then check the MAC.

That said, Maarten brings up some important practical considerations that you should definitely take into consideration. The encoding before MAC means you have some expansion of the ciphertext that you are computing the MAC over, which adds overhead. There are also issues surrounding the encoding of the JSON, etc.

In the end, I think that Maarten's suggestion to MAC then encode is the better answer for the reasons he has specified.

$\endgroup$
6
  • $\begingroup$ Uh, and how would you send the tag? $\endgroup$
    – Maarten Bodewes
    Sep 5, 2018 at 18:56
  • $\begingroup$ @MaartenBodewes, I'm not sure what the actual requirement is. OP says they need to base64 encode the ciphertext in order to send along with the HMAC, but I don't understand why. Does that mean they can only send base64 encoded data? If that's the case, then you are right. $\endgroup$
    – mikeazo
    Sep 5, 2018 at 19:06
  • $\begingroup$ Given the update on how the OP intends to send the data, I think that what I've outlined here is complete. Either way will work, one way does give a potential performance improvement. $\endgroup$
    – mikeazo
    Sep 5, 2018 at 19:23
  • $\begingroup$ Sorry, but I voted this down: you would still first have to decode the tag, so not having to decode the ciphertext is not a security advantage. And you would still have to MAC 25% more ciphertext + overhead if you decide to MAC the base 64 instead of the binary. It is also semantically incorrect; the JSON in the question is just a JSON representation of the message; you would not expect the HMAC to be dependent on the encoding that is chosen. What if the JSON is UTF-16? Your tag is now dependent on the the binary and character encoding. $\endgroup$
    – Maarten Bodewes
    Sep 5, 2018 at 21:08
  • $\begingroup$ @maarten I see now. Thanks for the update on your answer. $\endgroup$
    – mikeazo
    Sep 6, 2018 at 11:09
6
$\begingroup$

Generally you would use encrypt-then-MAC before encoding. There is no need to process the expanded ciphertext before encoding, and you may be able to send the message both in binary and ASCII-armored with base 64.

Note that you would have to base 64 encode the authentication tag (the HMAC output) anyway if you would choose encrypt-then-encode-then-MAC, it would be encrypt-then-encode-then-MAC-then-encode.


Choosing the encrypt-then-MAC-then-encode does require you to:

  1. Establish where the tag is situated:

Generally this is performed by just appending the tag and then count 32 bytes backwards (assuming the entire 256 bit output is used for the MAC, the default) or to include a length encoding for the ciphertext. However, a full ASN.1 structure could also be defined to create a so called container format.

  1. Make sure that your base 64 decoder is not vulnerable against attack:

Because the base 64 decoding will now happen before the message is authenticated (but this is also the case if the tag is base 64 encoded separately, so yeah, make sure your base 64 codec is secure).


If you decide on using a container format (CMS or your own container format) then please note that attacking the format may expose security issues before the authentication can be verified. Windows famously had such vulnerabilities before user authentication took place. But many crypto related libraries such as OpenSSL and NSS have suffered similar issues.


EDIT: On Jason

You've specified JSON as message format. That's fine, but please note that JSON doesn't have an explicit way to encode binary values. Therefore you would have yourself to define the exact encoding of the binary message in payload, e.g. base 64 as defined here. Otherwise you may run into interop issues (base64url, padding or no padding, line breaks or no line breaks at specific locations, etc.

Furthermore, HMAC takes binary data as input. So that would mean that you'd have to specify the character encoding as well, at least for calculation of the authentication tag. JSON defaults to UTF-8 (which is compatible with ASCII for base 64 encoded values) but it also allows UTF-16 and UTF-32. Note that the encoding of the JSON and the encoding for the HMAC input could differ.

Because of these kind of issues, it is infinitely easier to just calculate the value over the binary value. You don't want to end up with the XML-digsig standard. Of course you'd still have to define the character set for the JSON message and base 64 encoding for the values within it, but at least a single bit wrong won't break your implementation.

$\endgroup$
3
$\begingroup$

Always operate on raw bytes, do not operate on encoded strings. Cryptographic algorithms do not define how data is encoded (the vast majority assume raw bytes). It is up to the protocol to establish how data should be encoded.

A simply example of how you might implement your example in psuedo-python - def enc_and_mac(m): c = encrypt(m) h = hmac(c) data = { 'payload': b64encode(c), 'hmac': b64encode(h) }

Then you would validate and decrypt via: def val_and_dec(data): c = b64decode(data['payload']) h = b64decode(data['hmac']) if const_compare(hmac(c), h) is True: m = decrypt(c)

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.