| // | 
| // ZipAESTransform.cs | 
| // | 
| // Copyright 2009 David Pierson | 
| // | 
| // This program is free software; you can redistribute it and/or | 
| // modify it under the terms of the GNU General Public License | 
| // as published by the Free Software Foundation; either version 2 | 
| // of the License, or (at your option) any later version. | 
| // | 
| // This program is distributed in the hope that it will be useful, | 
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | 
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
| // GNU General Public License for more details. | 
| // | 
| // You should have received a copy of the GNU General Public License | 
| // along with this program; if not, write to the Free Software | 
| // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. | 
| // | 
| // Linking this library statically or dynamically with other modules is | 
| // making a combined work based on this library.  Thus, the terms and | 
| // conditions of the GNU General Public License cover the whole | 
| // combination. | 
| //  | 
| // As a special exception, the copyright holders of this library give you | 
| // permission to link this library with independent modules to produce an | 
| // executable, regardless of the license terms of these independent | 
| // modules, and to copy and distribute the resulting executable under | 
| // terms of your choice, provided that you also meet, for each linked | 
| // independent module, the terms and conditions of the license of that | 
| // module.  An independent module is a module which is not derived from | 
| // or based on this library.  If you modify this library, you may extend | 
| // this exception to your version of the library, but you are not | 
| // obligated to do so.  If you do not wish to do so, delete this | 
| // exception statement from your version. | 
| // | 
|   | 
| #if !NET_1_1 && !NETCF_2_0 | 
| // Framework version 2.0 required for Rfc2898DeriveBytes  | 
|   | 
| using System; | 
| using System.Security.Cryptography; | 
|   | 
| namespace ICSharpCode.SharpZipLib.Encryption { | 
|   | 
|     /// <summary> | 
|     /// Transforms stream using AES in CTR mode | 
|     /// </summary> | 
|     internal class ZipAESTransform : ICryptoTransform { | 
|   | 
|         private const int PWD_VER_LENGTH = 2; | 
|   | 
|         // WinZip use iteration count of 1000 for PBKDF2 key generation | 
|         private const int KEY_ROUNDS = 1000; | 
|   | 
|         // For 128-bit AES (16 bytes) the encryption is implemented as expected. | 
|         // For 256-bit AES (32 bytes) WinZip do full 256 bit AES of the nonce to create the encryption | 
|         // block but use only the first 16 bytes of it, and discard the second half. | 
|         private const int ENCRYPT_BLOCK = 16; | 
|   | 
|         private int _blockSize; | 
|         private ICryptoTransform _encryptor; | 
|         private readonly byte[] _counterNonce; | 
|         private byte[] _encryptBuffer; | 
|         private int _encrPos; | 
|         private byte[] _pwdVerifier; | 
|         private HMACSHA1 _hmacsha1; | 
|         private bool _finalised; | 
|   | 
|         private bool _writeMode; | 
|   | 
|         /// <summary> | 
|         /// Constructor. | 
|         /// </summary> | 
|         /// <param name="key">Password string</param> | 
|         /// <param name="saltBytes">Random bytes, length depends on encryption strength. | 
|         /// 128 bits = 8 bytes, 192 bits = 12 bytes, 256 bits = 16 bytes.</param> | 
|         /// <param name="blockSize">The encryption strength, in bytes eg 16 for 128 bits.</param> | 
|         /// <param name="writeMode">True when creating a zip, false when reading. For the AuthCode.</param> | 
|         /// | 
|         public ZipAESTransform(string key, byte[] saltBytes, int blockSize, bool writeMode) { | 
|   | 
|             if (blockSize != 16 && blockSize != 32)    // 24 valid for AES but not supported by Winzip | 
|                 throw new Exception("Invalid blocksize " + blockSize + ". Must be 16 or 32."); | 
|             if (saltBytes.Length != blockSize / 2) | 
|                 throw new Exception("Invalid salt len. Must be " + blockSize / 2 + " for blocksize " + blockSize); | 
|             // initialise the encryption buffer and buffer pos | 
|             _blockSize = blockSize; | 
|             _encryptBuffer = new byte[_blockSize]; | 
|             _encrPos = ENCRYPT_BLOCK; | 
|   | 
|             // Performs the equivalent of derive_key in Dr Brian Gladman's pwd2key.c | 
|             Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(key, saltBytes, KEY_ROUNDS); | 
|             RijndaelManaged rm = new RijndaelManaged(); | 
|             rm.Mode = CipherMode.ECB;            // No feedback from cipher for CTR mode | 
|             _counterNonce = new byte[_blockSize]; | 
|             byte[] byteKey1 = pdb.GetBytes(_blockSize); | 
|             byte[] byteKey2 = pdb.GetBytes(_blockSize); | 
|             _encryptor = rm.CreateEncryptor(byteKey1, byteKey2); | 
|             _pwdVerifier = pdb.GetBytes(PWD_VER_LENGTH); | 
|             // | 
|             _hmacsha1 = new HMACSHA1(byteKey2); | 
|             _writeMode = writeMode; | 
|         } | 
|   | 
|         /// <summary> | 
|         /// Implement the ICryptoTransform method. | 
|         /// </summary> | 
|         public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) { | 
|   | 
|             // Pass the data stream to the hash algorithm for generating the Auth Code. | 
|             // This does not change the inputBuffer. Do this before decryption for read mode. | 
|             if (!_writeMode) { | 
|                 _hmacsha1.TransformBlock(inputBuffer, inputOffset, inputCount, inputBuffer, inputOffset); | 
|             } | 
|             // Encrypt with AES in CTR mode. Regards to Dr Brian Gladman for this. | 
|             int ix = 0; | 
|             while (ix < inputCount) { | 
|                 if (_encrPos == ENCRYPT_BLOCK) { | 
|                     /* increment encryption nonce   */ | 
|                     int j = 0; | 
|                     while (++_counterNonce[j] == 0) { | 
|                         ++j; | 
|                     } | 
|                     /* encrypt the nonce to form next xor buffer    */ | 
|                     _encryptor.TransformBlock(_counterNonce, 0, _blockSize, _encryptBuffer, 0); | 
|                     _encrPos = 0; | 
|                 } | 
|                 outputBuffer[ix + outputOffset] = (byte)(inputBuffer[ix + inputOffset] ^ _encryptBuffer[_encrPos++]); | 
|                 // | 
|                 ix++; | 
|             } | 
|             if (_writeMode) { | 
|                 // This does not change the buffer.  | 
|                 _hmacsha1.TransformBlock(outputBuffer, outputOffset, inputCount, outputBuffer, outputOffset); | 
|             } | 
|             return inputCount; | 
|         } | 
|   | 
|         /// <summary> | 
|         /// Returns the 2 byte password verifier | 
|         /// </summary> | 
|         public byte[] PwdVerifier { | 
|             get { | 
|                 return _pwdVerifier; | 
|             } | 
|         } | 
|   | 
|         /// <summary> | 
|         /// Returns the 10 byte AUTH CODE to be checked or appended immediately following the AES data stream. | 
|         /// </summary> | 
|         public byte[] GetAuthCode() { | 
|             // We usually don't get advance notice of final block. Hash requres a TransformFinal. | 
|             if (!_finalised) { | 
|                 byte[] dummy = new byte[0]; | 
|                 _hmacsha1.TransformFinalBlock(dummy, 0, 0); | 
|                 _finalised = true; | 
|             } | 
|             return _hmacsha1.Hash; | 
|         } | 
|   | 
|         #region ICryptoTransform Members | 
|   | 
|         /// <summary> | 
|         /// Not implemented. | 
|         /// </summary> | 
|         public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount) { | 
|   | 
|             throw new NotImplementedException("ZipAESTransform.TransformFinalBlock"); | 
|         } | 
|   | 
|         /// <summary> | 
|         /// Gets the size of the input data blocks in bytes. | 
|         /// </summary> | 
|         public int InputBlockSize { | 
|             get { | 
|                 return _blockSize; | 
|             } | 
|         } | 
|   | 
|         /// <summary> | 
|         /// Gets the size of the output data blocks in bytes. | 
|         /// </summary> | 
|         public int OutputBlockSize { | 
|             get { | 
|                 return _blockSize; | 
|             } | 
|         } | 
|   | 
|         /// <summary> | 
|         /// Gets a value indicating whether multiple blocks can be transformed. | 
|         /// </summary> | 
|         public bool CanTransformMultipleBlocks { | 
|             get { | 
|                 return true; | 
|             } | 
|         } | 
|   | 
|         /// <summary> | 
|         /// Gets a value indicating whether the current transform can be reused. | 
|         /// </summary> | 
|         public bool CanReuseTransform { | 
|             get { | 
|                 return true; | 
|             } | 
|         } | 
|   | 
|         /// <summary> | 
|         /// Cleanup internal state. | 
|         /// </summary> | 
|         public void Dispose() { | 
|             _encryptor.Dispose(); | 
|         } | 
|   | 
|         #endregion | 
|   | 
|     } | 
| } | 
| #endif |