如何在 C#.NET 中创建持久的 AesCng 密钥或 TripleDesCng 密钥?

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

4.6.2版本时。 .NET Framework 发布后,release notes 中包含的功能之一是 'Support for Persisted-Key Symmetric Encryption'。此功能描述如下:

Windows 加密库 (CNG) 支持在软件和硬件设备上存储永久对称密钥。 .NET Framework 现在公开了此 CNG 功能,您可以在下面的示例中看到这一点。 您需要使用具体实现 classes,例如 AesCng 才能使用此新功能,而不是更常见的工厂方法 Aes.Create()。此要求是由于密钥名称和密钥提供程序是特定于实现的 已分别在 AesCng TripleDESCng classes 中为 AES 和 3DES 算法添加持久密钥对称加密。

查看 AesCng and TripleDESCng classes 的文档后,我注意到每个 class 中都有许多构造函数允许 class 从 'existing persisted AES/TripleDES key' 创建 - 但没有机制可以在 class 本身中保留密钥。

经过一些研究,我遇到了 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。

本质上,我的问题是如何在 C#.NET 中创建持久的 AesCng 密钥或 TripleDesCng 密钥?

我注意到之前在 Whosebug 上有人问过 ;然而,在尝试了标记为正确的答案后,我的代码崩溃并出现以下异常:System.Security.Cryptography.CryptographicException:'不支持请求的操作。 '

works on my machine with .NET Framework 4.6.2 and above. Another helpful answer on this topic (but for RSA) can be found .

以下方法创建一个持久的 32 字节密钥 (AES-256),如果它不存在,或者加载一个持久密钥,如果它已经存在,并执行加密:

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);
}

请注意,key默认是exportable,所以aes.Key会生成一个System.Security.Cryptography.CryptographicException:请求的操作是不支持,即例如:

var encryptor = aes.CreateEncryptor(aes.Key, aes.IV);

会抛出那个异常。也许这个或类似的东西是您环境中出现异常的原因。

对应的解密对应为:

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);
}

以下代码:

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);

执行两次加密,在两种情况下生成 相同的 密文:第一次调用创建并保存密钥,第二次调用加载保存的密钥。相同的密文证明使用了相同的密钥进行加密

为了也能导出key,需要做如下修改:

CngKeyCreationParameters keyCreationParameters = new CngKeyCreationParameters()
{
    ExportPolicy = CngExportPolicies.AllowPlaintextExport,
    Provider = keyStorageProvider
};

现在可以访问密钥,例如aes.Key.

请注意,静态 IV 仅用于比较两种加密的密文。当然,实际上,每次加密都必须应用随机 IV。

为了完整起见,应该提到代码与 .NET Core 兼容(已针对 .NET Core 3.1 进行测试)


密钥持久性与 3DES 类似,即 TripleDESCng。由于必须使用标识符 3DES(无效的标识符也会触发上面发布的 CryptographicException),即:

CngKey.Create(new CngAlgorithm("3DES"), keyName, keyCreationParameters);

这将创建并保留一个 24 字节的密钥 (3TDEA)。但是请注意,与当前标准 AES 相比,过时且性能相对较差的 3DES 不应再应用。