ASP.NET Core 3.1 - AES Cipher

Ken Haggerty
Created 05/23/2020 - Updated 09/02/2021 01:55

This article will describe the AES Cipher. I will assume you have downloaded the FREE ASP.NET Core 3.1 - FIDO Utilities Project or created a new ASP.NET Core 3.1 Razor Pages project. I won't use Identity or Individual User Accounts. See Tutorial: Get started with Razor Pages in ASP.NET Core.

FIDO Utilities Project and Article Series

The ASP.NET Core 3.1 - FIDO Utilities Project (FUP) is a collection of utilities I use in the ASP.NET Core 5.0 - Users Without Passwords Project (UWPP). I have upgraded the FUP with Bootstrap v5 and Bootstrap Native v4. FUP version 2.1 features SingleUser Authentication, Admin Page with Send Email Tests, ExceptionEmailerMiddleware, Bootstrap v5 Detection JavaScript, Copy and Paste Demo, Offcanvas Partial Demo, and Path QR Code Demo. The SMTP Settings Tester is updated and now located in the authorized Pages > Admin folder. The EmailSender and AES Cipher Demo are also updated. Registered users can download the source code for free on KenHaggerty. Com at Manage > Assets. The UWPP is published at Fido. KenHaggerty. Com.

Update 09/01/2021

I enhanced the AesCrypto class to generate new 128, 192, and 256 bit random keys as Base64 or Hexadecimal strings. I extended the Encrypt and Decrypt methods to include initialization vector, key size, and format parameters. The FUP does not require SQL Server but includes an empty ApplicationDbContext and an encrypted DefaultConnection in app settings to demonstrate encryption for SQL Server ConnectionStrings. The AesCrypto class includes public static readonly properties CipherKey and CipherIV. Cipher text requires the same key and iv to decrypt back to plain text. I updated the article with the new AesCrypto class and ConnectionStrings' DefaultConnection decryption. See ASP.NET Core 5.0 - AppSettings Encryption.

I included an AES Cipher demo in the free FIDO Utilities Project. I also published the demo to Ken's Demo Site/ Demos/ AES Cipher.

AES Cipher
AES Cipher Mobile

The Users Without Passwords Project implements FIDO2 authenticators. The user logs in with a device rather than a password. The Users Without Identity Project implements a password hasher. Both use encryption to verify the user. With this fresh experience I decided to implement a cipher for database connection strings in appsettings. json. My research found Encryption And Decryption Using A Symmetric Key In C# which I used to developed my AesCrypto class. Vivek Kumar's example uses a hardcoded value as the key. My early implementations used a 32 hex character string converted from a new Guid byte array for random keys. See How do you convert a byte array to a hexadecimal string, and vice versa?

SecretKey = BitConverter.ToString(Guid.NewGuid().ToByteArray()).Replace("-", "");

This met my requirements to encrypt the appsettings connection strings. I used code behind and the cipher to generate a key, encrypt the local connection string for appsettings. development. json and the production connection string for appsettings. json. The key was hardcoded and used in Startup.cs > ConfigureServices to decrypt either connection string. The research for this article found more.

The Advanced Encryption Standard (AES), also known by its original name Rijndael is a specification for the encryption of electronic data established by the U.S. National Institute of Standards and Technology (NIST) in 2001.
AES is a subset of the Rijndael block cipher developed by two Belgian cryptographers, Vincent Rijmen and Joan Daemen, who submitted a proposal to NIST during the AES selection process. Rijndael is a family of ciphers with different key and block sizes. Wikipedia - Advanced Encryption Standard

Which lead to more about key sizes and the initialization vector.

AES was chosen as a subset of the family of block ciphers known as Rijndael. That family includes no less than 15 variants, for three possible block sizes (128, 192 and 256 bits) and five possible key sizes (128, 160, 192, 224 and 256 bits). AES, as standardized by NIST, includes only three variants, all with 128-bit blocks, and with keys of 128, 192 or 256 bits. Encrypting using AES-256, can I use 256 bits IV?

I enhanced the AesCrypto class to generate new 128, 192, and 256 bit random keys as Base64 or Hexadecimal strings. I extended the Encrypt and Decrypt methods to include initialization vector, key size, and format parameters.

using System;
using System.IO;
using System.Security.Cryptography;
// Copyright © Ken Haggerty (https://kenhaggerty.com)
// Licensed under the MIT License.
public static class AesCrypto
{
    /// <summary>
    /// The default Key property used to encrypt/decrypt strings.
    /// </summary>
    /// <remarks>
    /// This Key is used for demonstration purposes. You should generate a new Key then delete this remark.
    /// </remarks>
    public static readonly string CipherKey = "64TONHcDshZtqIssUahkHg==";
    /// <summary>
    /// The default initialization vector (IV) property used to encrypt/decrypt strings.
    /// </summary>
    /// <remarks>
    /// This IV is used for demonstration purposes. You should generate a new IV then delete this remark.
    /// </remarks>
    public static readonly string CipherIV = "+EEbo3sI7eLSwBnR47GUjg==";

    /// <summary>
    /// Generates a new random key.
    /// </summary>
    /// <param name="keyByteSize">Key size in bytes.</param>
    /// <param name="useHex">Use Hexadecimal format.</param>
    /// <returns>
    /// A Base64 or Hexadecimal string representing a byte array of random values.
    /// </returns>
    /// <remarks>
    /// The AES Key can be 128 bits = 16 bytes, 192 bits = 24 bytes or 256 bits = 32 bytes.
    /// </remarks>
    /// <exception cref="ArgumentNullException"></exception>
    public static string GetNewRandom(int keyByteSize = 16, bool useHex = false)
    {
        byte[] randomArray = new byte[keyByteSize];
        using var rng = RandomNumberGenerator.Create();
        rng.GetBytes(randomArray);

        if (useHex)
            return BitConverter.ToString(randomArray).Replace("-", "");
        else
            return Convert.ToBase64String(randomArray);
    }

    /// <summary>
    /// Encrypts plain text using the key and initialization vector (iv).
    /// </summary>
    /// <param name="key">The key in Base64 or Hexadecimal string format.</param>
    /// <param name="iv">The initialization vector (iv) in Base64 or Hexadecimal string format.</param>
    /// <param name="plainText">The text to be encrypted.</param>
    /// <param name="keyByteSize">Key size in bytes.</param>
    /// <param name="useHex">Use Hexadecimal format for key and iv.</param>
    /// <returns>
    /// A string AES encrypted using the key and initialization vector (iv).
    /// </returns>
    /// <remarks>
    /// The AES Key can be 128 bits = 16 bytes, 192 bits = 24 bytes or 256 bits = 32 bytes.
    /// </remarks>
    /// <exception cref="ArgumentException"></exception>
    /// <exception cref="ArgumentNullException"></exception>
    /// <exception cref="ArgumentOutOfRangeException"></exception>
    /// <exception cref="FormatException"></exception>
    /// <exception cref="OverflowException"></exception>
    /// <exception cref="ObjectDisposedException"></exception>
    /// <exception cref="NotSupportedException"></exception>
    /// <exception cref="IOException"></exception>
    public static string EncryptString(string key, string iv, string plainText, int keyByteSize = 16, bool useHex = false)
    {
        byte[] keyBytes = new byte[keyByteSize];
        byte[] ivBytes = new byte[16];
        byte[] cipherBytes;

        if (useHex)
        {
            int len = keyByteSize * 2;
            for (int i = 0; i < len; i += 2)
            {
                keyBytes[i / 2] = Convert.ToByte(key.Substring(i, 2), 16);
                if (i < 32)
                    ivBytes[i / 2] = Convert.ToByte(iv.Substring(i, 2), 16);
            }
        }
        else
        {
            keyBytes = Convert.FromBase64String(key);
            ivBytes = Convert.FromBase64String(iv);
        }

        using Aes aes = Aes.Create();
        aes.Key = keyBytes;
        aes.IV = ivBytes;
        ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
        using MemoryStream memoryStream = new MemoryStream();
        using CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write);
        using (StreamWriter streamWriter = new StreamWriter(cryptoStream))
        {
            streamWriter.Write(plainText);
        }
        cipherBytes = memoryStream.ToArray();
        return Convert.ToBase64String(cipherBytes);
    }

    /// <summary>
    /// Decrypts cipher text using the key and initialization vector (iv).
    /// </summary>
    /// <param name="key">The key in Base64 or Hexadecimal string format.</param>
    /// <param name="iv">The initialization vector (iv) in Base64 or Hexadecimal string format.</param>
    /// <param name="cipherText">The encrypted text.</param>
    /// <param name="keyByteSize">Key size in bytes.</param>
    /// <param name="useHex">Use Hexadecimal format for key and iv.</param>
    /// <returns>
    /// A string AES decrypted using the key and initialization vector (iv).
    /// </returns>
    /// <remarks>
    /// The AES Key can be 128 bits = 16 bytes, 192 bits = 24 bytes or 256 bits = 32 bytes.
    /// </remarks>
    /// <exception cref="ArgumentException"></exception>
    /// <exception cref="ArgumentNullException"></exception>
    /// <exception cref="ArgumentOutOfRangeException"></exception>
    /// <exception cref="FormatException"></exception>
    /// <exception cref="OverflowException"></exception>
    /// <exception cref="OutOfMemoryException"></exception>
    /// <exception cref="IOException"></exception>
    public static string DecryptString(string key, string iv, string cipherText, int keyByteSize = 16, bool useHex = false)
    {
        byte[] keyBytes = new byte[keyByteSize];
        byte[] ivBytes = new byte[16];
        byte[] cipherBytes = Convert.FromBase64String(cipherText);

        if (useHex)
        {
            int len = keyByteSize * 2;
            for (int i = 0; i < len; i += 2)
            {
                keyBytes[i / 2] = Convert.ToByte(key.Substring(i, 2), 16);
                if (i < 32)
                    ivBytes[i / 2] = Convert.ToByte(iv.Substring(i, 2), 16);
            }
        }
        else
        {
            keyBytes = Convert.FromBase64String(key);
            ivBytes = Convert.FromBase64String(iv);
        }

        using Aes aes = Aes.Create();
        aes.Key = keyBytes;
        aes.IV = ivBytes;
        ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
        using MemoryStream memoryStream = new MemoryStream(cipherBytes);
        using CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read);
        using StreamReader streamReader = new StreamReader(cryptoStream);
        return streamReader.ReadToEnd();
    }
}

The SQL ConnectionStrings' DefaultConnection is encrypted in appsettings. json and decrypted in Startup ConfigureServices.

appsettings.json
"ConnectionStrings": {
  // Set connectionStringEncrypted in Startup.cs to false to use plain text. See Quickstart.txt.
  //"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=FidoUtilities;Trusted_Connection=True;MultipleActiveResultSets=true;"
  // Remove the escape character (\\ to \) before encryption.
  "DefaultConnection": "k4cy//VMt82xu/RgHqL4bSqqBSj+mamhbQxkr0Gn5nowXKCX+b48mClz+6u4fl3YLI8WGUm7PDguTW1GzBrqU0mXIOi8XxuZ4j0kbsEYNY2SvNH543fF+YZk2wnyo2g7QsmOZniE58YKQ+Gt75Wk6Q=="
},
"ConectionConfigured": false
Startup.cs:
// Set to false to use plain text for appsettings.json's connection string. See Quickstart.txt.
private readonly bool connectionStringEncrypted = true;
Startup.cs > ConfigureServices:
var connectionConfigured = Configuration.GetValue("ConnectionConfigured");
if (connectionConfigured)
{
    var defaultConnection = Configuration.GetConnectionString("DefaultConnection");
    if (connectionStringEncrypted)
        defaultConnection = AesCrypto.DecryptString(AesCrypto.CipherKey, AesCrypto.CipherIV, defaultConnection);

    services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(defaultConnection));
}

Article Tags:

FIDO Validation

Comment Count = 0

Please log in to comment.

Login Register
Logged in users receive web notifications.
Web Notifications