首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >将IV添加到CryptoStream开头

将IV添加到CryptoStream开头
EN

Stack Overflow用户
提问于 2018-05-21 09:29:44
回答 3查看 950关注 0票数 3

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

我可以找到的许多示例代码,比如微软的,演示了如何直接写入文件,但我需要做的是提供一个在程序其他地方使用的流:

代码语言:javascript
复制
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字节,考虑到何时实际开始读取流和写入文件的控制超出了我的代码范围?

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2018-05-21 12:06:19

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

你用的是:

代码语言:javascript
复制
var mergedStream = new MergedStream(new Stream[] 
{
    new MemoryStream(iv),
    cryptoStream,
});

现在..。当有人试图从MergedStream中读取时,首先将消耗包含IV的MemoryStream,然后使用cryptoStream

代码语言:javascript
复制
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 => streams.Sum(s => s.Length);

    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;
        }
    }
}
票数 5
EN

Stack Overflow用户

发布于 2018-05-21 11:14:50

使用CryptoStream在两个本地方之间进行通信并不是一个好的设计。您应该使用通用的InputStream或管道(用于进程间通信)。然后,您可以将IV的MemoryStreamCryptoStream组合起来,并返回组合。关于如何做到这一点,请参阅xanatos的回答 (如果需要的话,您可能仍然需要填写Seek功能)。

CryptoStream只能处理密文。由于您需要在接收端更改代码,如果您想解密,最好重构为InputStream

如果您需要保持当前的设计,那么就有一个黑客可用。首先,使用欧洲央行模式“解密”IV,而不加填充。由于单个分组密码调用总是成功的,结果将是一个数据块--在使用CipherStream加密时--再次转化为IV。

步骤:

  1. 在一个数组中生成16个随机字节,这将是真正的IV;
  2. 使用不加填充的欧洲央行解密16字节IV和用于CipherStream的密钥;
  3. 使用键和全零,16字节IV初始化CipherStream
  4. 使用CipherStream加密“解密”IV;
  5. 输入其余的明文。

您将需要创建一个InputStream,它首先接收解密的IV (作为MemoryStream),然后是明文(作为FileStream),这样才是可行的。再说一遍,也请看xanatos的答案。介绍了如何做到这一点。或者,例如,在good ol‘StackOverflow上看到这个组合器HugeStream。然后使用组合流作为CipherInputStream的源。

但是不用说,像这样的黑客应该在方便的时候就被记录下来并移除。

备注:

  • 这个技巧不适用于任何模式,它适用于CBC模式,但其他模式可能会使用不同的IV;
  • 请注意,OutputStream通常对加密更有意义,设计中可能有其他错误。
票数 2
EN

Stack Overflow用户

发布于 2018-05-21 11:50:54

感谢那些花时间回答的人。最后,我意识到我必须在缓冲代码中了解IV长度,没有办法绕过它,所以选择让它保持简单:

加密方法(伪码):

代码语言:javascript
复制
/* 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 */

解密方法(伪码):

代码语言:javascript
复制
/* 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 */
票数 -1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/50445749

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档