public static StreamEncryptionHeader Read(
            byte[] buffer,
            int offset,
            int count,
            int blockSizeBytes,
            bool expectFullHeader = true
            )
        {
            var header = new StreamEncryptionHeader();

            if (expectFullHeader)
            {
                header.From = BitConverter.ToInt64(buffer, offset);
                offset     += sizeof(long);

                header.To = BitConverter.ToInt64(buffer, offset);
                offset   += sizeof(long);
            }

            header.IV = new byte[blockSizeBytes];
            for (int i = 0; i < header.IV.Length; i++)
            {
                header.IV[i] = buffer[offset + i];
            }
            offset += header.IV.Length;

            return(header);
        }
        public ChunkEncryptorStreamReader(
            Stream previousStream,
            SymmetricAlgorithm algorithm,
            long from,
            long to,
            long totalFileLength,
            IEnumerable <GeneralDigest> digests = null,
            byte[] iv      = null,
            ILogger logger = null
            )
        {
            this.PreviousStream = previousStream;

            this.TotalClearLength  = totalFileLength;
            this.ClearStreamLength = to - from + 1;

            this.BlockSizeBytes = algorithm.BlockSize / 8;

            this.CipherBuffer = new byte[BlockSizeBytes];
            this.BlockBuffer  = new byte[BlockSizeBytes];

            this.Digests = digests;

            if (from == 0)
            {
                Header = new StreamEncryptionHeader(BlockSizeBytes)
                {
                    From = 0,
                    To   = this.TotalClearLength
                };

                algorithm.GenerateIV();
                algorithm.IV.CopyTo(Header.IV, 0);
                // rant: this seems a poor API design because of the race condition.
                // but seeing as it's random data, if it collides with other
                // data, well, that might not be so bad.... but still.

                HeaderStream = new MemoryStream(StreamEncryptionHeader.HeaderLength(BlockSizeBytes));
                Header.WriteTo(HeaderStream);
                HeaderStream.Seek(0, SeekOrigin.Begin);
                if (iv != null)
                {
                    throw new Exception("Encountered non-null IV on first chunk");
                }

                iv           = Header.IV;
                InjectHeader = true;
            }
            else
            if (iv == null)
            {
                throw new Exception("Secondary chunk with null IV encountered");
            }

            long clearLength  = to - from + 1;
            long headerLength = (from == 0)
                ? StreamEncryptionHeader.HeaderLength(BlockSizeBytes)
                : 0;

            // note, we are not adding room for padding. EVEN if it's the last block.
            // This class does not use any traditional padding modes, it's length prefixed.
            long cipherLength =
                (clearLength / BlockSizeBytes) * BlockSizeBytes
                + ((clearLength % BlockSizeBytes == 0) ? 0 : BlockSizeBytes); // if it was not a multiple, add another block

            this.StreamLength = headerLength + cipherLength;

            this.Encryptor = algorithm.CreateEncryptor(algorithm.Key, iv);
        }
        public DecryptingStreamWriter(
            Stream previousStream,
            SymmetricAlgorithm algorithm,
            long from,
            long to,
            long totalLength,
            ILogger logger = null
            )
        {
            this.PreviousStream = previousStream;
            this.Algorithm      = algorithm;

            Algorithm.Padding = PaddingMode.None;

            this.From        = from;
            this.To          = to;
            this.TotalLength = totalLength;

            this.BlockSizeBytes = algorithm.BlockSize / 8;

            this.CipherBuffer = new byte[BlockSizeBytes];
            this.BlockBuffer  = new byte[BlockSizeBytes];

            this.IsSeeking = this.From != 0;
            int fullHeaderLength = StreamEncryptionHeader.HeaderLength(BlockSizeBytes);

            this.ExpectedHeaderBytes = this.IsSeeking
                ? BlockSizeBytes
                : fullHeaderLength;

            this.HeaderStream = new MemoryStream();

            this.StreamLength = to - from + 1;

            from += fullHeaderLength;
            to   += fullHeaderLength;

            var blockNumber = from / BlockSizeBytes;
            var blockStart  = blockNumber * BlockSizeBytes;

            SkipBytes = (int)(from - blockStart);

            // backup to either the full header or a prior block
            from = IsSeeking
                ? from - BlockSizeBytes - SkipBytes
                : 0;

            if (from < 0)
            {
                throw new Exception("nonsense output from encryption stream seeking algorithm");
            }

            // if "to" isn't on the edge of a complete block,
            // round it up to the nearest end of block
            if ((to + 1) % BlockSizeBytes != 0)
            {
                to = (((to + 1) / BlockSizeBytes) + 1) * BlockSizeBytes - 1;
            }

            this.CipherFrom = from;

            this.CipherTo = to;

            // note, we are not adding room for padding. EVEN if it's the last block.
            // This class does not use any traditional padding modes, it's length prefixed.
            this.CipherTotalLength = this.ExpectedHeaderBytes
                                     + (this.TotalLength / BlockSizeBytes) * BlockSizeBytes
                                     + ((this.TotalLength % BlockSizeBytes == 0) ? 0 : BlockSizeBytes); // if it was not a multiple, add another block
        }
        public override void Write(byte[] buffer, int offset, int count)
        {
            if (ExpectedHeaderBytes > 0)
            {
                int headerBytes = Math.Min(count, ExpectedHeaderBytes);
                HeaderStream.Write(buffer, offset, headerBytes);
                ExpectedHeaderBytes -= headerBytes;
                count  -= headerBytes;
                offset += headerBytes;

                if (ExpectedHeaderBytes > 0)
                {
                    return;
                }

                var headerBuffer = HeaderStream.ToArray();
                Header = StreamEncryptionHeader.Read(
                    headerBuffer, 0, headerBuffer.Length, BlockSizeBytes, !IsSeeking);

                Decryptor = Algorithm.CreateDecryptor(Algorithm.Key, Header.IV);
            }

            while (count > 0)
            {
                int blockBytes = Math.Min(BlockSizeBytes - CipherBufferPosition, count);

                Array.Copy(buffer, offset, CipherBuffer, CipherBufferPosition, blockBytes);

                count  -= blockBytes;
                offset += blockBytes;
                CipherBufferPosition += blockBytes;

                if (CipherBufferPosition == BlockSizeBytes)
                {
                    CipherBufferPosition = 0;
                    int cryptoBytes = Decryptor.TransformBlock(
                        CipherBuffer, 0, BlockSizeBytes, BlockBuffer, 0);

                    if (cryptoBytes < BlockSizeBytes)
                    {
                        throw new Exception("CryptoTransform did not return a full block, check padding mode");
                    }

                    int clearSize = (int)Math.Min(BlockSizeBytes, TotalLength - ClearStreamPosition);

                    if (ClearStreamPosition + clearSize > StreamLength)
                    {
                        clearSize -= (int)((ClearStreamPosition + clearSize) - StreamLength);
                    }

                    if (SkipBytes + clearSize > BlockSizeBytes)
                    {
                        clearSize -= (int)((SkipBytes + clearSize) - BlockSizeBytes);
                    }

                    PreviousStream.Write(BlockBuffer, SkipBytes, clearSize);
                    SkipBytes = 0;

                    ClearStreamPosition += clearSize;
                }
            }
        }