此 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()
,即使它已被处置。
又是一年,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()
,即使它已被处置。