I have been searching for resources on the internet about the efficiency of Encrypt then MAC, MAC then encrypt, MAC and encrypt, and hash then encrypt and I have come up short with all of them. Most sources will give what they will protect against but none will tell me what is more efficient in time and resource wise.
-
$\begingroup$ Our canonical Q/A on the comparison; Should we MAC-then-encrypt or encrypt-then-MAC? $\endgroup$– kelalakaCommented Jun 12, 2022 at 19:26
-
$\begingroup$ @kelalaka I would suggest not. 15 answers is just plain confusing (or is it that I'm easily confused?) My gut tells me that there's no difference, but it's difficult to fathom from that menagerie of answers. $\endgroup$– Paul UszakCommented Jun 13, 2022 at 15:13
-
$\begingroup$ And could someone please respectfully upvote the question? $\endgroup$– Paul UszakCommented Jun 13, 2022 at 15:24
2 Answers
Encrypt-then-MAC and MAC-then-encrypt are about as efficient. When implemented with a block cipher, both require the same number $2\lceil n/b\rceil+1$ of block encryptions, for $n$-bit message and $b$-bit block.
When efficiency matters, it should be used authenticated encryption with integrity thru a universal hash, like AES-GCM or ChaCha-Poly1305: because the universal hash is much faster than CBC-MAC, there's a significant cost saving, for the same functionality as MAC-then-encrypt.
You aren't finding much about performance because it's basically the same. There's interesting stuff to say about their relative security but not a lot to say beyond “basically the same”.
Setting aside hash-then-encrypt which mostly doesn't work, there are three methods. Given a plaintext P:
- Encrypt-then-MAC: encrypt the plaintext
C = E(P)
and append the MAC of the ciphertextT = M(E(P))
. In the other direction, givenC || T
, check thatT
is the MAC ofC
then decryptC
to findP
. - MAC-then-encrypt: calculate the MAC of the plaintext
T = M(P)
and encrypt the plaintext with the MAC appendedE(P || T)
. In the other direction, decrypt the ciphertext, split the result asP || T
and check thatT = M(P)
. - MAC-and-encrypt: encrypt the plaintext
C = E(P)
and append the MAC of the plaintextT = M(P)
. In the other direction, givenC || T
, decryptC
to findP
and check thatT
is the MAC ofP
.
In all cases, for a message of length n
, you need to calculate the MAC of n
bytes and the encryption or decryption of n
bytes. The difference is, at most, whether you need to encrypt/decrypt the MAC value. This is a constant cost (independent of the message length) and it's the same order of magnitude as other details of the algorithms, such as whether padding is involved, how the length of the message is encoded, how the key is prepared for use, etc. So there is no meaningful difference between the generic approaches.
Above I didn't take parallelization into account. It is attractive to do the authentication calculation and the encryption/decryption calculation in parallel if your hardware supports it. Taking for example encrypt-then-MAC, the input to the MAC is the output of encryption, so you can't do the two perfectly in parallel. On the contrary, MAC-and-encrypt allows the encryption and the MAC to be parallelized apart from the last step of encrypting the MAC. However, most primitives process the data one small block at a time from left to right, so as soon as encryption has processed one block, you can start the MAC operation with that block: thus encrypt-then-MAC can be parallelized as
- Encrypt the first block.
- MAC the first block while encrypting the second block.
- MAC the second block while encrypting the third block.
and so on. Thus the parallelization penalty is only one block regardless of the message length. Furthermore, the parallelization penalty is inverted for decryption. As a result, even when considering parallelization, the performance differences are tiny, and they're inverted between encryption and decryption, so once again the generic construction matters less for performance than the details of the algorithms.