代码之家  ›  专栏  ›  技术社区  ›  technophebe

在密码流的开头添加IV

  •  1
  • technophebe  · 技术社区  · 6 年前

    我正在现有的文件管理程序中实现本地加密。

    我可以找到很多示例代码,例如 Microsoft's ,演示如何直接写入文件,但我需要做的是提供程序中其他地方使用的流:

    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添加到加密流的开头,以便它将是读取加密流时返回的第一个x字节,因为对何时实际开始读取流和写入文件的控制不在SCO范围内。我的代码的PE?

    3 回复  |  直到 6 年前
        1
  •  4
  •   xanatos    6 年前

    好啊。。。既然你的问题很清楚,那就“相当”容易了…遗憾的是.NET没有包含合并两个类的类 Stream 但是我们可以很容易地创建它。这个 MergedStream 是一个只读的、仅向前的多流合并。

    你的用法是:

    var mergedStream = new MergedStream(new Stream 
    {
        new MemoryStream(iv),
        cryptoStream,
    }
    

    现在。。。当有人试图从 合并数据流 ,首先是 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;
            }
        }
    }
    
        2
  •  1
  •   Maarten Bodewes    6 年前

    使用 CryptoStream 在两个地方团体之间进行沟通。你应该使用一个通用的 InputStream 或管道(用于进程间通信)。然后您可以组合 MemoryStream 对于静脉注射和静脉注射 标准接口 并返回组合。查看 answer of xanatos 关于如何执行此操作(您可能仍然需要填写 Seek 功能(如果需要)。

    标准接口 只能处理密文。因为你需要更改接收器的代码 无论如何 如果你想解密,不妨重构为 输入流 .


    如果你需要保持当前的设计,那么有一个黑客可用。首先,使用无填充的ECB模式“解密”IV。作为一个分组密码调用 总是 成功,结果将是一个数据块,当使用 CipherStream -又变成静脉注射。

    步骤:

    1. 在一个数组中生成16个随机字节,这将是实数IV;
    2. 解密 使用不带填充的ECB的16字节IV和用于 密码流 ;
    3. 初始化 密码流 使用键和全零16字节IV;
    4. 使用 密码流 ;
    5. 输入其余的纯文本。

    您需要创建一个 输入流 第一个接收解密的IV 内存流 )然后是明文 FileStream )这是可行的。再一次, also see the answer of xanatos 关于如何做到这一点。或者比如看 this combiner this HugeStream 在良好的堆栈溢出上。然后使用组合流作为 CipherInputStream .

    但不用说,像这样的黑客应该被很好地记录下来,并在方便的时候尽早删除。


    笔记:

    • 这个技巧不适用于任何模式;它适用于CBC模式,但其他模式可能使用不同的IV;
    • 注意,一个 OutputStream 一般来说,加密会更有意义,设计上可能还有其他问题。
        3
  •  -1
  •   technophebe    6 年前

    感谢那些花时间回答问题的人。最后我意识到我必须知道缓冲代码中的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 */