The linked code works on my machine with .NET Framework 4.6.2 and above. Another helpful answer on this topic (but for RSA) can be found here.
The following method creates a persistent 32 bytes key (AES-256), if it does not exist, or loads a persistent key, if it already exists, and performs an encryption:
private static string encryptWithKey(string plaintext, string keyName, string iv)
{
CngProvider keyStorageProvider = CngProvider.MicrosoftSoftwareKeyStorageProvider;
if (!CngKey.Exists(keyName, keyStorageProvider))
{
CngKeyCreationParameters keyCreationParameters = new CngKeyCreationParameters()
{
Provider = keyStorageProvider
};
CngKey.Create(new CngAlgorithm("AES"), keyName, keyCreationParameters);
}
Aes aes = new AesCng(keyName, keyStorageProvider);
aes.IV = Encoding.UTF8.GetBytes(iv);
var encryptor = aes.CreateEncryptor();
byte[] plaintextBytes = Encoding.UTF8.GetBytes(plaintext);
byte[] ciphertextBytes = encryptor.TransformFinalBlock(plaintextBytes, 0, plaintextBytes.Length);
aes.Dispose();
return Convert.ToBase64String(ciphertextBytes);
}
Please note that the key is not exportable by default, so that aes.Key
generates a System.Security.Cryptography.CryptographicException: The requested operation is not supported, i.e. e.g.:
var encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
would throw that exception. Maybe this or something similar is the reason for the exception in your environment.
The corresponding counterpart for decryption is:
private static string decryptWithKey(string ciphertext, string keyName, string iv)
{
CngProvider keyStorageProvider = CngProvider.MicrosoftSoftwareKeyStorageProvider;
if (!CngKey.Exists(keyName, keyStorageProvider))
{
throw new Exception("Error: key doesn't exist...");
}
Aes aes = new AesCng(keyName, keyStorageProvider);
aes.IV = Encoding.UTF8.GetBytes(iv);
var decryptor = aes.CreateDecryptor();
byte[] ciphertextBytes = Convert.FromBase64String(ciphertext);
byte[] plaintextBytes = decryptor.TransformFinalBlock(ciphertextBytes, 0, ciphertextBytes.Length);
aes.Dispose();
return Encoding.UTF8.GetString(plaintextBytes);
}
The following code:
string plaintext = "The quick brown fox jumps over the lazy dog";
string iv = "0123456789012345";
string keyName = "keyName";
string ciphertext1 = encryptWithKey(plaintext, keyName, iv);
Console.WriteLine(ciphertext1);
string ciphertext2 = encryptWithKey(plaintext, keyName, iv);
Console.WriteLine(ciphertext2);
string decryptedText1 = decryptWithKey(ciphertext1, keyName, iv);
Console.WriteLine(decryptedText1);
performs two encryptions, generating the same ciphertext in both cases: With the first call the key is created and persisted, with the second call the persisted key is loaded. The identical ciphertext proves that the same key was used for encryption.
So that the key can also be exported, the following modification is necessary:
CngKeyCreationParameters keyCreationParameters = new CngKeyCreationParameters()
{
ExportPolicy = CngExportPolicies.AllowPlaintextExport,
Provider = keyStorageProvider
};
Now the key can be accessed e.g. with aes.Key
.
Please note that the static IV is only used to allow a comparison of the ciphertexts of both encryptions. In practice, of course, a random IV must be applied for each encryption.
For completeness it should be mentioned that the code is compatible with .NET Core (tested for .NET Core 3.1)
The key persistence works analogously for 3DES, i.e. for TripleDESCng
. As identifier 3DES
must be used (an invalid identifier also triggers the CryptographicException posted above), i.e.:
CngKey.Create(new CngAlgorithm("3DES"), keyName, keyCreationParameters);
This creates and persists a 24 bytes key (3TDEA). Note, however, that in contrast to the current standard AES, the outdated and comparably inperformant 3DES should no longer be applied.