Skip to content

Commit

Permalink
Basic AES decryption support for ZipInputStream
Browse files Browse the repository at this point in the history
  • Loading branch information
Numpsy committed Aug 25, 2019
1 parent 81445e2 commit 372ce27
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ public long Skip(long count)
/// <summary>
/// Clear any cryptographic state.
/// </summary>
protected void StopDecrypting()
protected virtual void StopDecrypting()
{
inputBuffer.CryptoTransform = null;
}
Expand Down
110 changes: 93 additions & 17 deletions src/ICSharpCode.SharpZipLib/Zip/ZipInputStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -264,9 +264,20 @@ public ZipEntry GetNextEntry()
size = entry.Size;
}

if (method == (int)CompressionMethod.Stored && (!isCrypted && csize != size || (isCrypted && csize - ZipConstants.CryptoHeaderSize != size)))
if (method == (int)CompressionMethod.Stored)
{
if (!isCrypted && csize != size || (isCrypted && csize - ZipConstants.CryptoHeaderSize != size))
{
throw new ZipException("Stored, but compressed != uncompressed");
}
}
else if (method == (int)CompressionMethod.WinZipAES && entry.CompressionMethod == CompressionMethod.Stored)
{
throw new ZipException("Stored, but compressed != uncompressed");
var sizeWithoutAesOverhead = csize - (entry.AESSaltLen + ZipConstants.AESPasswordVerifyLength + ZipConstants.AESAuthCodeLength);
if (sizeWithoutAesOverhead != size)
{
throw new ZipException("Stored, but compressed != uncompressed");
}
}

// Determine how to handle reading of data if this is attempted.
Expand Down Expand Up @@ -308,6 +319,40 @@ private void ReadDataDescriptor()
entry.Size = size;
}

/// <summary>
/// Complete any decryption processing and clear any cryptographic state.
/// </summary>
protected override void StopDecrypting()
{
base.StopDecrypting();

if (entry.AESKeySize != 0)
{
byte[] authBytes = new byte[ZipConstants.AESAuthCodeLength];
int authBytesRead = inputBuffer.ReadRawBuffer(authBytes, 0, authBytes.Length);

if (authBytesRead < ZipConstants.AESAuthCodeLength)
{
throw new Exception("Internal error missed auth code"); // Coding bug
// Final block done. Check Auth code.
}

/*
byte[] calcAuthCode = (this.cryptoTransform as ZipAESTransform).GetAuthCode();
for (int i = 0; i < ZipConstants.AESAuthCodeLength; i++)
{
if (calcAuthCode[i] != authBytes[i])
{
// throw new Exception("AES Authentication Code does not match. This is a super-CRC check on the data in the file after compression and encryption. \r\n"
// + "The file may be damaged.");
}
}
*/

// Dispose the transform?
}
}

/// <summary>
/// Complete cleanup as the final part of closing.
/// </summary>
Expand Down Expand Up @@ -499,27 +544,58 @@ private int InitialRead(byte[] destination, int offset, int count)
throw new ZipException("No password set.");
}

// Generate and set crypto transform...
var managed = new PkzipClassicManaged();
byte[] key = PkzipClassic.GenerateKeys(ZipStrings.ConvertToArray(password));
if (entry.AESKeySize == 0)
{
// Generate and set crypto transform...
var managed = new PkzipClassicManaged();
byte[] key = PkzipClassic.GenerateKeys(ZipStrings.ConvertToArray(password));

inputBuffer.CryptoTransform = managed.CreateDecryptor(key, null);
inputBuffer.CryptoTransform = managed.CreateDecryptor(key, null);

byte[] cryptbuffer = new byte[ZipConstants.CryptoHeaderSize];
inputBuffer.ReadClearTextBuffer(cryptbuffer, 0, ZipConstants.CryptoHeaderSize);
byte[] cryptbuffer = new byte[ZipConstants.CryptoHeaderSize];
inputBuffer.ReadClearTextBuffer(cryptbuffer, 0, ZipConstants.CryptoHeaderSize);

if (cryptbuffer[ZipConstants.CryptoHeaderSize - 1] != entry.CryptoCheckValue)
{
throw new ZipException("Invalid password");
}
if (cryptbuffer[ZipConstants.CryptoHeaderSize - 1] != entry.CryptoCheckValue)
{
throw new ZipException("Invalid password");
}

if (csize >= ZipConstants.CryptoHeaderSize)
{
csize -= ZipConstants.CryptoHeaderSize;
if (csize >= ZipConstants.CryptoHeaderSize)
{
csize -= ZipConstants.CryptoHeaderSize;
}
else if ((entry.Flags & (int)GeneralBitFlags.Descriptor) == 0)
{
throw new ZipException(string.Format("Entry compressed size {0} too small for encryption", csize));
}
}
else if ((entry.Flags & (int)GeneralBitFlags.Descriptor) == 0)
else
{
throw new ZipException(string.Format("Entry compressed size {0} too small for encryption", csize));
int saltLen = entry.AESSaltLen;
byte[] saltBytes = new byte[saltLen];
int saltIn = inputBuffer.ReadRawBuffer(saltBytes, 0, saltLen);

if (saltIn != saltLen)
throw new ZipException("AES Salt expected " + saltLen + " got " + saltIn);

//
byte[] pwdVerifyRead = new byte[ZipConstants.AESPasswordVerifyLength];
int pwdBytesRead = inputBuffer.ReadRawBuffer(pwdVerifyRead, 0, pwdVerifyRead.Length);

if (pwdBytesRead != pwdVerifyRead.Length)
throw new EndOfStreamException();

int blockSize = entry.AESKeySize / 8; // bits to bytes

var decryptor = new ZipAESTransform(password, saltBytes, blockSize, false);
byte[] pwdVerifyCalc = decryptor.PwdVerifier;
if (pwdVerifyCalc[0] != pwdVerifyRead[0] || pwdVerifyCalc[1] != pwdVerifyRead[1])
throw new ZipException("Invalid password for AES");

// The AES data has saltLen+AESPasswordVerifyLength bytes as a header, and AESAuthCodeLength bytes
// as a footer.
csize -= (saltLen + ZipConstants.AESPasswordVerifyLength + ZipConstants.AESAuthCodeLength);
inputBuffer.CryptoTransform = decryptor;
}
}
else
Expand Down

0 comments on commit 372ce27

Please sign in to comment.