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