Skip to content

Cryptography in C# — Complete Guide

DodaTech Updated 2026-06-28 6 min read

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

  1. Using MD5 or SHA-1 for security: Both are cryptographically broken. Use SHA-256 or SHA-512.

  2. Hardcoding encryption keys: Store keys in Azure Key Vault, environment variables, or the data protection API.

  3. ECB mode encryption: Never use AES in ECB mode. Use CBC or GCM mode for proper security.

  4. Comparing hashes with ==: Use CryptographicOperations.FixedTimeEquals to prevent timing attacks.

  5. Rolling your own cryptography: Always use built-in .NET cryptographic libraries. Custom crypto is extremely error-prone.

Practice Questions

  1. Implement a file encryption tool that encrypts a file using AES-GCM and saves the IV and ciphertext to a new file.

  2. Create a certificate generation utility that creates self-signed X509 certificates using CertificateRequest.

  3. Write a password strength estimator that checks entropy and common password patterns.

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

What is the difference between hashing and encryption?

Hashing is one-way and produces a fixed-size output. Encryption is two-way and produces variable-size ciphertext that can be decrypted with the correct key.

Which symmetric encryption algorithm should I use?

Use AES with GCM mode (Authenticated Encryption) for both confidentiality and integrity. AES-GCM is available via AesGcm class.

What is the recommended key size for RSA?

Use 2048-bit keys minimum. 4096-bit keys provide stronger security but are slower. For ECDSA, use P-256 or P-384 curves.

How do I store cryptographic keys safely?

Use the Data Protection API (IDataProtector) for application-level encryption. For secrets management, use Azure Key Vault or similar services.

Is RandomNumberGenerator thread-safe?

Yes, RandomNumberGenerator is thread-safe. The static methods can be called from multiple threads without issue.

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