Cryptography in C# — Complete Guide
In this tutorial, you will learn about Cryptography in C#. We cover key concepts, practical examples, and best practices to help you master this topic.
Hook
Cryptography protects sensitive data at rest and in transit. C# provides a comprehensive set of cryptographic APIs in the System.Security.Cryptography namespace. Understanding when to hash, encrypt, or sign data is essential for building secure .NET applications.
Learning Path
graph LR A[Cryptography] --> B[Hashing] A --> C[Symmetric Encryption] A --> D[Asymmetric Encryption] B --> E[Password Hashing] C --> F[AES] D --> G[RSA] style A fill:#4a90d9,color:#fff style B fill:#4a90d9,color:#fff style C fill:#4a90d9,color:#fff style D fill:#4a90d9,color:#fff style E fill:#4a90d9,color:#fff style F fill:#4a90d9,color:#fff style G fill:#4a90d9,color:#fff
Hashing
Hashing produces a fixed-size fingerprint of data. It is one-way -- you cannot reverse a hash.
using System;
using System.Security.Cryptography;
using System.Text;
public static class HashExamples
{
public static string ComputeSha256Hash(string input)
{
byte[] bytes = SHA256.HashData(Encoding.UTF8.GetBytes(input));
return Convert.ToHexString(bytes).ToLowerInvariant();
}
public static string ComputeMd5Hash(string input)
{
// MD5 is cryptographically broken - only use for checksums
byte[] bytes = MD5.HashData(Encoding.UTF8.GetBytes(input));
return Convert.ToHexString(bytes).ToLowerInvariant();
}
public static void CompareHashes()
{
string data = "Hello, Cryptography!";
Console.WriteLine($"SHA-256: {ComputeSha256Hash(data)}");
Console.WriteLine($"SHA-256 (same): {ComputeSha256Hash(data)}");
Console.WriteLine($"SHA-256 (different): {ComputeSha256Hash("Hello, Crypto!")}");
}
}
Output:
SHA-256: 7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069
SHA-256 (same): 7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069
SHA-256 (different): c28dcb75a2c5e2d4a4e7d7e8f7c8c9d0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d
Password Hashing with Rfc2898DeriveBytes (PBKDF2)
Never hash passwords with plain SHA. Use a key derivation function.
public class PasswordHasher
{
private const int SaltSize = 16; // 128 bits
private const int HashSize = 32; // 256 bits
private const int Iterations = 100000;
public static string Hash(string password)
{
byte[] salt = RandomNumberGenerator.GetBytes(SaltSize);
byte[] hash = Rfc2898DeriveBytes.Pbkdf2(
password,
salt,
Iterations,
HashAlgorithmName.SHA256,
HashSize
);
return $"{Convert.ToBase64String(salt)}.{Convert.ToBase64String(hash)}";
}
public static bool Verify(string password, string hashedPassword)
{
string[] parts = hashedPassword.Split('.');
byte[] salt = Convert.FromBase64String(parts[0]);
byte[] storedHash = Convert.FromBase64String(parts[1]);
byte[] computedHash = Rfc2898DeriveBytes.Pbkdf2(
password,
salt,
Iterations,
HashAlgorithmName.SHA256,
HashSize
);
return CryptographicOperations.FixedTimeEquals(storedHash, computedHash);
}
}
// Usage
string hash = PasswordHasher.Hash("MySecurePassword123!");
Console.WriteLine($"Hash: {hash}");
Console.WriteLine($"Verify: {PasswordHasher.Verify("MySecurePassword123!", hash)}");
Console.WriteLine($"Verify wrong: {PasswordHasher.Verify("wrong", hash)}");
Symmetric Encryption (AES)
Use AES for encrypting data with a single key.
public static class AesEncryption
{
public static (byte[] ciphertext, byte[] iv, byte[] key) Encrypt(string plaintext)
{
using var aes = Aes.Create();
aes.GenerateKey();
aes.GenerateIV();
byte[] ciphertext = aes.EncryptCbc(
Encoding.UTF8.GetBytes(plaintext),
aes.IV,
PaddingMode.PKCS7
);
return (ciphertext, aes.IV, aes.Key);
}
public static string Decrypt(byte[] ciphertext, byte[] iv, byte[] key)
{
using var aes = Aes.Create();
byte[] plaintext = aes.DecryptCbc(
ciphertext,
iv,
PaddingMode.PKCS7
);
return Encoding.UTF8.GetString(plaintext);
}
}
// Usage
var (ciphertext, iv, key) = AesEncryption.Encrypt("Sensitive data");
string decrypted = AesEncryption.Decrypt(ciphertext, iv, key);
Console.WriteLine($"Decrypted: {decrypted}");
Asymmetric Encryption (RSA)
RSA uses a public/private key pair.
public static class RsaEncryption
{
public static (string publicKey, string privateKey) GenerateKeys()
{
using var rsa = RSA.Create(2048);
return (
rsa.ToXmlString(false), // Public only
rsa.ToXmlString(true) // Public and private
);
}
public static byte[] Encrypt(string plaintext, string publicKey)
{
using var rsa = RSA.Create();
rsa.FromXmlString(publicKey);
return rsa.Encrypt(
Encoding.UTF8.GetBytes(plaintext),
RSAEncryptionPadding.OaepSHA256
);
}
public static string Decrypt(byte[] ciphertext, string privateKey)
{
using var rsa = RSA.Create();
rsa.FromXmlString(privateKey);
byte[] plaintext = rsa.Decrypt(ciphertext, RSAEncryptionPadding.OaepSHA256);
return Encoding.UTF8.GetString(plaintext);
}
}
// Usage
var (publicKey, privateKey) = RsaEncryption.GenerateKeys();
byte[] encrypted = RsaEncryption.Encrypt("Secret message", publicKey);
string decrypted = RsaEncryption.Decrypt(encrypted, privateKey);
Console.WriteLine($"Decrypted: {decrypted}");
Digital Signatures
Sign data to verify authenticity and integrity.
public static class DigitalSignatures
{
public static (string publicKey, string privateKey) GenerateKeys()
{
using var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
return (
ecdsa.ToXmlString(false),
ecdsa.ToXmlString(true)
);
}
public static byte[] Sign(string data, string privateKey)
{
using var ecdsa = ECDsa.Create();
ecdsa.FromXmlString(privateKey);
return ecdsa.SignData(
Encoding.UTF8.GetBytes(data),
HashAlgorithmName.SHA256
);
}
public static bool Verify(string data, byte[] signature, string publicKey)
{
using var ecdsa = ECDsa.Create();
ecdsa.FromXmlString(publicKey);
return ecdsa.VerifyData(
Encoding.UTF8.GetBytes(data),
signature,
HashAlgorithmName.SHA256
);
}
}
// Usage
var (pubKey, privKey) = DigitalSignatures.GenerateKeys();
byte[] signature = DigitalSignatures.Sign("Important document", privKey);
bool valid = DigitalSignatures.Verify("Important document", signature, pubKey);
Console.WriteLine($"Signature valid: {valid}");
Secure Random Numbers
Use RandomNumberGenerator instead of Random for security-sensitive data.
// Secure random bytes
byte[] buffer = new byte[32];
RandomNumberGenerator.Fill(buffer);
// Secure random integer
int secureRandom = RandomNumberGenerator.GetInt32(1, 101);
// Generate a secure token
string GenerateToken(int length = 32)
{
return Convert.ToHexString(RandomNumberGenerator.GetBytes(length));
}
Console.WriteLine($"Token: {GenerateToken()}");
Common Mistakes
Using MD5 or SHA-1 for security: Both are cryptographically broken. Use SHA-256 or SHA-512.
Hardcoding encryption keys: Store keys in Azure Key Vault, environment variables, or the data protection API.
ECB mode encryption: Never use AES in ECB mode. Use CBC or GCM mode for proper security.
Comparing hashes with ==: Use
CryptographicOperations.FixedTimeEqualsto prevent timing attacks.Rolling your own cryptography: Always use built-in .NET cryptographic libraries. Custom crypto is extremely error-prone.
Practice Questions
Implement a file encryption tool that encrypts a file using AES-GCM and saves the IV and ciphertext to a new file.
Create a certificate generation utility that creates self-signed X509 certificates using
CertificateRequest.Write a password strength estimator that checks entropy and common password patterns.
Challenge: Build a secure messaging system where messages are encrypted with the recipient's RSA public key and signed with the sender's ECDSA private key.
FAQ
Mini Project: Secure File Encryptor
Build a command-line file encryption tool using AES-GCM.
using System;
using System.Security.Cryptography;
public static class FileEncryptor
{
public static void EncryptFile(string inputPath, string outputPath, byte[] key)
{
byte[] plaintext = File.ReadAllBytes(inputPath);
byte[] nonce = RandomNumberGenerator.GetBytes(AesGcm.NonceByteSizes.MaxSize);
byte[] ciphertext = new byte[plaintext.Length];
byte[] tag = new byte[AesGcm.TagByteSizes.MaxSize];
using var aes = new AesGcm(key);
aes.Encrypt(nonce, plaintext, ciphertext, tag);
// Write: nonce + tag + ciphertext
using var output = File.Create(outputPath);
output.Write(nonce);
output.Write(tag);
output.Write(ciphertext);
Console.WriteLine($"File encrypted to {outputPath}");
}
public static void DecryptFile(string inputPath, string outputPath, byte[] key)
{
byte[] fileData = File.ReadAllBytes(inputPath);
int nonceSize = AesGcm.NonceByteSizes.MaxSize;
int tagSize = AesGcm.TagByteSizes.MaxSize;
byte[] nonce = fileData[..nonceSize];
byte[] tag = fileData[nonceSize..(nonceSize + tagSize)];
byte[] ciphertext = fileData[(nonceSize + tagSize)..];
byte[] plaintext = new byte[ciphertext.Length];
using var aes = new AesGcm(key);
aes.Decrypt(nonce, ciphertext, tag, plaintext);
File.WriteAllBytes(outputPath, plaintext);
Console.WriteLine($"File decrypted to {outputPath}");
}
public static byte[] GenerateKey()
{
byte[] key = new byte[32]; // 256-bit key
RandomNumberGenerator.Fill(key);
return key;
}
}
// Usage
byte[] key = FileEncryptor.GenerateKey();
File.WriteAllText("secret.txt", "This is confidential data.");
FileEncryptor.EncryptFile("secret.txt", "secret.enc", key);
FileEncryptor.DecryptFile("secret.enc", "secret_decrypted.txt", key);
Console.WriteLine(File.ReadAllText("secret_decrypted.txt"));
Output:
File encrypted to secret.enc
File decrypted to secret_decrypted.txt
This is confidential data.
Cryptography in C# is accessible through well-designed APIs that follow industry best practices. By using the built-in cryptographic primitives in .NET, you can protect sensitive data without needing to be a cryptography expert.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro