根据 Alexei 的反馈,对问题进行了简化:
如何在不使用 Convert.ToBase64String() 的情况下使用缓冲流方法将 CryptoStream(使用 ToBase64Transform)的内容转换为 StreamWriter(Unicode 编码)?
注意:调用 Convert.ToBase64String() 会抛出 OutOfMemoryException,因此需要使用缓冲/流方法进行转换。
根据 Alexei 的反馈,对问题进行了简化:
如何在不使用 Convert.ToBase64String() 的情况下使用缓冲流方法将 CryptoStream(使用 ToBase64Transform)的内容转换为 StreamWriter(Unicode 编码)?
注意:调用 Convert.ToBase64String() 会抛出 OutOfMemoryException,因此需要使用缓冲/流方法进行转换。
您可能应该实现 custom Stream
,而不是TextWriter。编写流比编写器容易得多(例如将流传递给压缩流)。
Stream
创建自定义流 -至少从 and 派生和实现Write
(如果Flush
您Read
需要 R/W 流)。其余的或多或少是可选的,取决于您的其他需求,定期复制到其他流不需要其他任何东西。
在构造函数中获取传递给您的内部流以进行写入。Base64 始终生成 ASCII 字符,因此将带有或不带有 BOM 的 UTF-8 输出直接写入流应该很容易,但是如果要指定编码,可以在内部包装内部流StreamWriter
。
在您的Write
实现中缓冲数据,直到您获得足够的字节以拥有 3 个字节的倍数(即 300)并调用Convert.ToBase64String
该部分。确保不要松开尚未转换的部分。由于 Base64 将 3 个字节转换为 4 个字符,转换为 3 倍大小的块将永远不会在末尾有=
/==
填充,并且可以与下一个块连接。因此,将转换后的部分写入内部流/写入器。请注意,您希望将块大小限制为相对较小的值,例如3*10000
避免在大型对象堆上分配块。
Flush
确保转换最后一个未写入的字节(这将是唯一一个最后有填充=
的字节)并将其也写入流中。
对于阅读,您可能需要更加小心,因为在 Base64 中允许使用空格,因此您无法读取固定数量的字符并将其转换为字节。最简单的方法是按字符读取StreamReader
并将每 4 个非空格字符转换为字节。
注意:您可以考虑直接从字节中手动写入/读取 Base64。它会给你一些性能上的好处,但如果你不擅长移位,可能会很难。
请尝试使用以下加密。我使用 fileName/filePath 作为输入。您可以根据自己的要求进行调整。使用这个我已经成功加密了超过 1 GB 的文件,没有任何内存不足的异常。
public bool EncryptUsingStream(string inputFileName, string outputFileName)
{
bool success = false;
// here assuming that you already have key
byte[] key = new byte[128];
SymmetricAlgorithm algorithm = SymmetricAlgorithm.Create();
algorithm.Key = key;
using (ICryptoTransform transform = algorithm.CreateEncryptor())
{
CryptoStream cs = null;
FileStream fsEncrypted = null;
try
{
using (FileStream fsInput = new FileStream(inputFileName, FileMode.Open, FileAccess.Read))
{
//First write IV
fsEncrypted = new FileStream(outputFileName, FileMode.Create, FileAccess.Write);
fsEncrypted.Write(algorithm.IV, 0, algorithm.IV.Length);
//then write using stream
cs = new CryptoStream(fsEncrypted, transform, CryptoStreamMode.Write);
int bytesRead;
int _bufferSize = 1048576; //buggersize = 1mb;
byte[] buffer = new byte[_bufferSize];
do
{
bytesRead = fsInput.Read(buffer, 0, _bufferSize);
cs.Write(buffer, 0, bytesRead);
} while (bytesRead > 0);
success = true;
}
}
catch (Exception ex)
{
//handle exception or throw.
}
finally
{
if (cs != null)
{
cs.Close();
((IDisposable)cs).Dispose();
if (fsEncrypted != null)
{
fsEncrypted.Close();
}
}
}
}
return success;
}