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