加密文件,但可以从任何点解密

时间:2014-07-08 03:51:41

标签: c# encryption

基本上我需要加密文件,然后能够从文件中的几乎任何一点解密文件。我需要这个的原因是我想将此用于视频等文件,仍然能够跳过文件或视频。该文件也将通过网络提供,因此无需下载整个文件非常重要。我存储文件的地方支持部分下载,所以我可以请求我需要的文件的任何部分,这适用于未加密的文件。问题是如何使这个工作加密文件。我需要在C#中加密和解密文件,但实际上没有任何其他限制。对称键是首选,但如果它不起作用,它不是一个交易破坏者。

我只想下载部分文件并解密的另一个例子是我将多个文件连接在一起但只需要检索其中一个文件的地方。这通常用于小于50MB的文件,如图片和信息文件。

---编辑---

要明确我正在寻找一个不会增加源文件大小的工作实现或库。流密码看起来很理想,但我还没有看到c#中的一个适用于流中的任何一点或者除了流的开头之外的任何东西。如果它在流中形成设置块,则会考虑基于块的实现。基本上我想通过这个传递原始流并且未加密出来流的另一端。很高兴设置它在整个文件/流中表示的起始偏移量。寻找的东西比工作,因为我不是加密专家。在那一刻,我从512kb到5mb块的数据源获取文件的部分,具体取决于客户端配置,我使用流CopyTo方法将其写入磁盘上的文件。我没有按顺序得到这些部件。我正在寻找一个流包装器,我可以使用它来传递到流上的CopyTo方法。

2 个答案:

答案 0 :(得分:3)

你最好的方法可能是将文件视为一个块列表(任何大小对你的应用程序都很方便;假设为50 kB)并分别加密。这将允许您独立于其他块解密每个块。

对于每个块,从主密钥派生新密钥,生成新的IV,然后对块进行加密 - 然后MAC。

此方法比一次加密整个文件具有更高的存储开销,并且由于所需的密钥重新生成而需要更多的计算。

如果您使用流密码而不是分组密码,只要解密器能够从某个地方获取当前的IV,您就可以在任何字节偏移处开始解密。

答案 1 :(得分:0)

对于那些感兴趣的人,我设法根据我发现的一些例子以及我自己的一些代码来解决这个问题。它使用bouncycastle但也应该使用dotnet AES进行一些调整。这允许从流中的任何点进行解密/加密。

using System;
using System.IO;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;

namespace StreamHelpers
{
    public class StreamEncryptDecrypt : Stream
    {
        private readonly Stream _streamToWrap;
        private readonly IBlockCipher _cipher;
        private readonly ICipherParameters _key;
        private readonly byte[] _iv;
        private readonly byte[] _counter;
        private readonly byte[] _counterOut;
        private readonly byte[] _output;
        private long currentBlockCount;

        public StreamEncryptDecrypt(Stream streamToWrap, IBlockCipher cipher, ParametersWithIV keyAndIv)
        {
            _streamToWrap = streamToWrap;
            _cipher = cipher;
            _key = keyAndIv.Parameters;
            _cipher.Init(true, _key);
            _iv = keyAndIv.GetIV();
            _counter = new byte[_cipher.GetBlockSize()];
            _counterOut = new byte[_cipher.GetBlockSize()];
            _output =  new byte[_cipher.GetBlockSize()];

            if (_iv.Length != _cipher.GetBlockSize())
            {
                throw new Exception("IV must be the same size as the cipher block size");
            } 
            InitCipher();
        }

        private void InitCipher()
        {
            long position = _streamToWrap.Position;

            Array.Copy(_iv, 0, _counter, 0, _counter.Length);
            currentBlockCount = 0;
            var targetBlock = position/_cipher.GetBlockSize();
            while (currentBlockCount < targetBlock)
            {
                IncrementCounter(false);
            }
            _cipher.ProcessBlock(_counter, 0, _counterOut, 0);
        }

        private void IncrementCounter(bool updateCounterOut = true)
        {
            currentBlockCount++;
            // Increment the counter
            int j = _counter.Length;
            while (--j >= 0 && ++_counter[j] == 0)
            {
            }
            _cipher.ProcessBlock(_counter, 0, _counterOut, 0);
        }



        public override long Position
        {
            get { return _streamToWrap.Position; }
            set
            {
                _streamToWrap.Position = value;
                InitCipher();


            }
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            var result = _streamToWrap.Seek(offset, origin);
            InitCipher();
            return result;
        }

        public void ProcessBlock(
            byte[] input,
            int offset,
            int length, long streamPosition)
        {
            if (input.Length < offset+length)
                throw new ArgumentException("input does not match offset and length");
            var blockSize = _cipher.GetBlockSize();
            var startingBlock = streamPosition / blockSize;
            var blockOffset = (int)(streamPosition - (startingBlock * blockSize));

            while (currentBlockCount < streamPosition / blockSize)
                {
                    IncrementCounter();
                }

            //process the left over from current block
            if (blockOffset !=0)
            {
                var blockLength = blockSize - blockOffset;
                blockLength = blockLength > length ? length : blockLength;
                //
                // XOR the counterOut with the plaintext producing the cipher text
                //
                for (int i = 0; i < blockLength; i++)
                {
                    input[offset + i] = (byte)(_counterOut[blockOffset + i] ^ input[offset + i]);
                }

                offset += blockLength;
                length -= blockLength;
                blockOffset = 0;

                if (length > 0)
                {
                    IncrementCounter();
                }
            }

            //need to loop though the rest of the data and increament counter when needed
            while (length > 0)
            {
                var blockLength =  blockSize > length ? length : blockSize;
                //
                // XOR the counterOut with the plaintext producing the cipher text
                //
                for (int i = 0; i < blockLength; i++)
                {
                    input[offset + i] = (byte)(_counterOut[i] ^ input[offset + i]);
                }
                offset += blockLength;
                length -= blockLength;
                if (length > 0)
                {
                    IncrementCounter();
                }
            }
        }




        public override int Read(byte[] buffer, int offset, int count)
        {
            var pos = _streamToWrap.Position;
            var result = _streamToWrap.Read(buffer, offset, count);
            ProcessBlock(buffer, offset, result, pos);
            return result;
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            var input = new byte[count];
            Array.Copy(buffer, offset, input, 0, count);
            ProcessBlock(input, 0, count, _streamToWrap.Position);
            _streamToWrap.Write(input, offset, count);
        }



        public override void Flush()
        {
            _streamToWrap.Flush();
        }


        public override void SetLength(long value)
        {
            _streamToWrap.SetLength(value);
        }

        public override bool CanRead
        {
            get { return _streamToWrap.CanRead; }
        }

        public override bool CanSeek
        {
            get { return true; }

        }

        public override bool CanWrite
        {
            get { return _streamToWrap.CanWrite; }
        }

        public override long Length
        {
            get { return _streamToWrap.Length; }
        }

        protected override void Dispose(bool disposing)
        {
            if (_streamToWrap != null)
            {
                _streamToWrap.Dispose();
            }

            base.Dispose(disposing);
        }
    }
}