ZipAESTransform.cs 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. using System;
  2. using System.Security.Cryptography;
  3. namespace ICSharpCode.SharpZipLib.Encryption
  4. {
  5. /// <summary>
  6. /// Transforms stream using AES in CTR mode
  7. /// </summary>
  8. internal class ZipAESTransform : ICryptoTransform
  9. {
  10. #if NET45
  11. class IncrementalHash : HMACSHA1
  12. {
  13. bool _finalised;
  14. public IncrementalHash(byte[] key) : base(key) { }
  15. public static IncrementalHash CreateHMAC(string n, byte[] key) => new IncrementalHash(key);
  16. public void AppendData(byte[] buffer, int offset, int count) => TransformBlock(buffer, offset, count, buffer, offset);
  17. public byte[] GetHashAndReset()
  18. {
  19. if (!_finalised)
  20. {
  21. byte[] dummy = new byte[0];
  22. TransformFinalBlock(dummy, 0, 0);
  23. _finalised = true;
  24. }
  25. return Hash;
  26. }
  27. }
  28. static class HashAlgorithmName
  29. {
  30. public static string SHA1 = null;
  31. }
  32. #endif
  33. private const int PWD_VER_LENGTH = 2;
  34. // WinZip use iteration count of 1000 for PBKDF2 key generation
  35. private const int KEY_ROUNDS = 1000;
  36. // For 128-bit AES (16 bytes) the encryption is implemented as expected.
  37. // For 256-bit AES (32 bytes) WinZip do full 256 bit AES of the nonce to create the encryption
  38. // block but use only the first 16 bytes of it, and discard the second half.
  39. private const int ENCRYPT_BLOCK = 16;
  40. private int _blockSize;
  41. private readonly ICryptoTransform _encryptor;
  42. private readonly byte[] _counterNonce;
  43. private byte[] _encryptBuffer;
  44. private int _encrPos;
  45. private byte[] _pwdVerifier;
  46. private IncrementalHash _hmacsha1;
  47. private byte[] _authCode = null;
  48. private bool _writeMode;
  49. /// <summary>
  50. /// Constructor.
  51. /// </summary>
  52. /// <param name="key">Password string</param>
  53. /// <param name="saltBytes">Random bytes, length depends on encryption strength.
  54. /// 128 bits = 8 bytes, 192 bits = 12 bytes, 256 bits = 16 bytes.</param>
  55. /// <param name="blockSize">The encryption strength, in bytes eg 16 for 128 bits.</param>
  56. /// <param name="writeMode">True when creating a zip, false when reading. For the AuthCode.</param>
  57. ///
  58. public ZipAESTransform(string key, byte[] saltBytes, int blockSize, bool writeMode)
  59. {
  60. if (blockSize != 16 && blockSize != 32) // 24 valid for AES but not supported by Winzip
  61. throw new Exception("Invalid blocksize " + blockSize + ". Must be 16 or 32.");
  62. if (saltBytes.Length != blockSize / 2)
  63. throw new Exception("Invalid salt len. Must be " + blockSize / 2 + " for blocksize " + blockSize);
  64. // initialise the encryption buffer and buffer pos
  65. _blockSize = blockSize;
  66. _encryptBuffer = new byte[_blockSize];
  67. _encrPos = ENCRYPT_BLOCK;
  68. // Performs the equivalent of derive_key in Dr Brian Gladman's pwd2key.c
  69. var pdb = new Rfc2898DeriveBytes(key, saltBytes, KEY_ROUNDS);
  70. var rm = Aes.Create();
  71. rm.Mode = CipherMode.ECB; // No feedback from cipher for CTR mode
  72. _counterNonce = new byte[_blockSize];
  73. byte[] key1bytes = pdb.GetBytes(_blockSize);
  74. byte[] key2bytes = pdb.GetBytes(_blockSize);
  75. // Use empty IV for AES
  76. _encryptor = rm.CreateEncryptor(key1bytes, new byte[16]);
  77. _pwdVerifier = pdb.GetBytes(PWD_VER_LENGTH);
  78. //
  79. _hmacsha1 = IncrementalHash.CreateHMAC(HashAlgorithmName.SHA1, key2bytes);
  80. _writeMode = writeMode;
  81. }
  82. /// <summary>
  83. /// Implement the ICryptoTransform method.
  84. /// </summary>
  85. public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
  86. {
  87. // Pass the data stream to the hash algorithm for generating the Auth Code.
  88. // This does not change the inputBuffer. Do this before decryption for read mode.
  89. if (!_writeMode)
  90. {
  91. _hmacsha1.AppendData(inputBuffer, inputOffset, inputCount);
  92. }
  93. // Encrypt with AES in CTR mode. Regards to Dr Brian Gladman for this.
  94. int ix = 0;
  95. while (ix < inputCount)
  96. {
  97. if (_encrPos == ENCRYPT_BLOCK)
  98. {
  99. /* increment encryption nonce */
  100. int j = 0;
  101. while (++_counterNonce[j] == 0)
  102. {
  103. ++j;
  104. }
  105. /* encrypt the nonce to form next xor buffer */
  106. _encryptor.TransformBlock(_counterNonce, 0, _blockSize, _encryptBuffer, 0);
  107. _encrPos = 0;
  108. }
  109. outputBuffer[ix + outputOffset] = (byte)(inputBuffer[ix + inputOffset] ^ _encryptBuffer[_encrPos++]);
  110. //
  111. ix++;
  112. }
  113. if (_writeMode)
  114. {
  115. // This does not change the buffer.
  116. _hmacsha1.AppendData(outputBuffer, outputOffset, inputCount);
  117. }
  118. return inputCount;
  119. }
  120. /// <summary>
  121. /// Returns the 2 byte password verifier
  122. /// </summary>
  123. public byte[] PwdVerifier
  124. {
  125. get
  126. {
  127. return _pwdVerifier;
  128. }
  129. }
  130. /// <summary>
  131. /// Returns the 10 byte AUTH CODE to be checked or appended immediately following the AES data stream.
  132. /// </summary>
  133. public byte[] GetAuthCode()
  134. {
  135. if (_authCode == null)
  136. {
  137. _authCode = _hmacsha1.GetHashAndReset();
  138. }
  139. return _authCode;
  140. }
  141. #region ICryptoTransform Members
  142. /// <summary>
  143. /// Not implemented.
  144. /// </summary>
  145. public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
  146. {
  147. if(inputCount > 0)
  148. {
  149. throw new NotImplementedException("TransformFinalBlock is not implemented and inputCount is greater than 0");
  150. }
  151. return new byte[0];
  152. }
  153. /// <summary>
  154. /// Gets the size of the input data blocks in bytes.
  155. /// </summary>
  156. public int InputBlockSize
  157. {
  158. get
  159. {
  160. return _blockSize;
  161. }
  162. }
  163. /// <summary>
  164. /// Gets the size of the output data blocks in bytes.
  165. /// </summary>
  166. public int OutputBlockSize
  167. {
  168. get
  169. {
  170. return _blockSize;
  171. }
  172. }
  173. /// <summary>
  174. /// Gets a value indicating whether multiple blocks can be transformed.
  175. /// </summary>
  176. public bool CanTransformMultipleBlocks
  177. {
  178. get
  179. {
  180. return true;
  181. }
  182. }
  183. /// <summary>
  184. /// Gets a value indicating whether the current transform can be reused.
  185. /// </summary>
  186. public bool CanReuseTransform
  187. {
  188. get
  189. {
  190. return true;
  191. }
  192. }
  193. /// <summary>
  194. /// Cleanup internal state.
  195. /// </summary>
  196. public void Dispose()
  197. {
  198. _encryptor.Dispose();
  199. }
  200. #endregion ICryptoTransform Members
  201. }
  202. }