Seer
Seer

Reputation: 495

Encrypt file but can decrypt from any point

Basically i need to encrypt a file and then be able to decrypt the file from almost any point in the file. The reason i need this is i would like to use this for files like Video etc and still be able to jump though the file or video. Also the file would be served over the web so not needing to download the whole file is important. Where i am storing the file supports partial downloads so i can request any part of the file i need and this works for an un encrypted file. The question is how could i make this work for an encrypted file. I need to encrypt and decrypt the file in C# but don't really have any other restrictions than that. Symmetric keys are preferred but if that wont work it is not a deal breaker.

Another example of where i only want to download part of a file and decrypt is where i have joined multiple files together but just need to retrieve one of them. This would generally be used for files smaller than 50MB like pictures and info files.

--- EDIT ---

To be clear i am looking for a working implementation or library that does not increase the size of the source file. Stream cipher seems ideal but i have not seen one in c# that works for any point in the stream or anything apart from the start of the stream. Would consider block based implementation if it works form set blocks in stream. Basically i want to pass a raw stream though this and have unencrypted come out other side of the stream. Happy to set the starting offset it represents in the whole file/stream. Looking for something than works as i am not encryption expert. At the minute i get the parts of the file from a data source in 512kb to 5mb blocks depending on client config and i use streams CopyTo method to write it out to a file on disk. I don't get these parts in order. I am looking for a stream wrapper that i could use to pass into the CopyTo method on stream.

Upvotes: 0

Views: 606

Answers (2)

Seer
Seer

Reputation: 495

For those interested i managed to work it out based on a number of examples i found plus some of my own code. It uses bouncycastle but should also work with dotnet AES with a few tweaks. This allows the decryption/encryption from any point in the stream.

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);
        }
    }
}

Upvotes: 0

user3553031
user3553031

Reputation: 6214

Your best best is probably to treat the file as a list of chunks (of whatever size is convenient for your application; let's say 50 kB) and encrypt each separately. This would allow you to decrypt each chunk independently of the others.

For each chunk, derive new keys from your master key, generate a new IV, and encrypt-then-MAC the chunk.

This method has higher storage overhead than encrypting the entire file at once and takes a bit more computation as well due to the key regeneration that it requires.

If you use a stream cipher instead of a block cipher, you'd be able to start decrypting at any byte offset, as long as the decryptor was able to get the current IV from somewhere.

Upvotes: 3

Related Questions