AES GCM (Galois Counter Mode) is a stream cipher mode for AES. It is based on the CTR mode, but is converted into a stream cipher. This provides low latency in the encryption/decryption process, and which is fast to process. As it is a stream cipher, it does not require padding. Along with this it integrates AEAD (Authenticated Encryption with Associated Data) for the authentication of the message. With AES-GCM, we have an encryption key (normally 128 bits or 256 bits) and a message, and then generate the cipher, a random nonce, and an authentication tag (and which is a message authentication code (MAC)). The tag helps authenticate the message. In this case we will use the Bouncy Castle library with C# [Download code].
AES GCM with Bouncy Castle and C# |
Coding
First we create a folder named "bc_gcm", and then go into that folder.We can create a Dotnet console project for .NET 8.0 with:
dotnet new console --framework net8.0
This produces a Csproject file of:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net8.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> </Project>
We then add the latest Bouncy Castle library:
dotnet add package BouncyCastle.Cryptography --version 2.2.1
The following is the coding:
namespace GCM { using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Engines; using Org.BouncyCastle.Crypto.Modes; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Security; class Program { static void Main(string[] args) { var msg="Hello"; var add=""; var iv="00112233445566778899AABBCCDDEEFF00"; if (args.Length >0) msg=args[0]; if (args.Length >1) add=args[1]; if (args.Length >2) iv=args[2]; try { var plainTextData=System.Text.Encoding.UTF8.GetBytes(msg); IBlockCipher cipher = new AesEngine(); int macSize = 8*cipher.GetBlockSize(); byte[] nonce = new byte[8]; Array.Copy(Convert.FromHexString(iv), nonce, 8); byte[] associatedText =System.Text.Encoding.UTF8.GetBytes(add); CipherKeyGenerator keyGen = new CipherKeyGenerator(); keyGen.Init(new KeyGenerationParameters(new SecureRandom(), 128)); KeyParameter keyParam = keyGen.GenerateKeyParameter(); AeadParameters keyParamAead = new AeadParameters(keyParam, macSize, nonce, associatedText); GcmBlockCipher cipherMode = new GcmBlockCipher(cipher); cipherMode.Init(true,keyParamAead); int outputSize = cipherMode.GetOutputSize(plainTextData.Length); byte[] cipherTextData = new byte[outputSize]; int result = cipherMode.ProcessBytes(plainTextData, 0, plainTextData.Length, cipherTextData, 0); cipherMode.DoFinal(cipherTextData, result); var rtn = cipherTextData; // Decrypt cipherMode.Init(false,keyParamAead); outputSize = cipherMode.GetOutputSize(cipherTextData.Length); plainTextData = new byte[outputSize]; result = cipherMode.ProcessBytes(cipherTextData, 0, cipherTextData.Length,plainTextData, 0); cipherMode.DoFinal(plainTextData, result); var pln=plainTextData; Console.WriteLine("=== AES GCM Cipher =="); namespace ECCurves { using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Engines; using Org.BouncyCastle.Crypto.Modes; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Security; class Program { static void Main(string[] args) { var msg="Hello"; var add=""; var iv="00112233445566778899AABBCCDDEEFF00"; var size=128; if (args.Length >0) msg=args[0]; if (args.Length >1) add=args[1]; if (args.Length >2) iv=args[2]; if (args.Length >3) size=Convert.ToInt32(args[3]); try { var plainTextData=System.Text.Encoding.UTF8.GetBytes(msg); IBlockCipher cipher = new AesEngine(); int macSize = 8*cipher.GetBlockSize(); byte[] nonce = new byte[8]; Array.Copy(Convert.FromHexString(iv), nonce, 8); byte[] associatedText =System.Text.Encoding.UTF8.GetBytes(add); CipherKeyGenerator keyGen = new CipherKeyGenerator(); keyGen.Init(new KeyGenerationParameters(new SecureRandom(), size)); KeyParameter keyParam = keyGen.GenerateKeyParameter(); AeadParameters keyParamAead = new AeadParameters(keyParam, macSize, nonce, associatedText); GcmBlockCipher cipherMode = new GcmBlockCipher(cipher); cipherMode.Init(true,keyParamAead); int outputSize = cipherMode.GetOutputSize(plainTextData.Length); byte[] cipherTextData = new byte[outputSize]; int result = cipherMode.ProcessBytes(plainTextData, 0, plainTextData.Length, cipherTextData, 0); cipherMode.DoFinal(cipherTextData, result); var rtn = cipherTextData; // Decrypt cipherMode.Init(false,keyParamAead); outputSize = cipherMode.GetOutputSize(cipherTextData.Length); plainTextData = new byte[outputSize]; result = cipherMode.ProcessBytes(cipherTextData, 0, cipherTextData.Length,plainTextData, 0); cipherMode.DoFinal(plainTextData, result); var pln=plainTextData; Console.WriteLine("=== AES GCM Cipher =="); Console.WriteLine("Message:\t\t{0}",msg); Console.WriteLine("IV:\t\t\t{0}",iv); Console.WriteLine("Key:\t\t\t{0} [{1}]",Convert.ToHexString(keyParam.GetKey()),Convert.ToBase64String(keyParam.GetKey())); Console.WriteLine("Additional data:\t{0}",add); Console.WriteLine("\nCipher (hex):\t\t{0}",Convert.ToHexString(rtn)); Console.WriteLine("Cipher (Base64):\t{0}",Convert.ToBase64String(rtn)); Console.WriteLine("\nPlain:\t\t\t{0}",System.Text.Encoding.UTF8.GetString(pln)); } catch (Exception e) { Console.WriteLine("Error: {0}",e.Message); } } } }
A sample run is:
=== AES GCM Cipher == Message: Hello 123 IV: 00112233445566778899AABBCCDDEEFF Key: C55D5A8DD42C1F6ECF5B66336D8A179C Additional data: test Cipher: 91EC468400C770848ABF730F7C6A0FED95CF0EFF6794F1F2B0 Cipher: kexGhADHcISKv3MPfGoP7ZXPDv9nlPHysA== Plain: Hello 123
Notice that the cipher stream is the same length as the plaintext stream.