ZipAESStream.cs 7.3 KB

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