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 }