将IV添加到CryptoStream的开头
我正在现有的文件管理程序中实现本地加密。
我可以找到的大部分示例代码(如Microsoft)都演示了如何直接写入文件,但我需要做的是提供一个在程序中其他地方使用的流:
CryptoStream GetEncryptStream(string filename) { var rjndl = new RijndaelManaged(); rjndl.KeySize = 256; rjndl.BlockSize = 256; rjndl.Mode = CipherMode.CBC; rjndl.Padding = PaddingMode.PKCS7; // Open read stream of unencrypted source fileStream: var fileStream = new FileStream(filename, FileMode.Open); /* Get key and iv */ var transform = rjndl.CreateEncryptor(key, iv); // CryptoStream in *read* mode: var cryptoStream = new CryptoStream(fileStream, transform, CryptoStreamMode.Read); /* What can I do here to insert the unencrypted IV at the start of the stream so that the first X bytes returned by cryptoStream.Read are the IV, before the bytes of the encrypted file are returned? */ return cryptoStream; // Return CryptoStream to be consumed elsewhere }
我的问题在最后一行的注释中概述但是一个:如何将IV添加到CryptoStream的开头,这样它将是读取CryptoStream时返回的第一个X字节,给定控制何时实际启动读取流并写入文件超出了我的代码范围?
好的…现在您的问题很明显,它“非常”容易……遗憾的是,.NET不包含合并两个Stream
的类,但我们可以轻松创建它。 MergedStream
是一个只读,仅向前多流合并。
你使用像:
var mergedStream = new MergedStream(new Stream { new MemoryStream(iv), cryptoStream, }
现在……当有人试图从MergedStream
读取时,首先会消耗包含IV的MemoryStream
,然后将使用cryptoStream
。
public class MergedStream : Stream { private Stream[] streams; private int position = 0; private int currentStream = 0; public MergedStream(Stream[] streams) { this.streams = streams; } public override bool CanRead => true; public override bool CanSeek => false; public override bool CanWrite => false; public override long Length => throw new NotImplementedException(); public override long Position { get => position; set => throw new NotSupportedException(); } public override void Flush() { } public override int Read(byte[] buffer, int offset, int count) { if (streams == null) { throw new ObjectDisposedException(nameof(MergedStream)); } if (currentStream >= streams.Length) { return 0; } int read; while (true) { read = streams[currentStream].Read(buffer, offset, count); position += read; if (read != 0) { break; } currentStream++; if (currentStream == streams.Length) { break; } } return read; } public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } public override void SetLength(long value) { throw new NotSupportedException(); } public override void Write(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } protected override void Dispose(bool disposing) { try { if (disposing && streams != null) { for (int i = 0; i < streams.Length; i++) { streams[i].Close(); } } } finally { streams = null; } } }
使用CryptoStream
在两个本地方之间进行通信并不是一个好的设计。 您应该使用通用InputStream
或管道(用于进程间通信)。 然后,您可以为IV和CryptoStream
组合MemoryStream
并返回组合。 请参阅xanatos关于如何执行此操作的答案 (如果需要,您可能仍需要填写Seek
function)。
CryptoStream
只能处理密文。 因为你需要在接收器上更改代码,如果你想要解密,你也可以重构到InputStream
。
如果您需要保留当前的设计,那么可以使用黑客。 首先使用ECB模式“解密”IV而不填充。 由于单个分组密码呼叫总是成功,结果将是一个数据块 – 当使用CipherStream
加密时 – 再次变为IV。
脚步:
- 在数组中生成16个随机字节,这将是真正的IV;
- 使用ECB 解密 16字节IV而不填充和用于
CipherStream
的密钥; - 使用密钥和全零,16字节IV初始化
CipherStream
; - 使用
CipherStream
加密“解密”IV; - 输入明文的其余部分。
您需要创建一个首先接收解密的IV(作为MemoryStream
)然后接收明文(作为FileStream
)的InputStream
,以实现此目的。 再次, 也看到xanatos的答案如何做到这一点。 或者看看这个组合器和这个HugeStream
就好了’StackOverflow。 然后使用组合流作为CipherInputStream
源。
但不用说,像这样的黑客应该在最方便的时候记录和删除。
笔记:
- 这个技巧不适用于任何模式; 它适用于CBC模式,但其他模式可能使用不同的IV;
- 请注意,
OutputStream
通常对加密更有意义,设计可能还有其他问题。
感谢那些花时间回答的人。 最后我意识到我必须知道缓冲代码中的IV长度,没有办法绕过它,所以选择保持简单:
加密方法(伪代码):
/* Get key and IV */ outFileStream.Write(IV); // Write IV to output stream var transform = rijndaelManaged.CreateEncryptor(key, iv); // CryptoStream in read mode: var cryptoStream = new CryptoStream(inFileStream, transform, CryptoStreamMode.Read); do { cryptoStream.Read(chunk, 0, blockSize); // Get and encrypt chunk outFileStream.Write(chunk); // Write chunk } while (chunk.Length > 0) /* Cleanup */
解密方法(伪码):
/* Get key */ var iv = inFileStream.Read(ivLength); // Get IV from input stream var transform = rijndaelManaged.CreateDecryptor(key, iv); // CryptoStream in write mode: var cryptoStream = new CryptoStream(outFileStream, transform, CryptoStreamMode.Write); do { inFileStream.Read(chunk, 0, blockSize); // Get chunk cryptoStream.Write(chunk); // Decrypt and write chunk } while (chunk.Length > 0) /* Cleanup */