# How to correctly implement an archetypal encrypt-then-MAC AES-CTR + HMAC mode cipher?

I've created an AES implementation in Python as a learning experience (mainly for encrypting/decrypting files), and wanted to make sure that I haven't made any huge mistakes in my logic (of course, implementation is another story).

The block cipher mode of operation is CTR. The implementation supports AES-128, AES-192, and AES-256 (default).

The AES key and HMAC key are generated from a user password using hashlib's pbkdf2_hmac with SHA-256, and a random 16-byte salt (I create a 64-byte key and split it in half for each key; for AES-128, I create a 32-byte key and split it in half).

The CTR IV is created by a random 8 byte nonce and an 8 byte counter that starts at 0. The salt is written as the first block of the ciphertext, followed by the CTR IV as the second block.

Lastly, a 32-byte HMAC value of the ciphertext is created using the HMAC key and SHA-256 and written as the last two blocks of the ciphertext (32 bytes).

When decrypting, the HMAC value is checked against the rest of the ciphertext before decrypting.

Is there any issues with this logic? From a cryptographical point, where are the weak points?

Thank you!

You are describing encrypt-then-MAC using AES-CTR for encryption and HMAC for the MAC. This indeed results in authenticated encryption.

There may be better choices for the key derivation function, but I'm not very familiar with the options, so I'll let others comment. I don't understand why you salt the KDF when deriving the key. This just means that you will need to re-key for each ciphertext. In some ways, re-keying is better for security (maybe not here with low-entropy passwords seeding the key), but usually people like to avoid re-keying for the sake of performance.

The IV is only $$8 \text{ bytes} = 64 \text{ bits}$$, so you will start to expect collisions after encrypting $$2^{32} =$$ a few billion ciphertexts. This doesn't seem very good to me. You are devoting a full $$8 \text{ bytes} = 64 \text{ bits }$$ to the counter. Do you really need to support ciphertexts that are $$2^{64}$$ blocks long? A better balance between IV+counter length would be preferable. For example, with a 12-byte IV and 4-byte counter, you avoid IV collisions up to 2^48 and can support ciphertexts that are $$2^{32}$$ blocks long ( $$2^{32} \times 128 \text { bits } = 0.5 \text{ terabits} = 64 \text{ gigabytes} ).$$

• Not noted in the answer (yet) is that, you should generate/derive distinct (ideally unrelated) keys for AES-CTR and HMAC. Here, unrelated just means they differ significantly, rather than derived from unrelated source. – DannyNiu Dec 11 '20 at 6:35
• PBKDF2-HMAC, or any password-based Key Derivation Function based on pure iterative hashing, is inappropriate when facing powerful adversaries: there's no way software on general-purpose CPUs can compete with ASICs similar to this (dedicated to bitcoin), which hash at 15pj/SHA256 (\$1 buys the energy for$\gg2^{64}$hashes), or even GPUs. Blowfish was a step in the right direction, scrypt is fine, Argon2 is state-of-the-art. – fgrieu Dec 11 '20 at 9:50 • If the counter is only 4 bytes, you'd have to repeat the counter for any file greater than 64 GB and not 500GB – ItM Dec 12 '20 at 3:35 • Oops, I think I was thinking in bits, not bytes. – Mikero Dec 12 '20 at 16:23 • You missed one issue, added an answer. – Maarten Bodewes Jan 11 at 11:05 Lastly, a 32-byte HMAC value of the ciphertext is created using the HMAC key and SHA-256 and written as the last two blocks of the ciphertext (32 bytes). You really should include the IV in the calculation. Otherwise the authentication tag validates, and with a wrong IV it will still result in invalid plaintext. This is less of an issue with the salt, as a wrong salt will result in a wrong key, and then the HMAC authentication should fail. • The salt and IV are included in the HMAC calculation. That's what I was trying to clarify here: "The salt is written as the first block of the ciphertext, followed by the CTR IV as the second block.". Thank you! – ItM Jan 16 at 6:13 • If you make it to a full protocol specification I would try and write it down more formally and explicitly define the data that is to be used as input to HMAC, e.g.$M_H = Salt \| IV \| CT$and then$\text{HMAC}(M_H)\$. – Maarten Bodewes Jan 16 at 11:14