Phong Yen Hou, Justin

A recently graduated Full Stack Developer 🖥️ eager to take on new challenges.

Title:

Password-Based AES Encryption and Decryption in C#

Overview

In this article, we demonstrate how to securely encrypt and decrypt data using the Advanced Encryption Standard (AES) in C#, combined with a password-based key derivation function (PBKDF2). This approach allows you to convert a user-provided password into a strong cryptographic key, enabling safe encryption of sensitive information without manually managing keys.

How this works?

This is a symmetric encryption and decryption using the AES algorithm combined with a password-based key derivation function PBKDF2. Symmetric encryption means that the same key is used to both encrypt and decrypt the data. Instead of hardcoding or directly using a password as a key (which is insecure), we derive a strong, secure key from the password using PBKDF2. This adds cryptographic strength by applying a hashing function with a random salt and multiple iterations, making brute-force attacks much more difficult.

First import the Cryptography library.

using System.Security.Cryptography;

Then we implement the encryption using the library. In this method, we generate two random values: a salt and an IV (Initialization Vector), each 16 bytes long. The salt is used in the PBKDF2 function to derive a unique 256-bit AES key from the password. This ensures that even if two users use the same password, their encryption keys will be different. The IV ensures that the same plaintext will produce different ciphertexts each time it’s encrypted, adding another layer of security. Both the salt and IV are written to the start of the output so they can be reused during decryption.

public static string Encrypt(string plainText, string password)
{
    using (Aes aesAlg = Aes.Create())
    {
        byte[] salt = new byte[16];
        byte[] iv = new byte[16];
        using (var rng = RandomNumberGenerator.Create())
        {
            rng.GetBytes(salt);
            rng.GetBytes(iv);
        }

        using (var keyDerivation = new Rfc2898DeriveBytes(password, salt, Iterations, HashAlgorithmName.SHA256))
        {
            aesAlg.Key = keyDerivation.GetBytes(32);
            aesAlg.IV = iv;

            using (var ms = new MemoryStream())
            {
                ms.Write(salt, 0, salt.Length);
                ms.Write(iv, 0, iv.Length);

                using (var cs = new CryptoStream(ms, aesAlg.CreateEncryptor(), CryptoStreamMode.Write))
                using (var sw = new StreamWriter(cs))
                {
                    sw.Write(plainText);
                }

                return Convert.ToBase64String(ms.ToArray());
            }
        }
    }
}

And this is decryption. The decryption function reverses the encryption steps. First, it Base64-decodes the input and extracts the original salt and IV. These are then used with PBKDF2 to regenerate the same key that was used for encryption. With the key and IV restored, the AES algorithm can decrypt the remaining encrypted bytes. The final output is the original plaintext. This entire design ensures that only someone with the correct password can successfully decrypt the data, even though the salt and IV are stored in plain form alongside the ciphertext.

public static string Decrypt(string cipherText, string password)
{
    cipherText = Uri.UnescapeDataString(cipherText);

    int padding = cipherText.Length % 4;
    if (padding != 0)
    {
        cipherText = cipherText.PadRight(cipherText.Length + (4 - padding), '=');
    }

    byte[] fullCipher = Convert.FromBase64String(cipherText);

    byte[] salt = new byte[16];
    byte[] iv = new byte[16];
    Array.Copy(fullCipher, 0, salt, 0, salt.Length);
    Array.Copy(fullCipher, salt.Length, iv, 0, iv.Length);

    using (var aesAlg = Aes.Create())
    {
        using (var keyDerivation = new Rfc2898DeriveBytes(password, salt, Iterations, HashAlgorithmName.SHA256))
        {
            aesAlg.Key = keyDerivation.GetBytes(32);
            aesAlg.IV = iv;

            using (var ms = new MemoryStream(fullCipher, salt.Length + iv.Length, fullCipher.Length - salt.Length - iv.Length))
            using (var cs = new CryptoStream(ms, aesAlg.CreateDecryptor(), CryptoStreamMode.Read))
            using (var sr = new StreamReader(cs))
            {
                return sr.ReadToEnd();
            }
        }
    }
}

When can we use this encryption?

This kind of AES encryption with a password-derived key is ideal when you need to securely store or transmit sensitive data without having to manage or share symmetric keys manually. It is especially useful in the following scenarios:

  • Secure local storage: You can encrypt files, configuration data, or user credentials stored on disk. Only users who know the password can decrypt the data, making it safe even if the file is exposed.
  • Web applications: You can encrypt sensitive data (like tokens or form data) in cookies, URLs, or hidden fields. Using AES with PBKDF2 ensures strong encryption while relying only on a shared password.
  • Password-protected documents: Applications that allow users to encrypt and protect files with a password—like note-taking apps, PDF generators, or personal finance tools—can use this pattern for user-friendly security.
  • Backup encryption: If you're saving backups of a database or user data, encrypting the data using this method ensures that even if the backup is stolen, it cannot be read without the password.
  • Offline encryption tools: Since this approach doesn't require a server or key management infrastructure, it’s well-suited for command-line tools and offline utilities that need lightweight, strong encryption.

Hastag:

  • #CSharp
  • #Self-Learn