i would like to implement a custom Encrypt than HMAC scheme instead of using AES-GCM can anyone share resource to do it correctly . are there any common pitfalls i should be aware of for secure implementation ( i browsed internet but got shady sites like medium.com , i couldn't trust hence asking here , sorry for poor english)
If you want to support associated data so the interface is the same as any AEAD, I started writing an Internet Draft called Encrypt-then-MAC for Committing AEAD (cAEAD), which can be found here.
That can be followed exactly and is what I'd recommend because it's the most flexible. It's then easy to transfer to a different AEAD in the future and encrypt data in chunks.
Otherwise, here's a summary for regular Encrypt-then-MAC without associated data:
- Use a 256-bit tag. A 128-bit tag isn't collision resistant, meaning the construction wouldn't be committing, so there would be no benefit over AES-GCM. Anything greater than 256 bits is unnecessary and creates more storage overhead. Truncation to 256 bits is fine.
- Derive a separate 256-bit encryption key and 256-bit MAC key using a KDF with the same input keying material. This is also required for commitment. You can either retrieve a larger output (512 bits) and split it in half, or you can do two calls with different context information. There's no need to use keys larger than 256 bits, and 128-bit keys should be avoided due to multi-target attacks/a security reduction.
- Authenticate the IV/nonce (e.g. by prepending it to the ciphertext before computing the MAC over everything).
- Authenticate headers (e.g. version numbers) to detect tampering. If in doubt, authenticate everything.
- You should be aware of canonicalization attacks when computing a MAC over multiple variable length inputs concatenated together. The best solution is to have a fixed format/use fixed length inputs. Otherwise, you should include the length of each concatenated input in the MAC calculation. This is somewhat explained in the draft (the little-endian encoding).
- For decryption, the recomputed tag must be compared with the stored tag in constant time to avoid leaking information.
- If the tags don't match, don't decrypt anything, just return an error.
- Zero the encryption key and MAC key from memory when you're done using them if possible.
This non-AEAD approach typically means you get one tag over the entire ciphertext rather than encrypting in chunks and getting multiple tags. Therefore, you ideally want a MAC that works well with large inputs, like BLAKE3.
If this sounds complicated, consider using a library like Tink that offers Encrypt-then-MAC implementations.