Everything You Ever Wanted To Know About Authenticated Encryption With Additional Data (AEAD), But…

Meet AES GCM, ChaCha20/Poly1305, AES CCM, AES SIV and AES OCB3

Everything You Ever Wanted To Know About Authenticated Encryption With Additional Data (AEAD), But Were Afraid to Ask

Meet AES GCM, ChaCha20/Poly1305, AES CCM, AES SIV and AES OCB3

Don’t you just hate all of those pesky acronyms in cybersecurity? Well, let’s explain a few … AES GCM, ChaCha20/Poly1305, AES CCM, AES SIV and AES OCB3, all of which can considerably enhance the security of your system, and stop Eve from playing with your secret and trusted information. In this case, we will investigate the main methods used to create AEAD: SIV (Synthetic Initialization Vector), CCM (counter with cipher block chaining message authentication code; counter with CBC-MAC), GCM (Galois/Counter Mode), ChaCha20 (Daniel J. Bernstein’s love of dancing) and SOCB3.

Security by design

Alice creates a secret message and ciphers it with her secret key, and then sends this to Bob. He also has the secret key, so he 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, 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.

The main methods we can use for AEAD are AES GCM, AES SIV, AES CCM, ChaCha20/Poly1305, and AES OCB3. These will be explained in the following sections.

AES GCM — The Fast Stream Cipher

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, 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.

from cryptography.hazmat.primitives.ciphers.aead import AESGCM

key = AESGCM.generate_key(bit_length=keysize)
cipher = AESGCM(key)
ct = cipher.encrypt(nonce,message.encode(), add.encode())
rtn=cipher.decrypt(nonce,ct, add.encode())

AES SIV — A Nonceless Approach and Nonce Reuse Protector

AES-SIV is now standardized with RFC 5297. With enhanced encryption methods, we can both authenticate the cipher and prove its integrity. With most conventional AEAD methods we create a nonce value and add additional data (AD) that is authenticated but not encrypted. With a nonce-less approach, we can use a key wrapping method, which has often been used to protect encryption keys.

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.

Within SIV, too, we can have multiple sources of this additional data (known as a vector of strings). This information could be spread across multiple sources and is likely to be difficult for an intruder to capture and replicate. This is one of the special features of SIV and allows multiple strings to be used for the authentication, rather than having to merge these into a single string for additional information:

from cryptography.hazmat.primitives.ciphers.aead import AESSIV 

key = AESSIV.generate_key(bit_length=2*keysize)
cipher = AESSIV(key)
aad = [add.encode(), nonce]
ct = cipher.encrypt(message.encode(), aad)
rtn=cipher.decrypt(ct, aad)

In this case, we see that aad is a list of vectors that we can use to authenticate the data. This could be a list of strings, such as username, session ID, domain string, and so on.

Traditional AEAD methods can be weak in terms of nonce reuse and also nonce misuse. In many systems, we use a non-deterministic method of generating a nonce, so there is no way of knowing if previously generated values have not been used before. Another weakness is where a nonce can be “played-back” by rolling back a virtual machine and discovering the nonce value used. Thus most AEAD methods are secure with a unique nonce, but where there is no guarantee of security if the nonce is not unique. As the nonce value is limited in size, there will always be a chance to reuse it with the same key, and thus we might have to change our keys on a regular basis.

SIV provides more protection for nonce reuse/misuse, where an attacker can only determine that a given plaintext value and a given set of associated data were protected using the defined key and nonce value.

AES CCM

With CBC (Cipher Block Chaining)-MAC (Message Authentication Code) we authenticate messages with a secret shared key. If Bob wants to send some text to Al-ice, he encrypts it with a shared key and then sends Alice the message digest (or hash of the message) of this. Alice does the same with the secret key and compares the hash that she gets. If they are the same, then Bob has continued to prove his identity (as only he can have the secret key that Bob and Alice share), and that the message has not been changed.

Overall we encrypted the message with the standard form of AES and then throw away everything apart from the last block, and use this as a fixed-length MAC. If the key is not secret the method provides little in the way of security.

For example, it we have a shared key of “test123” and a message of “hello”, the CBC-MAC is 9F63F3A838D17066 (16x4 hex characters is 64-bits), but with “hellp” is it A2CD2D8CBD8E6DAD. Only by knowing the secret key can I determine the correct hash. In AES Cipher Block Chaining (CBC) encryption we use an IV to make sure that the bits differ for the same message. In CBC-MAC the IV is set to zero, whereas with AES-CCM (Counter with CBC-MAC) the IV value is used to change the message digest.

from cryptography.hazmat.primitives.ciphers.aead import AESCCM

key = AESGCM.generate_key(bit_length=keysize)
cipher = AESCCM(key)
ct = cipher.encrypt(nonce,message.encode(), add.encode())
rtn=cipher.decrypt(nonce,ct, add.encode())

ChaCha20/Poly1305

Salsa20 and ChaCha20 were designed by Daniel J. Bernstein and are stream ciphers. Google has defined ChaCha20 as a standard for stream encryption [RFC 7539] and is included in TLS standards [RFC 7505]. It uses a 256-bit key, a 32-bit counter, and a 96-bit nonce, with 10 rounds of permutations. Poly1305 adds authenticated data with the use of a tag.

from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305

key = ChaCha20Poly1305.generate_key()
cipher = ChaCha20Poly1305(key)
ct = cipher.encrypt(nonce,message.encode(), add.encode())
rtn=cipher.decrypt(nonce,ct, add.encode())

And the great thing about ChaCha20 is that it isn’t AES. If you don’t trust the NIST-defined AES standard, ChaCha20 is a great alternative. Also, if AES was to be cracked, we still have an alternative with ChaCha20.

AES OCB3

OCB3 is a block cipher mode that supports 64-bit, 96-bit, and 128-bit authentication tags.

from cryptography.hazmat.primitives.ciphers.aead import AESOCB3

key = AESGCM.generate_key(bit_length=keysize)
cipher = AESOCB3(key)
ct = cipher.encrypt(nonce,message.encode(), add.encode())
rtn=cipher.decrypt(nonce,ct, add.encode())

The RFC is here.

Coding

The following is some coding that illustrates the methods:

import os
import sys
import base64

from cryptography.hazmat.primitives.ciphers.aead import AESSIV,AESOCB3,AESGCM,ChaCha20Poly1305,AESCCM

message = "Hello"
add = "Session 1234"
method=0
keysize=128
if (len(sys.argv)>1):
message=str(sys.argv[1])
if (len(sys.argv)>2):
add=str(sys.argv[2])
if (len(sys.argv)>3):
method=int(sys.argv[3])
if (len(sys.argv)>4):
keysize=int(sys.argv[4])

nonce = os.urandom(12)

try:
if (method==0):
key = AESSIV.generate_key(bit_length=2*keysize)
cipher = AESSIV(key)
print(f"==AES SIV key size={2*keysize} ==")
elif (method==1):
key = AESGCM.generate_key(bit_length=keysize)
cipher = AESGCM(key)
print("==AES CCM==")
elif (method==2):
key = AESOCB3.generate_key(bit_length=keysize) # 128, 192, 256
cipher = AESOCB3(key)
print("==AES AESOCB3==")
elif (method==3):
key = ChaCha20Poly1305.generate_key()
cipher = ChaCha20Poly1305(key)
print("==AES ChaCha20Poly1305==")
elif (method==4):
key = AESCCM.generate_key(bit_length=keysize)
cipher = AESCCM(key)
print("==AES CCM==")

if (method==0):
aad = [add.encode(), nonce]
ct = cipher.encrypt(message.encode(), aad)
rtn=cipher.decrypt(ct, aad)
else:
ct = cipher.encrypt(nonce,message.encode(), add.encode())
rtn=cipher.decrypt(nonce,ct, add.encode())

print(f"Message:\t{message}")
print(f"AAD:\t\t{add}")
print(f"\nNonce:\t\t{nonce.hex()}")
print(f"\nKey:\t\t{key.hex()}")
print(f"Key:\t\t{base64.b64encode(key).decode()}")
print(f"\nCipher:\t\t{ct.hex()}")
print(f"Cipher:\t\t{base64.b64encode(ct).decode()}")

print(f"\nDecrypt:\t{rtn.decode()}")
except Exception as e:
print(e)

and a sample run for 128-bit SIV (notice that the key size is twice as large as the key) [here]:

==AES SIV key size=256 ==
Message: Hello
AAD: Test 123

Nonce: cf48f99a3347e0730ea8267f

Key: a1b3c8fdf5aec3dbd10954041a2102932d2bbb17f195692099a888a8770beb5a
Key: obPI/fWuw9vRCVQEGiECky0ruxfxlWkgmaiIqHcL61o=

Cipher: e1fcb6f4f594dcab847fd80ef8386bda7f17264b21
Cipher: 4fy29PWU3KuEf9gO+Dhr2n8XJksh

Decrypt: Hello

and 128-bit GCM [here]:

==AES CCM==
Message: Hello
AAD: Test 123

Nonce: 7f162b557f737427a651eb49

Key: bb1cc6f724af8c48c25980dc68db64a5
Key: uxzG9ySvjEjCWYDcaNtkpQ==

Cipher: a263459142f53f73c893468a017342745a11112aa3
Cipher: omNFkUL1P3PIk0aKAXNCdFoRESqj

Decrypt: Hello

Conclusions

So, AEAD allows you to bind some additional data into the cipher, and so you can bind the ciphertext, so it cannot be used for other purposes.