Implementing AES Encryption with C#: A Beginner-Friendly Guide
Learn AES encryption in C# step by step, with AES-GCM, AES-CBC, key derivation, and safe code examples explained simply for beginners.
Implementing AES Encryption with C#: A Beginner-Friendly Guide
Imagine you want to send a secret note to your best friend in class. If you pass it in plain words, anyone who grabs it can read it. So you and your friend agree on a secret rule. You shift every letter forward by three. Now the note looks like a jumble. Only your friend, who knows the rule, can turn it back into real words.
That secret rule is the idea behind encryption. AES is a very strong, modern version of that idea, used by banks, WhatsApp, and even your country's government. In this guide, you will learn how to use AES in C# in a safe and simple way.
We will go slowly. Short sentences. Real examples. By the end, you will be able to lock and unlock data with confidence.
What is AES, in plain words?
AES stands for Advanced Encryption Standard. It is a method that scrambles your data using a secret key. The same key (or a related one) is used to unscramble it later.
Think of it like a tiffin box with a lock.
- You put your food (your data) inside.
- You lock it with a key.
- Anyone can carry the box, but they cannot open it without the key.
AES is called a symmetric algorithm. That means the same key both locks and unlocks. This is different from your house door, where one key locks and the same key unlocks. Compare that to a postbox, where anyone can drop a letter in (lock) but only the postman has the key to take letters out (that is asymmetric, a different topic).
Some words you should know
Before we write code, let us learn a few words. Do not worry, they are simple once you see them.
| Word | Simple meaning |
|---|---|
| Plaintext | The normal, readable data you want to hide |
| Ciphertext | The scrambled, locked version |
| Key | The secret that locks and unlocks |
| Nonce / IV | A small random value used once, so the same text does not look the same twice |
| Tag | A small stamp that proves nobody changed your data |
| AEAD | "Authenticated Encryption", it hides data AND checks for tampering |
The nonce (also called an IV, short for Initialization Vector) is very important. It is like adding a pinch of random salt to your cooking. Even if you cook the same dish twice, a different pinch makes each result slightly unique. This stops attackers from spotting patterns.
Two main ways to do AES in C#
In .NET there are two popular styles.
- AES-GCM (Galois/Counter Mode). This is the modern, recommended way. It hides your data and also gives you a tag to prove nobody touched it.
- AES-CBC (Cipher Block Chaining). This is older. It hides your data but does not check tampering on its own. You have to add that check yourself.
The official Microsoft guidance is clear: prefer AES-GCM when you can. It gives you both privacy and a tamper check in one go.
The golden rules of AES (read this twice)
These rules keep you safe. Break them and even strong AES becomes weak.
| Rule | Why it matters |
|---|---|
| Use a random key | A guessable key is like a lock with no real key |
| Never reuse a nonce with the same key | Reusing it can leak your secrets, especially in GCM |
| Make keys and nonces with a crypto random source | Normal Random is predictable and unsafe |
| Store keys safely, never in code | A leaked key means no protection at all |
| Prefer AES-GCM | You get tamper detection for free |
| Plan for key rotation | Old keys should be replaceable over time |
That second rule is the big one. For AES-GCM, never reuse a nonce with the same key. If you do, you can destroy the whole protection. We will make a fresh random nonce every single time.
Setting up
You do not need to install anything extra. AES lives inside the built-in namespace System.Security.Cryptography. This ships with .NET. We are using modern .NET (.NET 10 is the current LTS), but this code works on .NET 8 and newer too.
Add this line at the top of your file.
using System.Security.Cryptography;
using System.Text;Example 1: AES-GCM, the recommended way
Let us write a small helper that locks and unlocks a string. We will keep it readable.
In AES-GCM we need three random-ish things and we produce three outputs.
- A key (32 bytes for AES-256).
- A nonce (exactly 12 bytes, this is the rule for GCM).
- A tag (16 bytes, the tamper stamp).
Here is the encrypt method. Read the comments slowly.
public static class AesGcmHelper
{
// AES-256 uses a 32 byte key.
public const int KeySizeBytes = 32;
// GCM nonce must be 12 bytes. This is a fixed rule.
public const int NonceSizeBytes = 12;
// The tag (tamper stamp) is 16 bytes.
public const int TagSizeBytes = 16;
public static byte[] Encrypt(string plainText, byte[] key)
{
byte[] plainBytes = Encoding.UTF8.GetBytes(plainText);
// Make a fresh random nonce every time. Never reuse it!
byte[] nonce = RandomNumberGenerator.GetBytes(NonceSizeBytes);
byte[] cipher = new byte[plainBytes.Length];
byte[] tag = new byte[TagSizeBytes];
// In .NET 8+ we pass the tag size into the constructor.
using var aes = new AesGcm(key, TagSizeBytes);
aes.Encrypt(nonce, plainBytes, cipher, tag);
// We glue nonce + tag + cipher together so we can send one blob.
byte[] result = new byte[NonceSizeBytes + TagSizeBytes + cipher.Length];
Buffer.BlockCopy(nonce, 0, result, 0, NonceSizeBytes);
Buffer.BlockCopy(tag, 0, result, NonceSizeBytes, TagSizeBytes);
Buffer.BlockCopy(cipher, 0, result, NonceSizeBytes + TagSizeBytes, cipher.Length);
return result;
}
}Notice one important thing. We used RandomNumberGenerator.GetBytes. That is the safe random source. We did not use the normal Random class. The normal one is predictable, and predictable is dangerous in security.
Also notice the using keyword on AesGcm. This makes sure the object is cleaned up properly. Always do this.
Now the decrypt method. It just reverses the steps.
public static string Decrypt(byte[] encrypted, byte[] key)
{
// Split the blob back into nonce, tag, and cipher.
var nonce = new ReadOnlySpan<byte>(encrypted, 0, NonceSizeBytes);
var tag = new ReadOnlySpan<byte>(encrypted, NonceSizeBytes, TagSizeBytes);
int cipherLength = encrypted.Length - NonceSizeBytes - TagSizeBytes;
var cipher = new ReadOnlySpan<byte>(
encrypted, NonceSizeBytes + TagSizeBytes, cipherLength);
byte[] plainBytes = new byte[cipherLength];
using var aes = new AesGcm(key, TagSizeBytes);
// If anyone changed the data, this line throws an exception.
aes.Decrypt(nonce, cipher, tag, plainBytes);
return Encoding.UTF8.GetString(plainBytes);
}The magic is in the last comment. If even one byte of the encrypted data was changed by a bad person, the Decrypt call throws a CryptographicException. That is the tamper check doing its job. With AES-GCM, you get this for free.
AES-GCM encryption steps
Steps
Plaintext
Turn the string into bytes
Make nonce
Fresh 12-byte random value
Encrypt
Produce cipher and tag
Pack blob
Join nonce, tag, cipher
AES-GCM decryption steps
Steps
Read blob
Take the encrypted bytes
Split parts
Pull out nonce, tag, cipher
Verify tag
Throws if tampered
Plaintext
Return the original string
Where does the key come from?
Good question. You need a key, and it must be strong. There are two common ways.
Way A: A random key (best when you control storage)
If your app generates and stores the key in a safe place, make it random.
byte[] key = RandomNumberGenerator.GetBytes(32); // AES-256Store this key in Azure Key Vault, environment variables, or another protected store. Never write it directly in your code or commit it to Git.
Way B: A key from a password (when a human types a password)
Sometimes the user gives a password, like "mangojuice123". A password is not a good key by itself. It is too short and too guessable. So we stretch it into a proper key using PBKDF2.
public static byte[] DeriveKey(string password, byte[] salt)
{
// Use the one-shot static method. It is the recommended way.
return Rfc2898DeriveBytes.Pbkdf2(
password: password,
salt: salt,
iterations: 600_000, // slow on purpose, to block guessers
hashAlgorithm: HashAlgorithmName.SHA256,
outputLength: 32); // 32 bytes for AES-256
}A few notes for safety.
- The salt should be random and at least 16 bytes. Store it next to the data, it does not need to be secret.
- The high iteration count (600,000) makes guessing slow. Slow is good here. It tires out attackers.
- Use the static
Rfc2898DeriveBytes.Pbkdf2method. The older instance-style constructors are now marked obsolete in modern .NET.
A quick word on AES-CBC
Sometimes you must work with an older system that only understands AES-CBC. Here is the simplest safe shape using Aes.Create().
public static byte[] EncryptCbc(string plainText, byte[] key)
{
using var aes = Aes.Create();
aes.Key = key;
aes.GenerateIV(); // fresh random IV every time
using var encryptor = aes.CreateEncryptor();
byte[] plainBytes = Encoding.UTF8.GetBytes(plainText);
byte[] cipher = encryptor.TransformFinalBlock(plainBytes, 0, plainBytes.Length);
// Store the IV with the cipher so we can decrypt later.
byte[] result = new byte[aes.IV.Length + cipher.Length];
Buffer.BlockCopy(aes.IV, 0, result, 0, aes.IV.Length);
Buffer.BlockCopy(cipher, 0, result, aes.IV.Length, cipher.Length);
return result;
}But here is the honest truth. CBC alone does not detect tampering. If you must use CBC, you should also add an HMAC check (a separate signature) so you can tell if data was changed. This is more work and easier to get wrong. That is exactly why AES-GCM is preferred. With GCM, the tamper check is built in.
The short advice: use AES-GCM unless something forces you to use CBC.
Common mistakes beginners make
Let us look at traps. Avoiding these puts you ahead of many developers.
| Mistake | What happens | Fix |
|---|---|---|
| Hard-coding the key in code | Anyone reading the code gets your key | Use a secrets store |
| Reusing the same nonce | GCM security breaks badly | New random nonce each time |
Using Random for keys | Predictable, easy to attack | Use RandomNumberGenerator |
| Using CBC with no HMAC | Tampering goes undetected | Use GCM, or add HMAC |
| Storing password as the key | Too weak, too guessable | Stretch with PBKDF2 |
| Catching and ignoring the crypto exception | You hide real attacks | Let it fail loudly |
The full lifecycle of doing this right looks like the diagram below.
How would you use this in a real app?
Picture a small notes app. A user writes a private note. Before saving it to the database, you call AesGcmHelper.Encrypt. The database stores only the scrambled blob. When the user opens the note, you call Decrypt and show the real text.
If a hacker steals your database, they only see scrambled junk. Without the key, the notes are useless to them. That is the whole point. You protect the data even if the storage leaks.
A few real-world tips.
- Keep the key in Azure Key Vault or a similar managed secret store.
- Log failures, but never log the key or the plaintext.
- Plan for key rotation. One day you may want to switch keys. Store a small "key version" tag with each record so you know which key was used.
A note on libraries and trust
You might wonder if you should grab a third-party encryption library. For plain AES, you usually do not need one. The built-in System.Security.Cryptography is well-tested, fast, and maintained by Microsoft. Rolling your own crypto math is dangerous, but using the built-in classes is the safe, blessed path.
Also, a small heads-up unrelated to AES but useful to know in the .NET world: some popular libraries like MediatR and MassTransit have moved to commercial licenses in recent versions. That does not affect encryption, but it is good to check licenses before adopting any package in a serious project.
Quick recap
Here are the key ideas to remember.
- AES is a strong, symmetric encryption method. The same key locks and unlocks.
- Think of it like a locked tiffin box. Anyone can carry it, only the key opens it.
- Prefer AES-GCM. It hides data and detects tampering in one step.
- The nonce must be 12 bytes for GCM, random, and never reused with the same key.
- The tag is the tamper stamp. If data is changed,
Decryptthrows an exception. - Make keys and nonces with
RandomNumberGenerator, never withRandom. - Turn passwords into keys with
Rfc2898DeriveBytes.Pbkdf2, using a random salt and a high iteration count. - Use AES-CBC only when forced, and add HMAC for safety.
- Never hard-code keys. Store them in a secrets manager and plan for rotation.
You now know enough to encrypt and decrypt data safely in C#. Start with the AES-GCM helper, keep your keys safe, and you are in good shape.
References and further reading
- AesGcm Class, Microsoft Learn
- Cross-platform cryptography, Microsoft Learn
- SYSLIB0053: AesGcm should indicate the required tag size, Microsoft Learn
- SYSLIB0060: Rfc2898DeriveBytes constructors are obsolete, Microsoft Learn
- Authenticated Encryption in .NET with AES-GCM, Scott Brady
- How to perform AES encryption in .NET, siakabaro
Related Posts
How to Write Elegant Code with C# Pattern Matching
Learn C# pattern matching the easy way. Use is, switch expressions, property and list patterns to write clean, readable, and elegant .NET code.
How to Apply Functional Programming in C#: A Beginner's Guide
Learn functional programming in C# the simple way: pure functions, immutability, records, LINQ, pattern matching, and composition with friendly examples.
New Features in C# 13: A Friendly Beginner's Guide
Learn the new features in C# 13 with simple words, real-life examples, diagrams, and code you can read in minutes. Great for beginners.
Mastering Exception Handling in C#: A Comprehensive Guide
Learn C# exception handling the friendly way: try, catch, finally, custom exceptions, filters, throw vs throw ex, and real best practices for .NET 10.
Getting Started with C# Records: A Beginner's Friendly Guide
Learn C# records the easy way: value equality, with expressions, positional syntax, and record struct, explained with simple real-life examples.
Why I Switched to Primary Constructors for DI in C#
A friendly guide on why primary constructors made my C# dependency injection cleaner, with simple examples, diagrams, tables, and honest trade-offs.