ZipAESStream.cs 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. using System;
  2. using System.IO;
  3. using System.Security.Cryptography;
  4. using System.Net.ApiService.Compression.Core;
  5. namespace System.Net.ApiService.Compression.Encryption {
  6. /// <summary>
  7. /// Encrypts and decrypts AES ZIP
  8. /// </summary>
  9. /// <remarks>
  10. /// Based on information from http://www.winzip.com/aes_info.htm
  11. /// and http://www.gladman.me.uk/cryptography_technology/fileencrypt/
  12. /// </remarks>
  13. internal class ZipAESStream : CryptoStream
  14. {
  15. /// <summary>
  16. /// Constructor
  17. /// </summary>
  18. /// <param name="stream">The stream on which to perform the cryptographic transformation.</param>
  19. /// <param name="transform">Instance of ZipAESTransform</param>
  20. /// <param name="mode">Read or Write</param>
  21. public ZipAESStream(Stream stream, ZipAESTransform transform, CryptoStreamMode mode)
  22. : base(stream, transform, mode)
  23. {
  24. _stream = stream;
  25. _transform = transform;
  26. _slideBuffer = new byte[1024];
  27. // mode:
  28. // CryptoStreamMode.Read means we read from "stream" and pass decrypted to our Read() method.
  29. // Write bypasses this stream and uses the Transform directly.
  30. if (mode != CryptoStreamMode.Read)
  31. {
  32. throw new Exception("ZipAESStream only for read");
  33. }
  34. }
  35. // The final n bytes of the AES stream contain the Auth Code.
  36. private const int AUTH_CODE_LENGTH = 10;
  37. // Blocksize is always 16 here, even for AES-256 which has transform.InputBlockSize of 32.
  38. private const int CRYPTO_BLOCK_SIZE = 16;
  39. // total length of block + auth code
  40. private const int BLOCK_AND_AUTH = CRYPTO_BLOCK_SIZE + AUTH_CODE_LENGTH;
  41. private Stream _stream;
  42. private ZipAESTransform _transform;
  43. private byte[] _slideBuffer;
  44. private int _slideBufStartPos;
  45. private int _slideBufFreePos;
  46. // Buffer block transforms to enable partial reads
  47. private byte[] _transformBuffer = null;// new byte[CRYPTO_BLOCK_SIZE];
  48. private int _transformBufferFreePos;
  49. private int _transformBufferStartPos;
  50. // Do we have some buffered data available?
  51. private bool HasBufferedData =>_transformBuffer != null && _transformBufferStartPos < _transformBufferFreePos;
  52. /// <summary>
  53. /// Reads a sequence of bytes from the current CryptoStream into buffer,
  54. /// and advances the position within the stream by the number of bytes read.
  55. /// </summary>
  56. public override int Read(byte[] buffer, int offset, int count)
  57. {
  58. // Nothing to do
  59. if (count == 0)
  60. return 0;
  61. // If we have buffered data, read that first
  62. int nBytes = 0;
  63. if (HasBufferedData)
  64. {
  65. nBytes = ReadBufferedData(buffer, offset, count);
  66. // Read all requested data from the buffer
  67. if (nBytes == count)
  68. return nBytes;
  69. offset += nBytes;
  70. count -= nBytes;
  71. }
  72. // Read more data from the input, if available
  73. if (_slideBuffer != null)
  74. nBytes += ReadAndTransform(buffer, offset, count);
  75. return nBytes;
  76. }
  77. // Read data from the underlying stream and decrypt it
  78. private int ReadAndTransform(byte[] buffer, int offset, int count)
  79. {
  80. int nBytes = 0;
  81. while (nBytes < count)
  82. {
  83. int bytesLeftToRead = count - nBytes;
  84. // Calculate buffer quantities vs read-ahead size, and check for sufficient free space
  85. int byteCount = _slideBufFreePos - _slideBufStartPos;
  86. // Need to handle final block and Auth Code specially, but don't know total data length.
  87. // Maintain a read-ahead equal to the length of (crypto block + Auth Code).
  88. // When that runs out we can detect these final sections.
  89. int lengthToRead = BLOCK_AND_AUTH - byteCount;
  90. if (_slideBuffer.Length - _slideBufFreePos < lengthToRead)
  91. {
  92. // Shift the data to the beginning of the buffer
  93. int iTo = 0;
  94. for (int iFrom = _slideBufStartPos; iFrom < _slideBufFreePos; iFrom++, iTo++)
  95. {
  96. _slideBuffer[iTo] = _slideBuffer[iFrom];
  97. }
  98. _slideBufFreePos -= _slideBufStartPos; // Note the -=
  99. _slideBufStartPos = 0;
  100. }
  101. int obtained = StreamUtils.ReadRequestedBytes(_stream, _slideBuffer, _slideBufFreePos, lengthToRead);
  102. _slideBufFreePos += obtained;
  103. // Recalculate how much data we now have
  104. byteCount = _slideBufFreePos - _slideBufStartPos;
  105. if (byteCount >= BLOCK_AND_AUTH)
  106. {
  107. var read = TransformAndBufferBlock(buffer, offset, bytesLeftToRead, CRYPTO_BLOCK_SIZE);
  108. nBytes += read;
  109. offset += read;
  110. }
  111. else
  112. {
  113. // Last round.
  114. if (byteCount > AUTH_CODE_LENGTH)
  115. {
  116. // At least one byte of data plus auth code
  117. int finalBlock = byteCount - AUTH_CODE_LENGTH;
  118. nBytes += TransformAndBufferBlock(buffer, offset, bytesLeftToRead, finalBlock);
  119. }
  120. else if (byteCount < AUTH_CODE_LENGTH)
  121. throw new Exception("Internal error missed auth code"); // Coding bug
  122. // Final block done. Check Auth code.
  123. byte[] calcAuthCode = _transform.GetAuthCode();
  124. for (int i = 0; i < AUTH_CODE_LENGTH; i++)
  125. {
  126. if (calcAuthCode[i] != _slideBuffer[_slideBufStartPos + i])
  127. {
  128. throw new Exception("AES Authentication Code does not match. This is a super-CRC check on the data in the file after compression and encryption. \r\n"
  129. + "The file may be damaged.");
  130. }
  131. }
  132. // don't need this any more, so use it as a 'complete' flag
  133. _slideBuffer = null;
  134. break; // Reached the auth code
  135. }
  136. }
  137. return nBytes;
  138. }
  139. // read some buffered data
  140. private int ReadBufferedData(byte[] buffer, int offset, int count)
  141. {
  142. int copyCount = Math.Min(count, _transformBufferFreePos - _transformBufferStartPos);
  143. Array.Copy(_transformBuffer, _transformBufferStartPos, buffer, offset, count);
  144. _transformBufferStartPos += copyCount;
  145. return copyCount;
  146. }
  147. // Perform the crypto transform, and buffer the data if less than one block has been requested.
  148. private int TransformAndBufferBlock(byte[] buffer, int offset, int count, int blockSize)
  149. {
  150. // If the requested data is greater than one block, transform it directly into the output
  151. // If it's smaller, do it into a temporary buffer and copy the requested part
  152. bool bufferRequired = (blockSize > count);
  153. if (bufferRequired && _transformBuffer == null)
  154. _transformBuffer = new byte[CRYPTO_BLOCK_SIZE];
  155. var targetBuffer = bufferRequired ? _transformBuffer : buffer;
  156. var targetOffset = bufferRequired ? 0 : offset;
  157. // Transform the data
  158. _transform.TransformBlock(_slideBuffer,
  159. _slideBufStartPos,
  160. blockSize,
  161. targetBuffer,
  162. targetOffset);
  163. _slideBufStartPos += blockSize;
  164. if (!bufferRequired)
  165. {
  166. return blockSize;
  167. }
  168. else
  169. {
  170. Array.Copy(_transformBuffer, 0, buffer, offset, count);
  171. _transformBufferStartPos = count;
  172. _transformBufferFreePos = blockSize;
  173. return count;
  174. }
  175. }
  176. /// <summary>
  177. /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written.
  178. /// </summary>
  179. /// <param name="buffer">An array of bytes. This method copies count bytes from buffer to the current stream. </param>
  180. /// <param name="offset">The byte offset in buffer at which to begin copying bytes to the current stream. </param>
  181. /// <param name="count">The number of bytes to be written to the current stream. </param>
  182. public override void Write(byte[] buffer, int offset, int count)
  183. {
  184. // ZipAESStream is used for reading but not for writing. Writing uses the ZipAESTransform directly.
  185. throw new NotImplementedException();
  186. }
  187. }
  188. }