Skip to content

Commit

Permalink
Add support for AES encryption
Browse files Browse the repository at this point in the history
  • Loading branch information
Numpsy authored and remogloor committed Oct 18, 2022
1 parent d2a0c68 commit 769b6b3
Show file tree
Hide file tree
Showing 8 changed files with 468 additions and 76 deletions.
2 changes: 1 addition & 1 deletion src/ICSharpCode.SharpZipLib/Encryption/ZipAESStream.cs
Expand Up @@ -40,7 +40,7 @@ public ZipAESStream(Stream stream, ZipAESTransform transform, CryptoStreamMode m
}

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

// Blocksize is always 16 here, even for AES-256 which has transform.InputBlockSize of 32.
private const int CRYPTO_BLOCK_SIZE = 16;
Expand Down
39 changes: 35 additions & 4 deletions src/ICSharpCode.SharpZipLib/Encryption/ZipAESTransform.cs
Expand Up @@ -8,8 +8,6 @@ namespace ICSharpCode.SharpZipLib.Encryption
/// </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;

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

private bool _writeMode;
private Action<int> _appendHmac = remaining => { };

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

// Use empty IV for AES
_encryptor = rm.CreateEncryptor(key1bytes, new byte[16]);
_pwdVerifier = pdb.GetBytes(PWD_VER_LENGTH);
_pwdVerifier = pdb.GetBytes(Zip.ZipConstants.AESPasswordVerifyLength);
//
_hmacsha1 = IncrementalHash.CreateHMAC(HashAlgorithmName.SHA1, key2bytes);
_writeMode = writeMode;
}

/// <summary>
/// Append all of the last transformed input data to the HMAC.
/// </summary>
public void AppendAllPending()
{
_appendHmac(0);
}

/// <summary>
/// Append all except the number of bytes specified by remaining of the last transformed input data to the HMAC.
/// </summary>
/// <param name="remaining">The number of bytes not to be added to the HMAC. The excluded bytes are form the end.</param>
public void AppendFinal(int remaining)
{
_appendHmac(remaining);
}

/// <summary>
/// Implement the ICryptoTransform method.
/// </summary>
Expand All @@ -78,8 +94,16 @@ public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, b
// This does not change the inputBuffer. Do this before decryption for read mode.
if (!_writeMode)
{
_hmacsha1.AppendData(inputBuffer, inputOffset, inputCount);
if (!ManualHmac)
{
_hmacsha1.AppendData(inputBuffer, inputOffset, inputCount);
}
else
{
_appendHmac = remaining => _hmacsha1.AppendData(inputBuffer, inputOffset, inputCount - remaining);
}
}

// Encrypt with AES in CTR mode. Regards to Dr Brian Gladman for this.
int ix = 0;
while (ix < inputCount)
Expand Down Expand Up @@ -168,6 +192,13 @@ public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int input
/// </summary>
public bool CanReuseTransform => true;

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

/// <summary>
/// Cleanup internal state.
/// </summary>
Expand Down
@@ -1,6 +1,7 @@
using System;
using System.IO;
using System.Security.Cryptography;
using ICSharpCode.SharpZipLib.Encryption;

namespace ICSharpCode.SharpZipLib.Zip.Compression.Streams
{
Expand Down Expand Up @@ -92,9 +93,25 @@ public byte[] ClearText
public int Available
{
get { return available; }
set { available = value; }
set
{
if (cryptoTransform is ZipAESTransform ct)
{
ct.AppendFinal(value);
}

available = value;
}
}

/// <summary>
/// A limitation how much data is decrypted. If null all the data in the input buffer will be decrypted.
/// Setting limit is important in case the HMAC has to be calculated for each zip entry. In that case
/// it is not possible to decrypt all available data in the input buffer, and only the data
/// belonging to the current zip entry must be decrypted so that the HMAC is correctly calculated.
/// </summary>
internal int? DecryptionLimit { get; set; }

/// <summary>
/// Call <see cref="Inflater.SetInput(byte[], int, int)"/> passing the current clear text buffer contents.
/// </summary>
Expand All @@ -113,6 +130,11 @@ public void SetInflaterInput(Inflater inflater)
/// </summary>
public void Fill()
{
if (cryptoTransform is ZipAESTransform ct)
{
ct.AppendAllPending();
}

rawLength = 0;
int toRead = rawData.Length;

Expand All @@ -127,13 +149,11 @@ public void Fill()
toRead -= count;
}

clearTextLength = rawLength;
if (cryptoTransform != null)
{
clearTextLength = cryptoTransform.TransformBlock(rawData, 0, rawLength, clearText, 0);
}
else
{
clearTextLength = rawLength;
var size = CalculateDecryptionSize(rawLength);
cryptoTransform.TransformBlock(rawData, 0, size, clearText, 0);
}

available = clearTextLength;
Expand Down Expand Up @@ -290,7 +310,9 @@ public ICryptoTransform CryptoTransform
clearTextLength = rawLength;
if (available > 0)
{
cryptoTransform.TransformBlock(rawData, rawLength - available, available, clearText, rawLength - available);
var size = CalculateDecryptionSize(available);

cryptoTransform.TransformBlock(rawData, rawLength - available, size, clearText, rawLength - available);
}
}
else
Expand All @@ -301,6 +323,19 @@ public ICryptoTransform CryptoTransform
}
}

private int CalculateDecryptionSize(int availableBufferSize)
{
int size = DecryptionLimit ?? availableBufferSize;
size = Math.Min(size, availableBufferSize);

if (DecryptionLimit.HasValue)
{
DecryptionLimit -= size;
}

return size;
}

#region Instance Fields

private int rawLength;
Expand Down Expand Up @@ -459,9 +494,10 @@ public long Skip(long count)
/// <summary>
/// Clear any cryptographic state.
/// </summary>
protected void StopDecrypting()
protected virtual void StopDecrypting()
{
inputBuffer.CryptoTransform = null;
inputBuffer.DecryptionLimit = null;
}

/// <summary>
Expand Down
10 changes: 10 additions & 0 deletions src/ICSharpCode.SharpZipLib/Zip/ZipConstants.cs
Expand Up @@ -366,6 +366,16 @@ public static class ZipConstants
[Obsolete("Use CryptoHeaderSize instead")]
public const int CRYPTO_HEADER_SIZE = 12;

/// <summary>
/// The number of bytes in the WinZipAes Auth Code.
/// </summary>
internal const int AESAuthCodeLength = 10;

/// <summary>
/// The number of bytes in the password verifier for WinZipAes.
/// </summary>
internal const int AESPasswordVerifyLength = 2;

/// <summary>
/// The size of the Zip64 central directory locator.
/// </summary>
Expand Down
15 changes: 14 additions & 1 deletion src/ICSharpCode.SharpZipLib/Zip/ZipEntry.cs
Expand Up @@ -824,6 +824,19 @@ public int AESKeySize
}
}

/// <summary>
/// Gets the AES Version
/// 1: AE-1
/// 2: AE-2
/// </summary>
public int AESVersion
{
get
{
return _aesVer;
}
}

/// <summary>
/// AES Encryption strength for storage in extra data in entry header.
/// 1 is 128 bit, 2 is 192 bit, 3 is 256 bit.
Expand Down Expand Up @@ -1149,7 +1162,7 @@ public static string CleanName(string name)

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

#endregion Instance Fields
Expand Down

0 comments on commit 769b6b3

Please sign in to comment.