Skip to content

Commit 769b6b3

Browse files
Numpsyremogloor
authored andcommitted
Add support for AES encryption
1 parent d2a0c68 commit 769b6b3

File tree

8 files changed

+468
-76
lines changed

8 files changed

+468
-76
lines changed

src/ICSharpCode.SharpZipLib/Encryption/ZipAESStream.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public ZipAESStream(Stream stream, ZipAESTransform transform, CryptoStreamMode m
4040
}
4141

4242
// The final n bytes of the AES stream contain the Auth Code.
43-
public const int AUTH_CODE_LENGTH = 10;
43+
private const int AUTH_CODE_LENGTH = Zip.ZipConstants.AESAuthCodeLength;
4444

4545
// Blocksize is always 16 here, even for AES-256 which has transform.InputBlockSize of 32.
4646
private const int CRYPTO_BLOCK_SIZE = 16;

src/ICSharpCode.SharpZipLib/Encryption/ZipAESTransform.cs

+35-4
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ namespace ICSharpCode.SharpZipLib.Encryption
88
/// </summary>
99
internal class ZipAESTransform : ICryptoTransform
1010
{
11-
private const int PWD_VER_LENGTH = 2;
12-
1311
// WinZip use iteration count of 1000 for PBKDF2 key generation
1412
private const int KEY_ROUNDS = 1000;
1513

@@ -28,6 +26,7 @@ internal class ZipAESTransform : ICryptoTransform
2826
private byte[] _authCode = null;
2927

3028
private bool _writeMode;
29+
private Action<int> _appendHmac = remaining => { };
3130

3231
/// <summary>
3332
/// Constructor.
@@ -63,12 +62,29 @@ public ZipAESTransform(string key, byte[] saltBytes, int blockSize, bool writeMo
6362

6463
// Use empty IV for AES
6564
_encryptor = rm.CreateEncryptor(key1bytes, new byte[16]);
66-
_pwdVerifier = pdb.GetBytes(PWD_VER_LENGTH);
65+
_pwdVerifier = pdb.GetBytes(Zip.ZipConstants.AESPasswordVerifyLength);
6766
//
6867
_hmacsha1 = IncrementalHash.CreateHMAC(HashAlgorithmName.SHA1, key2bytes);
6968
_writeMode = writeMode;
7069
}
7170

71+
/// <summary>
72+
/// Append all of the last transformed input data to the HMAC.
73+
/// </summary>
74+
public void AppendAllPending()
75+
{
76+
_appendHmac(0);
77+
}
78+
79+
/// <summary>
80+
/// Append all except the number of bytes specified by remaining of the last transformed input data to the HMAC.
81+
/// </summary>
82+
/// <param name="remaining">The number of bytes not to be added to the HMAC. The excluded bytes are form the end.</param>
83+
public void AppendFinal(int remaining)
84+
{
85+
_appendHmac(remaining);
86+
}
87+
7288
/// <summary>
7389
/// Implement the ICryptoTransform method.
7490
/// </summary>
@@ -78,8 +94,16 @@ public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, b
7894
// This does not change the inputBuffer. Do this before decryption for read mode.
7995
if (!_writeMode)
8096
{
81-
_hmacsha1.AppendData(inputBuffer, inputOffset, inputCount);
97+
if (!ManualHmac)
98+
{
99+
_hmacsha1.AppendData(inputBuffer, inputOffset, inputCount);
100+
}
101+
else
102+
{
103+
_appendHmac = remaining => _hmacsha1.AppendData(inputBuffer, inputOffset, inputCount - remaining);
104+
}
82105
}
106+
83107
// Encrypt with AES in CTR mode. Regards to Dr Brian Gladman for this.
84108
int ix = 0;
85109
while (ix < inputCount)
@@ -168,6 +192,13 @@ public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int input
168192
/// </summary>
169193
public bool CanReuseTransform => true;
170194

195+
/// <summary>
196+
/// Gets of sets a value indicating if the HMAC is updates on every read of if updating the HMAC has to be controlled manually
197+
/// Manual control of HMAC is needed in case not all the Transformed data should be automatically added to the HMAC.
198+
/// E.g. because its not know how much data belongs to the current entry before the data is decrypted and analyzed.
199+
/// </summary>
200+
public bool ManualHmac { get; set; }
201+
171202
/// <summary>
172203
/// Cleanup internal state.
173204
/// </summary>

src/ICSharpCode.SharpZipLib/Zip/Compression/Streams/InflaterInputStream.cs

+44-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.IO;
33
using System.Security.Cryptography;
4+
using ICSharpCode.SharpZipLib.Encryption;
45

56
namespace ICSharpCode.SharpZipLib.Zip.Compression.Streams
67
{
@@ -92,9 +93,25 @@ public byte[] ClearText
9293
public int Available
9394
{
9495
get { return available; }
95-
set { available = value; }
96+
set
97+
{
98+
if (cryptoTransform is ZipAESTransform ct)
99+
{
100+
ct.AppendFinal(value);
101+
}
102+
103+
available = value;
104+
}
96105
}
97106

107+
/// <summary>
108+
/// A limitation how much data is decrypted. If null all the data in the input buffer will be decrypted.
109+
/// Setting limit is important in case the HMAC has to be calculated for each zip entry. In that case
110+
/// it is not possible to decrypt all available data in the input buffer, and only the data
111+
/// belonging to the current zip entry must be decrypted so that the HMAC is correctly calculated.
112+
/// </summary>
113+
internal int? DecryptionLimit { get; set; }
114+
98115
/// <summary>
99116
/// Call <see cref="Inflater.SetInput(byte[], int, int)"/> passing the current clear text buffer contents.
100117
/// </summary>
@@ -113,6 +130,11 @@ public void SetInflaterInput(Inflater inflater)
113130
/// </summary>
114131
public void Fill()
115132
{
133+
if (cryptoTransform is ZipAESTransform ct)
134+
{
135+
ct.AppendAllPending();
136+
}
137+
116138
rawLength = 0;
117139
int toRead = rawData.Length;
118140

@@ -127,13 +149,11 @@ public void Fill()
127149
toRead -= count;
128150
}
129151

152+
clearTextLength = rawLength;
130153
if (cryptoTransform != null)
131154
{
132-
clearTextLength = cryptoTransform.TransformBlock(rawData, 0, rawLength, clearText, 0);
133-
}
134-
else
135-
{
136-
clearTextLength = rawLength;
155+
var size = CalculateDecryptionSize(rawLength);
156+
cryptoTransform.TransformBlock(rawData, 0, size, clearText, 0);
137157
}
138158

139159
available = clearTextLength;
@@ -290,7 +310,9 @@ public ICryptoTransform CryptoTransform
290310
clearTextLength = rawLength;
291311
if (available > 0)
292312
{
293-
cryptoTransform.TransformBlock(rawData, rawLength - available, available, clearText, rawLength - available);
313+
var size = CalculateDecryptionSize(available);
314+
315+
cryptoTransform.TransformBlock(rawData, rawLength - available, size, clearText, rawLength - available);
294316
}
295317
}
296318
else
@@ -301,6 +323,19 @@ public ICryptoTransform CryptoTransform
301323
}
302324
}
303325

326+
private int CalculateDecryptionSize(int availableBufferSize)
327+
{
328+
int size = DecryptionLimit ?? availableBufferSize;
329+
size = Math.Min(size, availableBufferSize);
330+
331+
if (DecryptionLimit.HasValue)
332+
{
333+
DecryptionLimit -= size;
334+
}
335+
336+
return size;
337+
}
338+
304339
#region Instance Fields
305340

306341
private int rawLength;
@@ -459,9 +494,10 @@ public long Skip(long count)
459494
/// <summary>
460495
/// Clear any cryptographic state.
461496
/// </summary>
462-
protected void StopDecrypting()
497+
protected virtual void StopDecrypting()
463498
{
464499
inputBuffer.CryptoTransform = null;
500+
inputBuffer.DecryptionLimit = null;
465501
}
466502

467503
/// <summary>

src/ICSharpCode.SharpZipLib/Zip/ZipConstants.cs

+10
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,16 @@ public static class ZipConstants
366366
[Obsolete("Use CryptoHeaderSize instead")]
367367
public const int CRYPTO_HEADER_SIZE = 12;
368368

369+
/// <summary>
370+
/// The number of bytes in the WinZipAes Auth Code.
371+
/// </summary>
372+
internal const int AESAuthCodeLength = 10;
373+
374+
/// <summary>
375+
/// The number of bytes in the password verifier for WinZipAes.
376+
/// </summary>
377+
internal const int AESPasswordVerifyLength = 2;
378+
369379
/// <summary>
370380
/// The size of the Zip64 central directory locator.
371381
/// </summary>

src/ICSharpCode.SharpZipLib/Zip/ZipEntry.cs

+14-1
Original file line numberDiff line numberDiff line change
@@ -824,6 +824,19 @@ public int AESKeySize
824824
}
825825
}
826826

827+
/// <summary>
828+
/// Gets the AES Version
829+
/// 1: AE-1
830+
/// 2: AE-2
831+
/// </summary>
832+
public int AESVersion
833+
{
834+
get
835+
{
836+
return _aesVer;
837+
}
838+
}
839+
827840
/// <summary>
828841
/// AES Encryption strength for storage in extra data in entry header.
829842
/// 1 is 128 bit, 2 is 192 bit, 3 is 256 bit.
@@ -1149,7 +1162,7 @@ public static string CleanName(string name)
11491162

11501163
private bool forceZip64_;
11511164
private byte cryptoCheckValue_;
1152-
private int _aesVer; // Version number (2 = AE-2 ?). Assigned but not used.
1165+
private int _aesVer; // Version number (1 = AE-1, 2 = AE-2)
11531166
private int _aesEncryptionStrength; // Encryption strength 1 = 128 2 = 192 3 = 256
11541167

11551168
#endregion Instance Fields

0 commit comments

Comments
 (0)