As part of our implementation for the Union Bank Refund Process, we needed to build a secure channel for transferring sensitive transaction details from the Business to the Customer. To ensure confidentiality, integrity, and authenticity, we implemented AES encryption using the GCM (Galois/Counter Mode) — a modern and secure choice for authenticated encryption.
In this blog, I’ll walk you through how we’ve achieved this in C# with .NET Core, using the BouncyCastle cryptographic library.
Why AES-GCM?
- AES (Advanced Encryption Standard) is a widely adopted symmetric encryption algorithm.
- GCM (Galois/Counter Mode) adds integrity through authentication tags, preventing tampering.
- It's fast, secure, and ideal for real-time and financial applications like refunds.
Tech Stack
- Language: C# (.NET Core)
- Encryption Mode: AES-GCM
- Library: BouncyCastle
- Scenario: Encrypting/decrypting sensitive refund payloads between systems
Code. AES Encryption & Decryption Helper
using System;
using System.Text;
using System.Security.Cryptography;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Digests;
public class AesEncryptionHelper
{
private const int KeyLengthBits = 256;
private const int SaltLengthBytes = 16;
private const int IvLengthBytes = 16;
private const int TagLengthBits = 128;
private const int Pbkdf2Iterations = 65536;
public string PasswordBasedEncAES(string plainText, string password)
{
byte[] salt = GetRandomBytes(SaltLengthBytes);
byte[] iv = GetRandomBytes(IvLengthBytes);
byte[] key = DeriveKey(password, salt, Pbkdf2Iterations, KeyLengthBits);
GcmBlockCipher cipher = new GcmBlockCipher(new Org.BouncyCastle.Crypto.Engines.AesEngine());
AeadParameters parameters = new AeadParameters(new KeyParameter(key), TagLengthBits, iv, null);
cipher.Init(true, parameters);
byte[] inputBytes = Encoding.UTF8.GetBytes(plainText);
byte[] cipherText = new byte[cipher.GetOutputSize(inputBytes.Length)];
int len = cipher.ProcessBytes(inputBytes, 0, inputBytes.Length, cipherText, 0);
cipher.DoFinal(cipherText, len);
// Combine IV + CipherText + Salt
byte[] result = new byte[iv.Length + cipherText.Length + salt.Length];
Buffer.BlockCopy(iv, 0, result, 0, iv.Length);
Buffer.BlockCopy(cipherText, 0, result, iv.Length, cipherText.Length);
Buffer.BlockCopy(salt, 0, result, iv.Length + cipherText.Length, salt.Length);
return Convert.ToBase64String(result);
}
public string PasswordBasedDecryptAES(string cipherTextBase64, string password)
{
byte[] input = Convert.FromBase64String(cipherTextBase64);
byte[] iv = new byte[IvLengthBytes];
byte[] cipherText = new byte[input.Length - IvLengthBytes - SaltLengthBytes];
byte[] salt = new byte[SaltLengthBytes];
Buffer.BlockCopy(input, 0, iv, 0, iv.Length);
Buffer.BlockCopy(input, iv.Length, cipherText, 0, cipherText.Length);
Buffer.BlockCopy(input, iv.Length + cipherText.Length, salt, 0, salt.Length);
byte[] key = DeriveKey(password, salt, Pbkdf2Iterations, KeyLengthBits);
GcmBlockCipher cipher = new GcmBlockCipher(new Org.BouncyCastle.Crypto.Engines.AesEngine());
AeadParameters parameters = new AeadParameters(new KeyParameter(key), TagLengthBits, iv, null);
cipher.Init(false, parameters);
byte[] plainText = new byte[cipher.GetOutputSize(cipherText.Length)];
int len = cipher.ProcessBytes(cipherText, 0, cipherText.Length, plainText, 0);
cipher.DoFinal(plainText, len);
return Encoding.UTF8.GetString(plainText).TrimEnd('\0');
}
private byte[] DeriveKey(string password, byte[] salt,int Pbkdf2Iterations, int keyLengthBits)
{
var gen = new Pkcs5S2ParametersGenerator(new Sha256Digest());
gen.Init(PbeParametersGenerator.Pkcs5PasswordToBytes(password.ToCharArray()), salt, Pbkdf2Iterations);
KeyParameter keyParam = (KeyParameter)gen.GenerateDerivedMacParameters(keyLengthBits);
return keyParam.GetKey();
}
private byte[] GetRandomBytes(int length)
{
byte[] bytes = new byte[length];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(bytes);
}
return bytes;
}
}
Key Concepts Implemented:
- Salt Generation: Random salt ensures uniqueness for each encryption.
- IV (Initialization Vector): Prevents repetition and ensures cipher uniqueness.
- PBKDF2 Key Derivation: Strengthens password using multiple iterations and salt.
- AES-GCM with BouncyCastle: Provides both encryption and authentication.
- Base64 Encoding: Encoded encrypted payload for safe transmission via APIs.
How does it work?
Encryption (PasswordBasedEncAES
)
- Generates random salt and IV.
- Derives a 256-bit key using PBKDF2 with SHA-256.
- Encrypts the plaintext using AES in GCM mode.
- Combines IV + CipherText + Salt into a single Base64 string for transport.
Decryption (PasswordBasedDecryptAES
)
- Extracts IV, CipherText, and Salt from the received Base64 string.
- Derives the key again using the same password and extracted salt.
- Decrypts the ciphertext using AES-GCM.
- Returns the original plain text.
Example Usage
var helper = new AesEncryptionHelper();
string encrypted = helper.PasswordBasedEncAES("REFUND_AMOUNT=2500|ACCOUNT=XXXX1234", "MySecurePass123!");
string decrypted = helper.PasswordBasedDecryptAES(encrypted, "MySecurePass123!");
Console.WriteLine("Encrypted: " + encrypted);
Console.WriteLine("Decrypted: " + decrypted);
Security Best Practices Followed
- Key derivation with a high iteration count to slow brute-force attempts.
- Per-message random IV and Salt for non-repeatability.
- Authenticated encryption with GCM mode.
- Minimal plaintext exposure, all sensitive data is encrypted before transit.
Important Notes
- Use a strong, secret password, preferably managed via a secure vault.
- Ensure TLS is used during transmission, even with encrypted payloads.
- Periodically rotate encryption passwords/keys if feasible.
Libraries Used
Install BouncyCastle via NuGet:
dotnet add package BouncyCastle.NetCore
Conclusion
By using AES-GCM with PBKDF2 in .NET Core and BouncyCastle, we’ve established a robust encryption standard for securely handling refund transactions from businesses to customers via Union Bank APIs. This approach can be adopted in any secure B2C transaction pipeline where confidentiality and integrity are essential.
Have questions or want to contribute to this implementation? Drop a comment below or connect with me on LinkedIn.