How Do We Defend Against Cipher Playbacks? Meet AEAD

Alice creates a secret message and ciphers it with her secret key, and then sends this to Bob. He also has the secret key, and so she…

Photo by Alexander Shatov on Unsplash

How Do We Defend Against Cipher Playbacks? Meet AEAD

Alice creates a secret message and ciphers it with her secret key, and then sends this to Bob. He also has the secret key, and so she decrypts it and reveals the secret message. It says “You can take tomorrow as a holiday”. Bob is happy and takes the holiday. Eve, though, has been listening to their communications, and, the next day, resends the ciphered message, even though she cannot read it. Bob takes the next day off, and Alice wonders why he is not at work? Eve has thus performed a replay attack on Alice’s ciphered message. So what we need is to bind the cipher to a network connection or a session, in order that Eve cannot recreate the same scenario.

With enhanced encryption methods, we can both authenticate the cipher and prove its integrity. This is known as Authenticated Encryption with Associated Data (AEAD). For this we provide additional data to authenticate the encryption process, and where we can identify where the ciphertext has been modified, and for it not to be decrypted. With most conventional AEAD methods we create a nonce value and add additional data (AD) that is authenticated but not encrypted. The additional data can include [here]:

addresses, ports, sequence numbers, protocol version numbers, and other fields that indicate how the plaintext or ciphertext should be handled, forwarded, or processed

In this way, we can bind network packets to the encrypted data, and provide integrity, so that an intruder cannot copy and paste valid ciphertext from other transmissions. For example, if we bind to a packet sequence number and the port, the authentication would fail for another sequence number or another port.

AES GCM converts the AES method into a stream cipher. It thus does not need padding and is faster than other modes. GCM also supports AEAD , and where we can add additional data into the cipher, and which can be used to authenticate the cipher. It does this by adding an authentication tag. The additional data might bind the cipher to a given network port or session. This means that an intruder cannot replay a cipher because they cannot create the same additional data. In this case we will generate a random 256-bit encryption key:

import os
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
import sys
import binascii


msg = "a message"
add = "additional data"

if (len(sys.argv)>1):
msg=str(sys.argv[1])

if (len(sys.argv)>2):
add=str(sys.argv[2])

print ("Data:\t",msg)
print ("Additional data:\t",add)

key = ChaCha20Poly1305.generate_key()
chacha = ChaCha20Poly1305(key)
nonce = os.urandom(12)
cipher = chacha.encrypt(nonce, msg.encode(), add.encode())
rtn=chacha.decrypt(nonce, cipher, add.encode())

print ("\nKey:\t",binascii.b2a_hex(key).decode())
print ("Nonce:\t",binascii.b2a_hex(nonce).decode())
print ("\nCipher:\t",binascii.b2a_hex(cipher).decode())
print ("Decrypted:\t",rtn.decode())

In this case, we have simply added the additional message of “additional data”, but we could have easily had taken a session number, a subject field header, so on. Overall the wider the range of additional data, the more difficult it will be for Eve to replicate it. A sample run is:

Data:    a message
Additional data: additional data

Key: cc959f846f440701bfdfa791b6a048901b0a20adcb0b61ec8f08ce6ae130c46f
Nonce: b529ed416f1ff9089b5f8c67

Cipher: d77ef575d5e163d11b4ecc30cd9be8b6d6c89ba9c0def0d981
Decrypted: a message

But, that is a random key, and we have the problem of Alice passing this to Bob. We could do this with public key encryption, and where Bob could pass his public key to Alice, and then Alice can encrypt the secret key for him. He can then decrypt with his private key. But Bob and Alice could use a secret passphrase for the generation of the key, and use it whenever they needed to recreate the key. For this, we can use a KDF (Key Derivation Function), and which takes a passphrase (and some salt) and generate an encryption key of a given size. One of the most popular for this is HKDF (HMAC Key Derivation Function). In this case, we will modify the program to generate the encryption key from the passphrase:

import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import sys
import binascii
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import hashes


msg = "a message"
add = "additional data"
password="qwerty"
length=32 # 256 bits


if (len(sys.argv)>1):
password=str(sys.argv[1])

if (len(sys.argv)>2):
msg=str(sys.argv[2])

if (len(sys.argv)>3):
add=str(sys.argv[3])


hkdf = HKDF(algorithm=hashes.SHA256(), length=length,salt=b"", info=b"")

mykey=hkdf.derive(password.encode())

print ("Password: ",password)
print ("Data:\t",msg)
print ("Additional data:\t",add)

# key = ChaCha20Poly1305.generate_key()
aesgcm = AESGCM(mykey)
nonce = os.urandom(12)
cipher = aesgcm.encrypt(nonce, msg.encode(), add.encode())
rtn=aesgcm.decrypt(nonce, cipher, add.encode())

print ("\nKey:\t",binascii.b2a_hex(mykey).decode())
print ("Nonce:\t",binascii.b2a_hex(nonce).decode())
print ("\nCipher:\t",binascii.b2a_hex(cipher).decode())
print ("Decrypted:\t",rtn.decode())

A sample run is:

Password:  qwerty
Data: a message
Additional data: additional data

Key: 697b0111081294978a075c6cae24729665be7f5a646007ac90aadc3954a484f8
Nonce: a3bf6cf2b981c84a2f07c99c

Cipher: 74a798cbd1025f1a20006109f47e13f0769684ca5be1a0736b
Decrypted: a message

Note that normally we would add a salt value (a nonce) to the key generation process. Now, if we add the incorrect data we will generate an exception:

import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import sys
import binascii
from cryptography.exceptions import InvalidTag
msg = "a message"
add = "additional data"
if (len(sys.argv)>1):
msg=str(sys.argv[1])
if (len(sys.argv)>2):
add=str(sys.argv[2])
print ("--- AES GCM with AEAD ---")
print ("Data:\t",msg)
print ("Additional data:\t",add)
key = AESGCM.generate_key(256)
aesgcm = AESGCM(key)
nonce = os.urandom(12)
cipher = aesgcm.encrypt(nonce, msg.encode(), add.encode())
add="Evil Eve is here"
print ("\nKey:\t",binascii.b2a_hex(key).decode())
print ("Nonce:\t",binascii.b2a_hex(nonce).decode())
print ("\nCipher:\t",binascii.b2a_hex(cipher).decode())
try:
rtn=aesgcm.decrypt(nonce, cipher, add.encode())
 print ("Decrypted:\t",rtn.decode())
except InvalidTag:
print ("Invalid Tag!!!")

A sample run gives:

--- AES GCM with AEAD ---
Data: a message
Additional data: additional data
Key:     bd277c6475c0c473ec2b672def46dff70cd71b6d0a8404f48db401c6e6452218
Nonce: 2b43c4b12ca69a43207a197b
Cipher:  15fecc8eaf9c0582f0ca65015c723fa39d53b94079417c05fc
Invalid Tag!!!

While we used AES GCM mode for AEAD in this case, we can also integrate AEAD with a range of ciphers, including ChaCha20:

and for light-weight cryptography methods: