Both the ChaCha20 / Poly1305 construction and AES in GCM mode operate in basically the same manner. First the AAD is MAC-ed, then the ciphertext, resulting in the authentication tag. However, since the ciphertext itself is generated first, there is no requirement to verify the authentication tag before decryption. One can just perform the stream decryption without looking at the authentication tag whatsoever.
As for the implementation, there are several choices that can be made (focusing on the encryption part):
- performing verification and releasing the plaintext in a single method call;
- updating and buffering the plaintext, and releasing it during the final call that verifies the authentication tag;
- allowing online encryption / decryption property, releasing the plaintext before the authentication tag is verified.
Personally I'm in favor of at least offering option 3 to developers because it can be useful to directly stream plaintext to file instead of keeping it in memory. It could also allow developers to reuse the buffer of the ciphertext for the plaintext (and vice versa during encryption).
For similar reasons I would always treat the nonce, the AD, the ciphertext and tag as separate input values for this kind of lower level functionality. Some libraries such as Java include the authentication tag with the ciphertext (mainly for backward compatibility with modes that do not produce one, it seems), which makes buffering / resizing & reusing the arrays much more cumbersome. I've tried and succeeded in a 30% code decrease (and a much larger decrease in complexity due to symmetry between the encryption / decryption methods) if fully online encryption / decryption with a separate authentication tag is implemented.
It is of course dangerous as developers could use the plaintext before validation, so I'd generally issue a warning in the documentation about this. Cryptography has many pitfalls though, and I haven't seen anyone disappear in this particular pit. On the other hand, there was quite a lot of back-and-forth in the Java community about this, e.g. when coupled with
Option 1 could then be created as a convenience method that is also more secure. Of course, if you have option 2 or option 3 then it is relatively easy for a developer to create such a method themselves, and that's exactly what I would recommend you to do if you find that the functionality is missing.
Java is a bit special because the API basically offers incremental updates and a
final method where the authentication tag is verified. The authentication tag is present in the ciphertext. That means that it cannot perform fully online decryption, as the ciphertext received during the update methods may include the authentication tag. Therefore it will return plaintext from a "window" into the buffers that lags 16 bytes (in case the maximum tag size is used) from the supplied ciphertext.
This also means that the encryption and decryption of authenticated ciphers has no symmetry: encryption can be performed online, while the decryption lags behind. As indicated, this is probably because the developers wanted AEAD ciphers as a drop-in addition to existing cipher modes.
To show that you can decrypt GCM ciphertext without verifying the tag please have a look at my Java implementation here.