I am trying to implement AES CTR encryption mode with HMAC authentication for messages.
It's encrypting and decrypting fine as long as the key length is 64 bytes, since AES key and HMAC key are being derived from this key.
Questions
- Is it safe to append IV or nonce to the encrypted messages?
- Is it safe to append HMAC digest to append to the messages?
- Can you review it for best security coding practices? I am using Pycryptodome
Code
import os
import zlib
from Crypto.Hash import HMAC
from Crypto.Hash import SHA256
from Crypto.Cipher import AES
from Crypto.Util import Counter
import zlib
def encrypt(full_key, plaintext):
if len(full_key) != 64:
raise Exception("FULL key length shall be equal to 64")
key = full_key[:len(full_key) //2]
# Use the last half as the HMAC key
hmac_key = full_key[len(full_key) // 2:]
if isinstance(plaintext, str):
plaintext = plaintext.encode()
compressed = zlib.compress(plaintext, 5)
print (f"compressed plaintext {compressed}")
# Choose a random, 16-byte IV.
iv = os.urandom(16)
# Convert the IV to a Python integer.
iv_int = int(binascii.hexlify(iv), 16)
# Create a new Counter object with IV = iv_int.
ctr = Counter.new(128, initial_value=iv_int)
# Create AES-CTR cipher.
aes = AES.new(key, AES.MODE_CTR, counter=ctr)
# Encrypt and return IV and ciphertext.
ciphertext = aes.encrypt(compressed)
hmac_obj = HMAC.new(hmac_key, compressed, SHA256)
mac = hmac_obj.digest()
return iv+ciphertext+mac
def decrypt(key, ciphertext):
# Initialize counter for decryption. iv should be the same as the output of
# encrypt().
if len(full_key) != 64:
raise Exception("FULL key length shall be equal to 64")
key = full_key[:len(full_key) //2]
# Use the last half as the HMAC key
hmac_key = full_key[len(full_key) // 2:]
mac_length = 32
iv_length = 16
iv = ciphertext[:16]
mac = ciphertext[-mac_length:]
_ciphertext = ciphertext[iv_length:-mac_length]
iv_int = int(iv.hex(), 16)
ctr = Counter.new(128, initial_value=iv_int)
# Create AES-CTR cipher.
aes = AES.new(key, AES.MODE_CTR, counter=ctr)
ciphertext = aes.decrypt(_ciphertext)
# Extract the MAC from the end of the file
hmac_obj = HMAC.new(hmac_key, ciphertext, SHA256)
computed_mac = hmac_obj.digest()
if computed_mac != mac:
raise Exception("Messege integrity violated")
plaintext= zlib.decompress(ciphertext)
# Decrypt and return the plaintext.
return plaintext