Serious and Easy Crypto With AES/GCM

Yaşar Yücel Yeşilbağ
The Startup
Published in
5 min readDec 5, 2020

--

Today’s libraries do complicated things while providing easy interfaces. We’ll look into crypto version of this nice fashion, and see how serious crypto can be accomplished easily. Thanks to .Net (Core), all these processes will be inter-operable and cross-platform 😎

AES, being short for Advanced Encryption Standard, is actually a crypto method named Rijndael. It was selected by NIST, among other submitted proposals, to be “the AES” at 2001. It’s a symmetric-key algorithm, meaning the same key is used for both encrypting and decrypting the data.

AES can be run in many modes, we’ll be looking at GCM, Galois/Counter Mode. This article will not dive into details, descriptions will be kept simple. But for curious ones, Wikipedia has very good pages for AES, its modes of operations and GCM.

First things first, here are some definitions:

  • Key: A byte array that both parties have, 128-bit (16 bytes)
  • Nonce: A number used once, 96-bit (12 bytes)
  • Authorization data: A public text from sender, arbitrary size.
  • Authorization tag: A byte array created at sender side, 128-bit (16 bytes)
  • Plain text: Data to be encrypted, can be text or binary, arbitrary size.
  • Cipher text: Encrypted data, same size as plain data.

4 of these components should be transmitted/received by communicating parties; nonce, cipher, authorization tag and authorization data. Below is the structure for these 4 components, considered as the data package to be carried over i.e. a network.

public struct AESData
{
public string nonce;
public string cipher;
public string authTag;
public string authData;
}

Encrypted data is essentially a byte array, containing non-ASCII characters. We’ll use base64 encoding to convert byte arrays to strings, so the package can be sent and received over any text-based communication medium.

Encryption

AES/GCM, when encrypting, takes the key, nonce, authorization data and plain text as input, gives cipher text and authorization tag as output.

Plain text gets encrypted using the key and the nonce, creating cipher text. Then, authorization data is mixed with cipher text using the key, resulting 128-bit authorization tag, which is to prove both authenticity and integrity of the message.

Below piece of code shows how encryption is done in C#.

// declare aes variables
byte[] key = new byte[16];
byte[] nonce = new byte[12];
byte[] authTag = new byte[16];
byte[] authData = utf8enc.GetBytes("Auth data");
// data to be transmitted and received in base64 encoding
AESData aesData = new AESData();
// assign plain text
string plainText = "Hello AES/GCM! Some non-standard chars: öçşığü";
// convert the plain text string to a byte array
byte[] plainBytes = utf8enc.GetBytes(plainText);
// allocate the cipher text byte array as the same size as the plain text byte array
byte[] cipher = new byte[plainBytes.Length];
// perform encryption
using (AesGcm aesgcm = new AesGcm(key)) aesgcm.Encrypt(nonce, plainBytes, cipher, authTag, authData);
// encode aes data to Base64 strings, which will be transmitted
aesData.nonce = Convert.ToBase64String(nonce);
aesData.cipher = Convert.ToBase64String(cipher);
aesData.authTag = Convert.ToBase64String(authTag);
aesData.authData = Convert.ToBase64String(authData);

Decryption

AES/GCM, when decrypting, takes the key, nonce, authorization data, authorization tag and cipher text as input, gives plain text as output.

Authorization data is mixed with cipher text using the key, resulting 128-bit authorization tag. If this calculated tag matches the received tag, then the message is really sent by the sender, since only the person with the correct key can create that authorization tag. Then, cipher text gets decrypted using the key and the nonce, creating plain text.

Below piece of code shows how decryption is done in C#.

// decode received Base64 strings to aes data
nonce = Convert.FromBase64String(aesData.nonce);
cipher = Convert.FromBase64String(aesData.cipher);
authTag = Convert.FromBase64String(aesData.authTag);
authData = Convert.FromBase64String(aesData.authData);
// allocate the decrypted text byte array as the same size as the plain text byte array
byte[] decryptedBytes = new byte[cipher.Length];
// perform decryption
using (AesGcm aesgcm = new AesGcm(key)) aesgcm.Decrypt(nonce, cipher, authTag, decryptedBytes, authData);
// convert the byte array to the plain text string
string decryptedText = utf8enc.GetString(decryptedBytes);

What about nonce?

So far we got data authenticity, integrity and confidentiality, without using nonce value. But what if an eavesdropper saves a copy of a package, despite being unable to see the plain content, send it to receiver at a later time. How the receiver can determine whether the package is from the real sender or an eavesdropper? This is called replay attack.

Nonce value can be used to have protection against replay attack. Communicating parties may both have a record of nonce value of zero at the beginning. Every time one party preparing an AES package, may increase nonce value by one and use that value. Receiver may check if nonce value in the received package is greater than the last value of nonce on its record or not. If it’s not, then that package is a replayed package, so receiver discards it.

That’s it. This was a brief look into AES/GCM encryption. For completeness, the full code and output of a console program are at the bottom. It’s in C#, targeting .Net Core 3.1, so it can be published (Visual Studio 2019 -> Build -> Publish) to Windows, Macos, Linux distros and even Raspbian. Method can be used in .Net projects targeting mobile platforms as well. All this platforms can crypto-communicate with each other. 🧐🤓

using System;
using System.Security.Cryptography;
using System.Text;
namespace aes_gcm
{
class Program
{
// aes data in Base64 encoding, which will be transmitted and received
public struct AESData
{
public string nonce;
public string cipher;
public string authTag;
public string authData;
}
static void Main(string[] args)
{
// all string <-> byte array conversions will use UTF8 encoding
// some non-ascii chars are added to auth data and plain text for testing purposes
UTF8Encoding utf8enc = new UTF8Encoding();
// console will use UTF8 encoding
Console.OutputEncoding = utf8enc;
// declare aes variables
byte[] key = new byte[16];
byte[] nonce = new byte[12];
byte[] authTag = new byte[16];
byte[] authData = utf8enc.GetBytes("Auth data öçşığü");
// data to be transmitted and received in base64 encoding
AESData aesData = new AESData();
// randomly generate aes key
using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) rng.GetBytes(key);
WriteByteArray("key", key);
WriteByteArray("nonce", nonce);
// assign plain text
string plainText = "Hello AES/GCM! Some non-standard chars: öçşığü";
Console.WriteLine("plainText='{0}'", plainText);
Console.WriteLine();
// convert the plain text string to a byte array
byte[] plainBytes = utf8enc.GetBytes(plainText);
WriteByteArray("plainBytes", plainBytes);
// allocate the cipher text byte array as the same size as the plain text byte array
byte[] cipher = new byte[plainBytes.Length];
// perform encryption
using (AesGcm aesgcm = new AesGcm(key)) aesgcm.Encrypt(nonce, plainBytes, cipher, authTag, authData);
WriteByteArray("cipher", cipher);
WriteByteArray("authTag", authTag);
// encode aes data to Base64 strings, which will be transmitted
aesData.nonce = Convert.ToBase64String(nonce);
aesData.cipher = Convert.ToBase64String(cipher);
aesData.authTag = Convert.ToBase64String(authTag);
aesData.authData = Convert.ToBase64String(authData);
// print aes data
Console.WriteLine("AES data: (nonce/cipher/authTag/authData)");
Console.WriteLine(aesData.nonce);
Console.WriteLine(aesData.cipher);
Console.WriteLine(aesData.authTag);
Console.WriteLine(aesData.authData);
Console.WriteLine();
/*** transmission of encrypted data ***/
// decode received Base64 strings to aes data
nonce = Convert.FromBase64String(aesData.nonce);
cipher = Convert.FromBase64String(aesData.cipher);
authTag = Convert.FromBase64String(aesData.authTag);
authData = Convert.FromBase64String(aesData.authData);
// allocate the decrypted text byte array as the same size as the plain text byte array
byte[] decryptedBytes = new byte[cipher.Length];
// perform decryption
using (AesGcm aesgcm = new AesGcm(key)) aesgcm.Decrypt(nonce, cipher, authTag, decryptedBytes, authData);
WriteByteArray("decryptedBytes", decryptedBytes);
// convert the byte array to the plain text string
string decryptedText = utf8enc.GetString(decryptedBytes);
Console.WriteLine("decryptedText='{0}'", decryptedText);
Console.WriteLine();
}
static void WriteByteArray(string name, byte[] byteArray)
{
Console.WriteLine("\"{0}\", {1} bytes, {2} bits:", name, byteArray.Length, byteArray.Length << 3);
Console.WriteLine(BitConverter.ToString(byteArray));
Console.WriteLine();
}
}
}
AES/GCM cryption output
AES/GCM cryption output

--

--