
Learn encryption in C# with AES, RSA, and hashing. Complete developer guide with runnable code examples, best practices, and security pitfalls. Start coding securely today.
Whether you're storing user passwords, protecting API keys, or transmitting sensitive data, encryption in C# is a skill every .NET developer needs in 2026. The good news? The .NET base class library ships with battle-tested cryptography primitives in the System.Security.Cryptography namespace, so you rarely need third-party packages. The bad news? It's surprisingly easy to use them incorrectly and ship insecure code that looks safe.
This complete developer guide covers the three pillars of applied cryptography — AES encryption in C# (symmetric), RSA encryption in C# (asymmetric), and C# hashing (one-way) — with practical, runnable code examples. More importantly, we'll explain why each technique works the way it does, so you can make secure decisions instead of copy-pasting Stack Overflow snippets that leak data.
Encryption in C#: The Three Categories You Must Know
Before writing a single line of code, you need to understand that "encryption" is an umbrella term covering distinct tools for distinct jobs. Choosing the wrong one is the most common — and most dangerous — mistake in C# cryptography.
- Symmetric encryption (AES): One shared secret key encrypts and decrypts. It's extremely fast and ideal for bulk data — files, database columns, message payloads.
- Asymmetric encryption (RSA): A public/private key pair. The public key encrypts; only the private key decrypts. Slower, but solves the key-distribution problem and enables digital signatures.
- Hashing (SHA-256, PBKDF2): A one-way fingerprint. You can never reverse it back to the original. Used for passwords and integrity checks — not encryption at all, despite often being lumped in.
A simple rule of thumb: if you ever need the original data back, you want encryption (AES or RSA). If you only need to verify data without recovering it — like checking a password — you want hashing.
AES Encryption in C#: The Workhorse of Symmetric Cryptography
AES (Advanced Encryption Standard) is the most widely used symmetric algorithm in the world, trusted by governments and banks. In modern .NET, the recommended way to do AES encryption in C# is with AES-GCM (Galois/Counter Mode), which provides authenticated encryption — it both encrypts your data and guarantees nobody tampered with it.
The older CipherMode.CBC approach you'll find in many tutorials encrypts data but does not detect tampering, leaving you open to padding-oracle and bit-flipping attacks. Prefer GCM whenever your target framework supports it (.NET Core 3.0+ and .NET 5/6/7/8/9).
using System;
using System.Security.Cryptography;
using System.Text;
public static class AesGcmEncryptor
{
// Returns nonce + tag + ciphertext packed into one Base64 string.
public static string Encrypt(string plainText, byte[] key)
{
byte[] plainBytes = Encoding.UTF8.GetBytes(plainText);
// A 12-byte nonce is the recommended size for AES-GCM.
// CRITICAL: never reuse a nonce with the same key.
byte[] nonce = RandomNumberGenerator.GetBytes(AesGcm.NonceByteSizes.MaxSize);
byte[] tag = new byte[AesGcm.TagByteSizes.MaxSize];
byte[] cipherBytes = new byte[plainBytes.Length];
using var aes = new AesGcm(key, tag.Length);
aes.Encrypt(nonce, plainBytes, cipherBytes, tag);
// Pack: [nonce][tag][ciphertext] so decryption has everything it needs.
byte[] result = new byte[nonce.Length + tag.Length + cipherBytes.Length];
Buffer.BlockCopy(nonce, 0, result, 0, nonce.Length);
Buffer.BlockCopy(tag, 0, result, nonce.Length, tag.Length);
Buffer.BlockCopy(cipherBytes, 0, result, nonce.Length + tag.Length, cipherBytes.Length);
return Convert.ToBase64String(result);
}
public static string Decrypt(string encryptedBase64, byte[] key)
{
byte[] data = Convert.FromBase64String(encryptedBase64);
int nonceSize = AesGcm.NonceByteSizes.MaxSize;
int tagSize = AesGcm.TagByteSizes.MaxSize;
var nonce = data.AsSpan(0, nonceSize);
var tag = data.AsSpan(nonceSize, tagSize);
var cipherBytes = data.AsSpan(nonceSize + tagSize);
byte[] plainBytes = new byte[cipherBytes.Length];
using var aes = new AesGcm(key, tagSize);
// Throws CryptographicException if the data was tampered with.
aes.Decrypt(nonce, cipherBytes, tag, plainBytes);
return Encoding.UTF8.GetString(plainBytes);
}
}
// Usage
byte[] key = RandomNumberGenerator.GetBytes(32); // 256-bit key
string secret = AesGcmEncryptor.Encrypt("Sensitive customer data", key);
Console.WriteLine(secret);
Console.WriteLine(AesGcmEncryptor.Decrypt(secret, key));
Why the nonce and key generation matter so much
Notice we use RandomNumberGenerator.GetBytes for both the key and the nonce. This is non-negotiable. Never use System.Random for cryptographic values — it's predictable and seeded from the clock, which means an attacker can reproduce your "random" keys.
The single most catastrophic mistake in AES-GCM is nonce reuse. Encrypting two different messages with the same key and the same nonce completely breaks the security guarantees and can leak the key. Always generate a fresh random nonce for every single encryption operation, as the code above does.
RSA Encryption in C#: Solving the Key Distribution Problem
AES is fast, but it has a chicken-and-egg problem: how do you securely share the secret key with the other party in the first place? That's where RSA encryption in C# shines. With RSA, you freely publish your public key. Anyone can encrypt a message to you, but only your secret private key can decrypt it.
Here's a clean, modern implementation using OAEP padding with SHA-256 — the secure default. Avoid the legacy RSAEncryptionPadding.Pkcs1 padding for new code, as it's vulnerable to certain oracle attacks.
using System;
using System.Security.Cryptography;
using System.Text;
public static class RsaEncryptor
{
public static (string publicKey, string privateKey) GenerateKeys()
{
using var rsa = RSA.Create(2048); // 2048-bit minimum; 3072+ for long-term data
string publicKey = Convert.ToBase64String(rsa.ExportRSAPublicKey());
string privateKey = Convert.ToBase64String(rsa.ExportRSAPrivateKey());
return (publicKey, privateKey);
}
public static string Encrypt(string plainText, string publicKeyBase64)
{
using var rsa = RSA.Create();
rsa.ImportRSAPublicKey(Convert.FromBase64String(publicKeyBase64), out _);
byte[] encrypted = rsa.Encrypt(
Encoding.UTF8.GetBytes(plainText),
RSAEncryptionPadding.OaepSHA256);
return Convert.ToBase64String(encrypted);
}
public static string Decrypt(string cipherBase64, string privateKeyBase64)
{
using var rsa = RSA.Create();
rsa.ImportRSAPrivateKey(Convert.FromBase64String(privateKeyBase64), out _);
byte[] decrypted = rsa.Decrypt(
Convert.FromBase64String(cipherBase64),
RSAEncryptionPadding.OaepSHA256);
return Encoding.UTF8.GetString(decrypted);
}
}
// Usage
var (pub, priv) = RsaEncryptor.GenerateKeys();
string cipher = RsaEncryptor.Encrypt("Hello from RSA", pub);
Console.WriteLine(RsaEncryptor.Decrypt(cipher, priv));
The hybrid encryption pattern (what the pros actually do)
RSA has a hard limit: a 2048-bit key can only encrypt about 190 bytes with OAEP-SHA256. You cannot RSA-encrypt a large file. The industry-standard solution is hybrid encryption, which is exactly how HTTPS/TLS works:
- Generate a random AES key and encrypt your large payload with fast AES-GCM.
- Encrypt that small AES key with the recipient's RSA public key.
- Send both. The recipient uses their RSA private key to recover the AES key, then decrypts the payload.
This gives you the speed of AES and the key-distribution magic of RSA — the best of both worlds.
C# Hashing: Passwords and Data Integrity Done Right
Now for the technique developers most often get wrong. C# hashing is one-way: SHA256 turns input into a fixed fingerprint that can't be reversed. It's perfect for verifying file integrity:
using System;
using System.Security.Cryptography;
using System.Text;
string ComputeSha256(string input)
{
byte[] bytes = SHA256.HashData(Encoding.UTF8.GetBytes(input));
return Convert.ToHexString(bytes).ToLowerInvariant();
}
Console.WriteLine(ComputeSha256("verify this file's integrity"));
Never use plain SHA-256 for passwords
Here is the most important security lesson in this entire guide: do not hash passwords with plain SHA-256 or MD5. They are designed to be fast, which means an attacker with a stolen database can try billions of guesses per second using a GPU. For passwords you need a deliberately slow, salted algorithm like PBKDF2, bcrypt, or Argon2.
.NET ships PBKDF2 built in via Rfc2898DeriveBytes. Always use a unique random salt per password and a high iteration count:
using System;
using System.Security.Cryptography;
public static class PasswordHasher
{
private const int SaltSize = 16; // 128-bit salt
private const int HashSize = 32; // 256-bit hash
private const int Iterations = 600_000; // OWASP 2023+ guidance for PBKDF2-SHA256
public static string HashPassword(string password)
{
byte[] salt = RandomNumberGenerator.GetBytes(SaltSize);
byte[] hash = Rfc2898DeriveBytes.Pbkdf2(
password, salt, Iterations, HashAlgorithmName.SHA256, HashSize);
return $"{Iterations}.{Convert.ToBase64String(salt)}.{Convert.ToBase64String(hash)}";
}
public static bool Verify(string password, string stored)
{
string[] parts = stored.Split('.');
int iterations = int.Parse(parts[0]);
byte[] salt = Convert.FromBase64String(parts[1]);
byte[] expected = Convert.FromBase64String(parts[2]);
byte[] actual = Rfc2898DeriveBytes.Pbkdf2(
password, salt, iterations, HashAlgorithmName.SHA256, expected.Length);
// Constant-time comparison prevents timing attacks.
return CryptographicOperations.FixedTimeEquals(actual, expected);
}
}
// Usage
string stored = PasswordHasher.HashPassword("P@ssw0rd!");
Console.WriteLine(PasswordHasher.Verify("P@ssw0rd!", stored)); // True
Console.WriteLine(PasswordHasher.Verify("wrong", stored)); // False
Notice CryptographicOperations.FixedTimeEquals. A naive == comparison can leak information through how long it takes to return, enabling a timing attack. The constant-time comparison closes that hole.
Best Practices and Common Pitfalls in C# Cryptography
Following these rules will put you ahead of the majority of production .NET code:
- Use authenticated encryption (AES-GCM). If you only encrypt without authenticating, attackers can silently tamper with ciphertext.
- Never hard-code keys. Keys in source control or appsettings.json are a breach waiting to happen. Use a secrets manager — Azure Key Vault, AWS KMS, or .NET user-secrets in development.
- Always use
RandomNumberGeneratorfor keys, salts, and nonces — neverSystem.Random. - Don't invent your own algorithms. Stick to AES, RSA, and vetted KDFs. Custom "encryption" is almost always trivially breakable.
- Rotate keys and store an algorithm/version identifier alongside ciphertext so you can migrate later without breaking old data.
- Salt every password individually. A shared salt defeats the purpose and re-enables rainbow tables.
- Dispose of crypto objects with
usingto release native resources promptly.
Conclusion: Key Takeaways for Encryption in C#
Mastering encryption in C# comes down to picking the right tool for the job and respecting the secure defaults the .NET platform gives you. Let's recap the essentials:
- AES-GCM is your default for fast, authenticated symmetric encryption of files and bulk data.
- RSA with OAEP-SHA256 solves key distribution and powers the hybrid encryption pattern for large payloads.
- PBKDF2 (or bcrypt/Argon2) — never plain SHA-256 — is mandatory for password storage, with a unique salt and high iteration count.
- Generate all keys, salts, and nonces with
RandomNumberGenerator, never reuse a GCM nonce, and store secrets outside your codebase.
Cryptography is unforgiving — small mistakes create big breaches. But by leaning on .NET's built-in primitives and the patterns in this guide, you can ship secure, production-grade C# code with confidence. Bookmark this guide, drop the code samples into your next project, and you'll have AES, RSA, and hashing covered the right way. Happy (secure) coding!
Your go-to resource for C#, .NET, and modern software development. Follow along for daily tutorials, tips, and real-world examples.
Comments
Post a Comment