这就是我们拥有的场景:我们有大量的加密文件,大约为千兆字节,如果我们将它们读到最后,我们就可以正确解密。 当我们读取并检测文件中的某个标志时会出现问题,然后我们停止读取并调用reader.Close(),会发生CryptographicException:“Padding无效且无法删除”。被扔了。 我有这个小控制台应用程序重现这种行为,测试它只是运行它,它将在你的C:\驱动器中创建一个文件然后按任意键时将逐行读取,并在按下'q'时停止
using System;
using System.IO;
using System.Security.Cryptography;
namespace encryptSample
{
class Program
{
static void Main(string[] args)
{
var transform = CreateCryptoTransform(true);
// first create encrypted file
using (FileStream destination = new FileStream("c:\\test_enc.txt", FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite))
{
using (CryptoStream cryptoStream = new CryptoStream(destination, transform, CryptoStreamMode.Write))
{
using (StreamWriter source = new StreamWriter(cryptoStream))
{
for (int i = 0; i < 1000; i++)
{
source.WriteLine("This is just random text to fill the file and show what happens when I stop reading in the middle - " + i);
}
// Also tried this line, but is the same with or without it
cryptoStream.FlushFinalBlock();
}
}
}
StreamReader reader;
ICryptoTransform transformDec;
CryptoStream cryptoStreamReader;
transformDec = CreateCryptoTransform(false);
FileStream fileStream = new FileStream("c:\\test_enc.txt", FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
cryptoStreamReader = new CryptoStream(fileStream, transformDec, CryptoStreamMode.Read);
reader = new StreamReader(cryptoStreamReader);
while (Console.In.ReadLine() != "q")
{
Console.WriteLine(reader.ReadLine());
}
try
{
cryptoStreamReader.Close();
reader.Close();
reader.Dispose();
}
catch (CryptographicException ex)
{
if (reader.EndOfStream)
throw;
}
}
private static ICryptoTransform CreateCryptoTransform(bool encrypt)
{
byte[] salt = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; // Must be at least eight bytes. MAKE THIS SALTIER!
const int iterations = 1042; // Recommendation is >= 1000.
const string password = "123456";
AesManaged aes = new AesManaged();
aes.BlockSize = aes.LegalBlockSizes[0].MaxSize;
aes.KeySize = aes.LegalKeySizes[0].MaxSize;
// NB: Rfc2898DeriveBytes initialization and subsequent calls to GetBytes must be eactly the same, including order, on both the encryption and decryption sides.
Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(password, salt, iterations);
aes.Key = key.GetBytes(aes.KeySize / 8);
aes.IV = key.GetBytes(aes.BlockSize / 8);
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
ICryptoTransform transform = encrypt ? aes.CreateEncryptor(aes.Key, aes.IV) : aes.CreateDecryptor(aes.Key, aes.IV);
return transform;
}
}
}
在我们原来的课程中,我们做读者。在Dispose()期间关闭。 我的问题是,检查reader.EndOfStream是否为false然后捕获CryptographicException是否有效?或者加密/解密方法有问题?也许我们错过了一些东西。
问候!
答案 0 :(得分:6)
在Dispose(true)期间抛出此异常。从Dispose中抛出已经是一个设计缺陷(https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1065-do-not-raise-exceptions-in-unexpected-locations#dispose-methods),但更糟糕的是,因为即使在基础流关闭之前抛出此异常也是如此。
这意味着接收可能为CryptoStream的Stream的任何内容都需要解决这个问题,并在“catch”块中关闭底层Stream本身(基本上需要引用完全不相关的内容) )或以某种方式警告所有听众流可能仍然是开放的(例如,“不要试图删除基础文件 - 它仍然是开放的!”)。
不,在我的书中,这是一个非常大的疏忽,而其他答案似乎并没有解决根本问题。 CryptoStream获取传入流的所有权,因此在控制离开Dispose(true),故事结束之前,它负责关闭底层流。
理想情况下,它也应该永远不会抛弃那些并非真正特殊的情况(例如“我们提前停止阅读,因为解密的数据格式错误,继续阅读是浪费时间”)。
我们的解决方案基本上就是(更新:但要注意 - 正如Will Krause在评论中指出的那样,这可能会在私人_InputBuffer
和_OutputBuffer
字段中留下敏感信息可以通过反射访问..NET Framework的4.5及更高版本没有这个问题。):
internal sealed class SilentCryptoStream : CryptoStream
{
private readonly Stream underlyingStream;
public SilentCryptoStream(Stream stream, ICryptoTransform transform, CryptoStreamMode mode)
: base(stream, transform, mode)
{
// stream is already implicitly validated non-null in the base constructor.
this.underlyingStream = stream;
}
protected override void Dispose(bool disposing)
{
try
{
base.Dispose(disposing);
}
catch (CryptographicException)
{
if (disposing)
{
this.underlyingStream.Dispose();
}
}
}
}
答案 1 :(得分:1)
据我了解,当最后一个字节读取不是有效的填充字节时,抛出异常。当您有意提前关闭流时,最后读取的字节很可能被视为“无效填充”并抛出异常。由于你故意结束,你应该安全地忽略例外。
答案 2 :(得分:1)
Close
调用Dispose(true)
调用引发异常的FlushFinalBlock
,因为这不是最后的阻止。
您可以通过覆盖Close
方法来阻止此操作,以便它不会调用FlushFinalBlock
:
public class SilentCryptoStream : CryptoStream {
public SilentCryptoStream(Stream stream, ICryptoTransform transform, CryptoStreamMode mode) :
base(stream, transform, mode) {
}
public override void Close() {
this.Dispose(false);
GC.SuppressFinalize(this);
}
}
(您还需要手动关闭基础流。)
检查reader.EndOfStream是否为false然后捕获是否有效 CryptographicException
我认为没关系。
答案 3 :(得分:0)
你可以关闭填充吗?
// aes.Padding = PaddingMode.PKCS7;
aes.Padding = PaddingMode.None;
答案 4 :(得分:0)
我的解决方案是,在我的派生类中,将它添加到我的Dispose(bool)覆盖:
protected override void Dispose(bool disposing)
{
// CryptoStream.Dispose(bool) has a bug in read mode. If the reader doesn't read all the way to the end of the stream, it throws an exception while trying to
// read the final block during Dispose(). We'll work around this here by moving to the end of the stream for them. This avoids the thrown exception and
// allows everything to be cleaned up (disposed, wiped from memory, etc.) properly.
if ((disposing) &&
(CanRead) &&
(m_TransformMode == CryptoStreamMode.Read))
{
const int BUFFER_SIZE = 32768;
byte[] buffer = new byte[BUFFER_SIZE];
while (Read(buffer, 0, BUFFER_SIZE) == BUFFER_SIZE)
{
}
}
base.Dispose(disposing);
...
通过确保始终将流读取到最后,可以避免CryptStream.Dispose中的内部问题。当然,你需要权衡这与你正在阅读的内容的性质,以确保它没有负面影响。仅将其用于已知有限长度的源。