Skip to main content
SEMastery
.NET Corebeginner

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.

13 min readUpdated December 1, 2025

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).

The basic idea of symmetric encryption with AES

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.

WordSimple meaning
PlaintextThe normal, readable data you want to hide
CiphertextThe scrambled, locked version
KeyThe secret that locks and unlocks
Nonce / IVA small random value used once, so the same text does not look the same twice
TagA 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.

  1. 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.
  2. 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.

Choosing between AES-GCM and AES-CBC

The golden rules of AES (read this twice)

These rules keep you safe. Break them and even strong AES becomes weak.

RuleWhy it matters
Use a random keyA guessable key is like a lock with no real key
Never reuse a nonce with the same keyReusing it can leak your secrets, especially in GCM
Make keys and nonces with a crypto random sourceNormal Random is predictable and unsafe
Store keys safely, never in codeA leaked key means no protection at all
Prefer AES-GCMYou get tamper detection for free
Plan for key rotationOld 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;

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

Plaintext
Make nonce
Encrypt
Pack blob

Steps

1

Plaintext

Turn the string into bytes

2

Make nonce

Fresh 12-byte random value

3

Encrypt

Produce cipher and tag

4

Pack blob

Join nonce, tag, cipher

What happens inside the Encrypt method

AES-GCM decryption steps

Read blob
Split parts
Verify tag
Plaintext

Steps

1

Read blob

Take the encrypted bytes

2

Split parts

Pull out nonce, tag, cipher

3

Verify tag

Throws if tampered

4

Plaintext

Return the original string

What happens inside the Decrypt method

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-256

Store 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.Pbkdf2 method. The older instance-style constructors are now marked obsolete in modern .NET.
Turning a weak password into a strong AES key

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.

MistakeWhat happensFix
Hard-coding the key in codeAnyone reading the code gets your keyUse a secrets store
Reusing the same nonceGCM security breaks badlyNew random nonce each time
Using Random for keysPredictable, easy to attackUse RandomNumberGenerator
Using CBC with no HMACTampering goes undetectedUse GCM, or add HMAC
Storing password as the keyToo weak, too guessableStretch with PBKDF2
Catching and ignoring the crypto exceptionYou hide real attacksLet it fail loudly

The full lifecycle of doing this right looks like the diagram below.

A safe AES workflow from start to finish

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, Decrypt throws an exception.
  • Make keys and nonces with RandomNumberGenerator, never with Random.
  • 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

Related Posts