此 AES 加密代码是否存在双重处理问题?

Does this AES cryptography code have a double-dispose problem?

又是一年,Microsoft 的好人希望我们编写 encryption/decryption 代码的方式又发生了变化。现在(2022 年 2 月,.Net 6)我们应该使用新的 Aes class 而不是已弃用的 RijndaelManaged class 及其表兄弟。

所以,这是我的对称 encryption/decryption 代码:

/// <summary> Encrypts a byte array using the AES encryption algorithm. </summary>
/// <param name="clearData"> The data to encrypt. </param>
/// <param name="key"> The encryption key. </param>
/// <param name="iv"> The initialization vector. </param>
/// <returns> The encrypted data. </returns>
public static byte[] Encrypt(byte[] clearData, byte[] key, byte[] iv)
{
    Contract.Requires(clearData != null);
    Contract.Requires(key != null);
    Contract.Requires(iv != null);

    using (var aes = Aes.Create()) {
        aes.Key = key;
        aes.IV = iv;
        using (var encryptor = aes.CreateEncryptor(aes.Key, aes.IV)) {
            return _PerformCryptography(clearData, encryptor);
        }
    }
}

/// <summary> Decrypts a byte array using the AES encryption algorithm. </summary>
/// <param name="encryptedData"> The encrypted data. </param>
/// <param name="key"> The encryption key. </param>
/// <param name="iv"> The initialization vector. </param>
/// <returns> The decrypted data. </returns>
public static byte[] Decrypt(byte[] encryptedData, byte[] key, byte[] iv)
{
    Contract.Requires(encryptedData != null);
    Contract.Requires(key != null);
    Contract.Requires(iv != null);

    using (var aes = Aes.Create()) {
        aes.Key = key;
        aes.IV = iv;
        using (var decryptor = aes.CreateDecryptor(aes.Key, aes.IV)) {
            return _PerformCryptography(encryptedData, decryptor);
        }
    }
}

/// <summary> Performs symmetrical encryption or decryption using a specified transformation algorithm. </summary>
/// <param name="data"> The data to encrypt or decrypt. </param>
/// <param name="cryptoTransform"> The crypto algorithm. </param>
/// <returns> The encrypted or decrypted data. </returns>
private static byte[] _PerformCryptography(byte[] data, ICryptoTransform cryptoTransform)
{
    using var ms = new MemoryStream();
    using var cs = new CryptoStream(ms, cryptoTransform, CryptoStreamMode.Write);
    cs.Write(data, 0, data.Length);
    cs.FlushFinalBlock();

    return ms.ToArray();
}

我的具体问题与 _PerformCryptography 中的 using 语句有关。我之前的实现没有依赖 using 语句来处理对象,而是使用 try/finally 构造 来避免两次处理 MemoryStream :

            // Get a SymmetricAlgorithm object
            using var algorithm = new RijndaelManaged();

            // Set the key and the initialization vector
            algorithm.Key = key;
            algorithm.GenerateIV();

            // Encrypt the information to a MemoryStream
            MemoryStream ms = null;
            CryptoStream cs = null;
            try {
                // See comment below re: "multiple dispose" issue
                ms = new MemoryStream();

                // Encrypt the data
                cs = new CryptoStream(ms, algorithm.CreateEncryptor(), CryptoStreamMode.Write);
                cs.Write(clearBytes, 0, clearBytes.Length);
                cs.FlushFinalBlock();

                // Return the encrypted stream of data as a byte array
                return ms.ToArray();
            }

            finally {
                if (cs != null) {
                    // Dispose the CryptoStream, which disposes the MemoryStream that it contains
                    ms = null;
                    cs.Dispose();
                }

                if (ms != null) ms.Dispose();
            }

这是否仍然是必需的,或者有什么改变所以“多次处置”不再是问题?(或者也许它从来就不是真正的问题?)

几件事...

  • 测试您所询问的代码并查看会发生什么非常容易。

  • 您可以在几乎所有 .NET class 上从 Visual Studio F12 直接转到源代码,这样您就可以研究如何(例如) CryptoStream.Dispose() 方法已实现。

  • 快速检查可用的 CryptoStream 构造函数表明有一个重载采用 bool leaveOpen 参数,这将使流在处理 CryptoStream 时保持打开状态.但是你不需要那个参数,因为...

  • 如果您转到 MemoryStream.Dispose() 的源代码,您会发现它除了设置一些内部字段值外并没有做太多事情 - 并明确留下 _buffer 字段完好无损。因此,即使您确实调用了两次,它也不会出现异常行为 - 您仍然可以在 MemoryStream 上安全地调用 .ToArray(),即使它已被处置。