I was trying to learn AES GCM from (https://asecuritysite.com/bouncy/bc_gcm). Although the site does a nice job combining the code all in one. I was trying to break it out and make it a function (GCM Encrypt and GCM Decrypt). I seem to have figured out the encrypt part, but I am totally lost on the decryption part. Any assistance would be greatly appreciated.
Oh, I did do a search for the errors I'm finding (Key length not 128/192/256 bits and 'mac check in GCM failed'). Most of them were Java, Python or android which I'm not familiar with and didn't really cover for the most part BouncyCastle GCM Encrypt and GCM Decrypt or in a way for a newbie to understand.
Here is what I was able to construct.
public static string Testing_GCM_Encrypt2(string msg)
{
//var msg = "Hello 123";
var add = "";
var iv = "00112233445566778899AABBCCDDEEFF00";
//
byte[] plainTextData = System.Text.Encoding.UTF8.GetBytes(msg);
IBlockCipher cipher = new AesEngine();
int macSize = 8 * cipher.GetBlockSize();
byte[] nonce = new byte[8];
Array.Copy(System.Text.Encoding.UTF8.GetBytes(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;
//
return Convert.ToBase64String(rtn);
}
// Decrypt:
public static string Testing_GCM_Decrypt2(string Ciphertext)
{
String iv = "00112233445566778899AABBCCDDEEFF00";
byte[] byteIV = System.Text.Encoding.UTF8.GetBytes(iv);
//
byte[] nonce = new byte[8];
//
byte[] TextData = System.Text.Encoding.UTF8.GetBytes(Ciphertext);
var add = string.Empty;
byte[] associatedText = System.Text.Encoding.UTF8.GetBytes(add);
//
GcmBlockCipher cipher = new GcmBlockCipher(new AesEngine());
cipher.Init(false, new AeadParameters(new KeyParameter(byteIV), 128, nonce));
//
int outputSize = cipher.GetOutputSize(TextData.Length);
TextData = new byte[outputSize];
//
int result = cipher.ProcessBytes(TextData, 0, TextData.Length, TextData, 0);
cipher.DoFinal(TextData, result);
var pln = TextData;
return System.Text.Encoding.UTF8.GetString(pln );
}
Decryption does not work for various reasons:
byteIV
is used as the key for decryption. This is 34 bytes in size and therefore does not correspond to any of the valid AES key lengths, which triggers a corresponding exception. The key generated during encryption can be determined with KeyParameter#GetKey()
and must be stored securely in a suitable location so that it is available for decryption.iv
by copying the first 8 bytes. For decryption, an empty 8 bytes nonce is used.TextData
), so that the encrypted data is lost with TextData = new byte[outputSize]
.AeadParameters
) are not taken into account during decryption. However, this has no effect as empty AADs are used during encryption.Each of these points (regarding the 1st point an incorrect key of valid length) result in a failed authentication with a corresponding exception.
The minimum changes required for decryption to work are:
public static string Testing_GCM_Decrypt2(string Ciphertext)
{
String iv = "00112233445566778899AABBCCDDEEFF00";
byte[] byteIV = System.Text.Encoding.UTF8.GetBytes(iv);
//
byte[] nonce = new byte[8];
Array.Copy(System.Text.Encoding.UTF8.GetBytes(iv), nonce, 8); // Fix 2: apply nonce from encryption
//
byte[] encryptedData = Convert.FromBase64String(Ciphertext); // Fix 4: apply encoding from encryption, Fix 3: use different variables for encrypted and decrypted data
var add = string.Empty;
byte[] associatedText = System.Text.Encoding.UTF8.GetBytes(add);
//
GcmBlockCipher cipher = new GcmBlockCipher(new AesEngine());
cipher.Init(false, new AeadParameters(new KeyParameter(key), 128, nonce, associatedText)); // Fix 1: apply key from encryption, Fix 5: consider AAD
//
int outputSize = cipher.GetOutputSize(encryptedData.Length); // Fix 3
byte[] TextData = new byte[outputSize];
//
int result = cipher.ProcessBytes(encryptedData, 0, encryptedData.Length, TextData, 0); // Fix 3
cipher.DoFinal(TextData, result);
var pln = TextData;
return System.Text.Encoding.UTF8.GetString(pln);
}
However, your implementation is inefficient and has serious security vulnerabilities (in the first place the reuse of key/nonce pairs). The following should be considered for AES/GCM:
A possible implementation is:
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
using System;
using System.Text;
...
public static string Encrypt(byte[] key, byte[] plaintext, byte[] aad)
{
// create random nonce
byte[] nonce = new byte[12];
SecureRandom secureRandom = new SecureRandom();
secureRandom.NextBytes(nonce);
// encrypt
GcmBlockCipher gcmBlockCipher = new GcmBlockCipher(new AesEngine());
gcmBlockCipher.Init(true, new AeadParameters(new KeyParameter(key), 128, nonce, aad));
int outputSizeCiphertextTag = gcmBlockCipher.GetOutputSize(plaintext.Length);
byte[] nonceCiphertextTag = new byte[nonce.Length + outputSizeCiphertextTag]; // buffer for nonce|ciphertext|tag
Buffer.BlockCopy(nonce, 0, nonceCiphertextTag, 0, nonce.Length);
int processedBytes = gcmBlockCipher.ProcessBytes(plaintext, 0, plaintext.Length, nonceCiphertextTag, nonce.Length);
gcmBlockCipher.DoFinal(nonceCiphertextTag, nonce.Length + processedBytes);
// Base64 encode encrypted data
return Convert.ToBase64String(nonceCiphertextTag);
}
public static byte[] Decrypt(byte[] key, string nonceCiphertextTagB64, byte[] aad)
{
// Base64 decode encrypted data
byte[] nonceCiphertextTag = Convert.FromBase64String(nonceCiphertextTagB64);
// separate nonce and ciphertextTag
int nonceSize = 12;
byte[] nonce = nonceCiphertextTag[..nonceSize];
byte[] ciphertextTag = nonceCiphertextTag[nonceSize..];
// decrypt
GcmBlockCipher gcmBlockCipher = new GcmBlockCipher(new AesEngine());
gcmBlockCipher.Init(false, new AeadParameters(new KeyParameter(key), 128, nonce, aad));
int outputSizeDecryptedData = gcmBlockCipher.GetOutputSize(ciphertextTag.Length);
byte[] decryptedData = new byte[outputSizeDecryptedData];
int processedBytes = gcmBlockCipher.ProcessBytes(ciphertextTag, 0, ciphertextTag.Length, decryptedData, 0);
gcmBlockCipher.DoFinal(decryptedData, processedBytes);
return decryptedData;
}
Use case:
// create key
CipherKeyGenerator cipherKeyGenerator = new CipherKeyGenerator();
cipherKeyGenerator.Init(new KeyGenerationParameters(new SecureRandom(), 128));
byte[] key = cipherKeyGenerator.GenerateKey();
// encrypt/decrypt
string plaintext = "The quick brown fox jumps over the lazy dog";
string aad = "some additional authenticated data";
string encryptedData = Encrypt(key, Encoding.UTF8.GetBytes(plaintext), Encoding.UTF8.GetBytes(aad));
byte[] decryptedData = Decrypt(key, encryptedData, Encoding.UTF8.GetBytes(aad));
Console.WriteLine(Encoding.UTF8.GetString(decryptedData)); // The quick brown fox jumps over the lazy dog
Edit: ..
is the range operator which is available as of C# 8. If the range operator is not available in your environment you can use Buffer.BlockCopy()
:
byte[] nonceCiphertextTag = Encoding.UTF8.GetBytes("abcdefghijklmnopqrstuvwxyz");
int nonceSize = 12;
//byte[] nonce = nonceCiphertextTag[..nonceSize];
//byte[] ciphertextTag = nonceCiphertextTag[nonceSize..];
byte[] nonce = new byte[nonceSize];
Buffer.BlockCopy(nonceCiphertextTag, 0, nonce, 0, nonce.Length);
byte[] ciphertextTag = new byte[nonceCiphertextTag.Length - nonceSize];
Buffer.BlockCopy(nonceCiphertextTag, nonce.Length, ciphertextTag, 0, ciphertextTag.Length);