With symmetric key, we use the same key to encrypt and to decrypt. The most popular symmetric key methods are AES and ChaCha20. In its pursest form, AES is a block cipher, with 128-bit block sizes. ChaCha20 is a stream cipher. With a block cipher we need padding, and there is no padding required for a stream cipher. The main modes that we get with symmetric key are ECB (Electronic Code Book), CBC (Cipher Block Chain), CTR (Counter) and GCM (Galois Counter Mode), and with either a 128-bit, a 192-bit or a 256-bit encryption key. In this case, we will use CNG to create a named key and then use the default AES mode of CBC and a 128-bit key. Overall, CNG creates a named key, and which is stored securely on the system, and can be recalled at a future time.
Cryptography Next Generation (CNG): AES with .NET and C# |
Coding
First we create a folder named "dotnet_aes", and then go into that folder.We can create a Dotnet console project for .NET 8.0 with:
dotnet new console --framework net8.0
This produces a Csproject file of:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net8.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> </Project>
First we can convert our message to a byte array:
var msg=Encoding.ASCII.GetBytes(message);
Next we can create our key storage provider:
CngProvider keyStorageProvider = CngProvider.MicrosoftSoftwareKeyStorageProvider; CngKeyCreationParameters keyCreationParameters = new CngKeyCreationParameters();
In this case, the key will be protected from being viewed and copied. If we want to view and copy it, we can set these properties:
keyCreationParameters.ExportPolicy = CngExportPolicies.AllowPlaintextExport; keyCreationParameters.Provider = keyStorageProvider;
Each key has a name on the system. If the key name does not exist, it will be created with:
if (CngKey.Exists("SymKey1").Equals(false)) { CngKey.Create(new CngAlgorithm("AES"), "SymKey1", keyCreationParameters); }
In this case, the name of the key in this case will be "SymKey1". Next, we can encrypt our message:
Aes aes = new AesCng("SymKey1", keyStorageProvider); aes.IV = nonce; var encryptor = aes.CreateEncryptor(); byte[] ciphertextBytes = encryptor.TransformFinalBlock(msg, 0, msg.Length);
To decrypt, we can then use the TransformFinalBlock() method:
var decryptor = aes.CreateDecryptor(); byte[] plain = decryptor.TransformFinalBlock(ciphertextBytes, 0, ciphertextBytes.Length);
The following is the coding:
namespace CngRSA { using System.Security.Cryptography; using System.Text; class Program { static void Main(string[] args) { var message="Hello"; var iv="00112233445566770011223344556677"; try { var padding = RSASignaturePadding.Pkcs1; if (args.Length >0) message=args[0]; if (args.Length >1) iv=args[1]; byte[] nonce = new byte[iv.Length/2]; Array.Copy(Convert.FromHexString(iv), nonce, iv.Length/2); var msg=Encoding.ASCII.GetBytes(message); CngProvider keyStorageProvider = CngProvider.MicrosoftSoftwareKeyStorageProvider; CngKeyCreationParameters keyCreationParameters = new CngKeyCreationParameters(); keyCreationParameters.ExportPolicy = CngExportPolicies.AllowPlaintextExport; keyCreationParameters.Provider = keyStorageProvider; if (CngKey.Exists("SymKey1").Equals(false)) { CngKey.Create(new CngAlgorithm("AES"), "SymKey1", keyCreationParameters); } Aes aes = new AesCng("SymKey1", keyStorageProvider); aes.IV = nonce; var encryptor = aes.CreateEncryptor(); byte[] ciphertextBytes = encryptor.TransformFinalBlock(msg, 0, msg.Length); var decryptor = aes.CreateDecryptor(); byte[] plain = decryptor.TransformFinalBlock(ciphertextBytes, 0, ciphertextBytes.Length); Console.WriteLine("Message:\t\t{0}",message); Console.WriteLine("IV:\t\t\t{0}",Convert.ToHexString(nonce)); Console.WriteLine("Cipher:\t\t\t{0}",Convert.ToHexString(ciphertextBytes)); Console.WriteLine("Decrypted:\t\t{0}",Encoding.UTF8.GetString(plain)); Console.WriteLine("\nBlocksize:\t\t{0}",aes.BlockSize); Console.WriteLine("Key:\t\t\t{0}",Convert.ToHexString(aes.Key)); Console.WriteLine("IV:\t\t\t{0}",Convert.ToHexString(aes.IV)); Console.WriteLine("Padding:\t\t{0}",aes.Padding); aes.Dispose(); } catch (Exception e) { Console.WriteLine("Error: {0}",e.Message); } } } }
A sample run is:
Message: Hello 12345 IV: 00112233445566770011223344556677 Cipher: 9D0DAA414A9D45168D2724982D418D9A Decrypted: Hello 12345 Blocksize: 128 Key: 5A87C97BCED0B0CFFA98098C70AE88D7AA0F0B5BD7DC9F38E71118FA370FB328 IV: 00112233445566770011223344556677 Padding: PKCS7 Mode: CBC
We can see that the key is "5A87C97BCED0B0CFFA98098C70AE88D7AA0F0B5BD7DC9F38E71118FA370FB328", and with a salt value of "00112233445566770011223344556677". In production, we could use a random salt value, and which we can generate with the aes.GenerateIV() method. For a given data input, a fixed symmetric key and a fixed IV value, we will always get the same ciphertext.