ZipAESTransform.cs 6.0 KB

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