[Crypto] AES GCM : is it acceptable to return the wrong plaintext if the tag is incorrect?

Solution 1:

There is an article* that answers the question in the negative for GCM and CCM. The article introduces the first formalization of the Releasing Unverified Plaintext (RUP) setting. The related security notion is the Ind-RUP.

The security question is can an adversary forge messages with unverified messages? In this game, confidentiality is not relevant, since in the game the adversary is accessing the plaintext of the chosen messages. AES-GCM fails to satisfies this.

  • How to Securely Release Unverified Plaintext in Authenticated Encryption by Elena Andreeva and Andrey Bogdanov and Atul Luykx and Bart Mennink and Nicky Mouha and Kan Yasuda

AE schemes such as GCM [34] and CCM [49] reduce to CTR mode in the RUP setting. This is because the adversary does not need to forge a ciphertext in order to obtain information about the corresponding (unverified) plaintext.

Your questions:

  1. It is very dangerous. AES-GCM internally uses CTR mode and bit flipping is very easy in the CTR mode. An attacker can easily change the ciphertext bit on their advantage. This is catastrophic if the structure of the message is known.

    One must never ignore the tag invalid!

    It is not about the leak of the key, CTR mode is IND-CPA secure. It is about changing the messages. This is why CTR mode can't have IND-CCA. GCM provides integrity and authentication and authenticated encryption > IND-CCAs

  2. For the second part you may benefit from is Q/A which provides a solution for the limited chunks for the GCM;

    • How to securely encrypt/decrypt data with a maximum chunk size?

Also for general guide

  • What are the rules for using AES-GCM correctly?, and
  • Deep into bounds Does GCM (or GHASH) only provide 64-bit security against forgeries?

*Thanks to Squeamish Ossifrage for pointing the article.

Solution 2:

how can we prevent the cipher from being returned in case the tag is wrong ? As far as I understand, to compute the tag the decryption process must be done entirely.

Actually, GCM decryption can be done in a two-step procedure:

  • Step 1: compute the expected GCM tag (which is a function of the ciphertext, AAD, teh secret H value, combined with the nonce and the key. Compare the expected GCM tag with the one that's included with the message (and fail if they're not the same)

  • Step 2: decrypt the message

Normally, steps 1 and 2 are done interleaved, but if someone monitoring the buffer is a concern, the above is a viable appproach.

Solution 3:

Is it acceptable to return the wrong plaintext if the tag is incorrect?

No. For one, it's against the spec quoted in question.

How bad is it to return the wrong plaintext anyway?

It's bad at least because if the AES-GCM API returned the wrong plaintext, then the software on top of the API might unwillingly use that wrong plaintext, just ignoring that it's wrong.

There is no risk to leak the key itself. But abusing such an invalid decryption API can let one encipher-and-authenticate arbitrary messages of their choice, which is typically not wanted.

Our AES implementation is written in C, how can we prevent the cipher from being returned in case the tag is wrong ?

Nothing beyond code obfuscation if adversaries can modify your code. If they can't, and can't inject fault, then it's enough to write correct code, and validate it. If there remains only the issue of fault injections, there are techniques to mitigate that, e.g. performing security checks twice at different times.

Solution 4:

There are several reasons for an authenticated decryption (with AES-GCM or any other AE or AEAD mechanism) not to return any plaintext if the ciphertext is not authentic (i.e if the tag does not match).

One danger is if the calling code starts using the partially decrypted plaintext. Suppose the caller does something with the beginning of the plaintext, then learns that the ciphertext was not authentic. The caller must then undo whatever was done, since this could be entirely spurious. Undoing what has been done is not always possible: if you've already sent a message over the network, you can't unsend it. If you've created a file, you can remove it, but traces of the data may remain in storage.

Furthermore, the way in which the code reacted to the partially decrypted plaintext may have indirect characteristics that are useful to an attacker. Side channels in the response to the partially decrypted plaintext may reveal parts of this plaintext to an external observer. This can happen with authentic plaintext, of course, but the situation with non-authentic plaintext is worse. If the plaintext is authentic, you know that it was created by a trusted party, and that usually means that it is well-formed in some sense. If the plaintext is not authentic, it may be ill-formed: it may contain numbers that are out of range, strings with invalid content, etc.

There are further problems if the entity that requests the decryption is doing it on behalf of a client. That is, suppose that Alice processes messages from clients, and the first thing the client does is submit an access token which is protected by (for example) AES-GCM. The first thing Alice does is to decrypt the token, and she reads that the token contains Bob's identity, so she starts reading the information she has on Bob and revealing some of it to the client (because after all why would you prevent Bob from retrieving information about himself?). But the token was actually produced by Eve, who can't create a valid token for Bob (because she doesn't have the key shared between Bob and Alice). And so Alice started to reveal Bob's information to Eve, which she will realize once she gets to the end of the ciphertext and learns that the token was not authentic.

To avoid these problems, you must not start processing the content of an authenticated-encrypted message until you have verified its authenticity. Ideally, this is enforced at the level of a cryptographic API, which either returns the authentic plaintext or a failure code. Sometimes, this is not possible, for example on an embedded system that must decrypt a message that doesn't fit in RAM. In such cases, the layer that processes the partially decrypted plaintext must not look inside the content: all it may do is store it temporarily in a secure place. Effectively, that layer becomes part of the cryptographic processing code, and it must take care not to leak unauthenticated plaintext to its caller.

If the job of the intermediate layer is to reveal the plaintext to an untrusted caller (for example, if you're writing code in a secure enclave), then the intermediate layer must take care not to reveal any unauthentic plaintext. If you do reveal unauthentic plaintext, you're allowing your caller to decrypt arbitrary data, and not just the messages that it may be entitled to.

If your intermediate layer doesn't have enough memory to store the not-yet-authenticated decrypted plaintext, one possible trick is to encrypt it on the fly with an unauthenticated cipher, then have it decrypted.

  1. Generate a single-use key K₁ for an unauthenticated cipher, for example AES-CTR.
  2. For each chunk of the ciphertext:
    1. Perform a chunk of authenticated decryption, putting the result in secure memory. (By “secure memory”, I mean memory that belongs to your intermediate layer, and that is not shared with your untrusted caller.)
    2. Encrypt the result with the stream cipher, putting the result in shared memory.
  3. Verify the authenticity of the authenticated ciphertext.
    • If it isn't authentic, erase K₁. The untrusted caller has data encrypted with K₁ but does not have K₁, so it can't do anything with that data.
    • If it is authentic, reveal K₁ to the caller. In practice, you might as well do the decryption (this is often preferable for performance since your layer is typically closer to any possible hardware acceleration, but sometimes the situation is the other way round if your code is running in a secure enclave which has less good performance than the untrusted caller). The caller may have modified the shared memory, but doing the decryption of modified data doesn't give the caller any advantage, since we're willing to reveal the key anyway.

Solution 5:

Just an additional point of information from the field. The NIST spec is strict in not allowing this, and it has good properties if an API does not do this. For example it protects against naive usage of the API which streams partial responses to some processing code which might execute commands or reveal timing information in side channels. However the biggest drawback is, that the data must be buffered completely by the decryption function. This either means you always have to provide large input buffers or the API has to allocate and double copy data. It also means larger segments become hard to process.

This issue becomes relatively apparent in the Java Crypto Extension: when you use the OpenJDK and formerly Oracle implementation which does the required buffering you are compliant but might have performance impact. If you use the BouncyCastle implementation you get a faster streaming API but you must be aware the authentication is only checked when reading the last byte.