Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AES decryption support in ZipInputStream #551

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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;
public 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);
remogloor marked this conversation as resolved.
Show resolved Hide resolved
}

/// <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; }
remogloor marked this conversation as resolved.
Show resolved Hide resolved

/// <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)
{
if (!DecryptionLimit.HasValue)
{
return availableBufferSize;
}

var size = Math.Min(DecryptionLimit.Value, availableBufferSize);
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