slickboy
slickboy

Reputation: 461

How to create a persisted AesCng key or TripleDesCng Key In C#.NET?

When version 4.6.2. of the .NET Framework was released, one of the features included in the release notes was 'Support for Persisted-Key Symmetric Encryption'. This feature was descibed as follows:

The Windows Cryptography Library (CNG) supports storing persisted symmetric keys on software and hardware devices. The .NET Framework now exposes this CNG capability, as you can see demonstrated in the example below. You need to use the concrete implementation classes, such as AesCng to use this new capability, as opposed to the more common factory approach, Aes.Create(). This requirement is due to key names and key providers being implementation-specific Persisted-key symmetric encryption has been added for the AES and 3DES algorithms, in the AesCng TripleDESCng classes, respectively.

Having reviewed the documentation for the AesCng and TripleDESCng classes, I note that there are a number of constuctors in each class that allows for an object of the class to be created from an 'existing persisted AES/TripleDES key' - but there is no mechanism to persist keys in either class itself.

Having performed some research, I came across the CngKey class that appears to offer this functionality - however, neither AES or TripleDES are listed in the suite of algorithms outlined in the CngAlgortithm class.

In essence, my question is how do I create a persisted AesCng key or TripleDesCng Key In C#.NET?

I note that a similar question was asked previously on StackOverflow; however, having tried the answer marked as correct, my code crashes with the following Exception: System.Security.Cryptography.CryptographicException: 'The requested operation is not supported. '

Upvotes: 1

Views: 1758

Answers (1)

Topaco
Topaco

Reputation: 49251

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.

Upvotes: 1

Related Questions