ZipAESTransform.cs 5.8 KB

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