Skip to content

Commit b73aebc

Browse files
committed
Basic AES decryption support for ZipInputStream
1 parent a0b3f78 commit b73aebc

File tree

2 files changed

+94
-18
lines changed

2 files changed

+94
-18
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,7 @@ public long Skip(long count)
459459
/// <summary>
460460
/// Clear any cryptographic state.
461461
/// </summary>
462-
protected void StopDecrypting()
462+
protected virtual void StopDecrypting()
463463
{
464464
inputBuffer.CryptoTransform = null;
465465
}

src/ICSharpCode.SharpZipLib/Zip/ZipInputStream.cs

Lines changed: 93 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -264,9 +264,20 @@ public ZipEntry GetNextEntry()
264264
size = entry.Size;
265265
}
266266

267-
if (method == CompressionMethod.Stored && (!isCrypted && csize != size || (isCrypted && csize - ZipConstants.CryptoHeaderSize != size)))
267+
if (method == CompressionMethod.Stored)
268+
{
269+
if (!isCrypted && csize != size || (isCrypted && csize - ZipConstants.CryptoHeaderSize != size))
270+
{
271+
throw new ZipException("Stored, but compressed != uncompressed");
272+
}
273+
}
274+
else if (method == CompressionMethod.WinZipAES && entry.CompressionMethod == CompressionMethod.Stored)
268275
{
269-
throw new ZipException("Stored, but compressed != uncompressed");
276+
var sizeWithoutAesOverhead = csize - (entry.AESSaltLen + ZipConstants.AESPasswordVerifyLength + ZipConstants.AESAuthCodeLength);
277+
if (sizeWithoutAesOverhead != size)
278+
{
279+
throw new ZipException("Stored, but compressed != uncompressed");
280+
}
270281
}
271282

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

322+
/// <summary>
323+
/// Complete any decryption processing and clear any cryptographic state.
324+
/// </summary>
325+
protected override void StopDecrypting()
326+
{
327+
base.StopDecrypting();
328+
329+
if (entry.AESKeySize != 0)
330+
{
331+
byte[] authBytes = new byte[ZipConstants.AESAuthCodeLength];
332+
int authBytesRead = inputBuffer.ReadRawBuffer(authBytes, 0, authBytes.Length);
333+
334+
if (authBytesRead < ZipConstants.AESAuthCodeLength)
335+
{
336+
throw new Exception("Internal error missed auth code"); // Coding bug
337+
// Final block done. Check Auth code.
338+
}
339+
340+
/*
341+
byte[] calcAuthCode = (this.cryptoTransform as ZipAESTransform).GetAuthCode();
342+
for (int i = 0; i < ZipConstants.AESAuthCodeLength; i++)
343+
{
344+
if (calcAuthCode[i] != authBytes[i])
345+
{
346+
// 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"
347+
// + "The file may be damaged.");
348+
}
349+
}
350+
*/
351+
352+
// Dispose the transform?
353+
}
354+
}
355+
311356
/// <summary>
312357
/// Complete cleanup as the final part of closing.
313358
/// </summary>
@@ -499,27 +544,58 @@ private int InitialRead(byte[] destination, int offset, int count)
499544
throw new ZipException("No password set.");
500545
}
501546

502-
// Generate and set crypto transform...
503-
var managed = new PkzipClassicManaged();
504-
byte[] key = PkzipClassic.GenerateKeys(ZipStrings.ConvertToArray(password));
547+
if (entry.AESKeySize == 0)
548+
{
549+
// Generate and set crypto transform...
550+
var managed = new PkzipClassicManaged();
551+
byte[] key = PkzipClassic.GenerateKeys(ZipStrings.ConvertToArray(password));
505552

506-
inputBuffer.CryptoTransform = managed.CreateDecryptor(key, null);
553+
inputBuffer.CryptoTransform = managed.CreateDecryptor(key, null);
507554

508-
byte[] cryptbuffer = new byte[ZipConstants.CryptoHeaderSize];
509-
inputBuffer.ReadClearTextBuffer(cryptbuffer, 0, ZipConstants.CryptoHeaderSize);
555+
byte[] cryptbuffer = new byte[ZipConstants.CryptoHeaderSize];
556+
inputBuffer.ReadClearTextBuffer(cryptbuffer, 0, ZipConstants.CryptoHeaderSize);
510557

511-
if (cryptbuffer[ZipConstants.CryptoHeaderSize - 1] != entry.CryptoCheckValue)
512-
{
513-
throw new ZipException("Invalid password");
514-
}
558+
if (cryptbuffer[ZipConstants.CryptoHeaderSize - 1] != entry.CryptoCheckValue)
559+
{
560+
throw new ZipException("Invalid password");
561+
}
515562

516-
if (csize >= ZipConstants.CryptoHeaderSize)
517-
{
518-
csize -= ZipConstants.CryptoHeaderSize;
563+
if (csize >= ZipConstants.CryptoHeaderSize)
564+
{
565+
csize -= ZipConstants.CryptoHeaderSize;
566+
}
567+
else if ((entry.Flags & (int)GeneralBitFlags.Descriptor) == 0)
568+
{
569+
throw new ZipException(string.Format("Entry compressed size {0} too small for encryption", csize));
570+
}
519571
}
520-
else if ((entry.Flags & (int)GeneralBitFlags.Descriptor) == 0)
572+
else
521573
{
522-
throw new ZipException(string.Format("Entry compressed size {0} too small for encryption", csize));
574+
int saltLen = entry.AESSaltLen;
575+
byte[] saltBytes = new byte[saltLen];
576+
int saltIn = inputBuffer.ReadRawBuffer(saltBytes, 0, saltLen);
577+
578+
if (saltIn != saltLen)
579+
throw new ZipException("AES Salt expected " + saltLen + " got " + saltIn);
580+
581+
//
582+
byte[] pwdVerifyRead = new byte[ZipConstants.AESPasswordVerifyLength];
583+
int pwdBytesRead = inputBuffer.ReadRawBuffer(pwdVerifyRead, 0, pwdVerifyRead.Length);
584+
585+
if (pwdBytesRead != pwdVerifyRead.Length)
586+
throw new EndOfStreamException();
587+
588+
int blockSize = entry.AESKeySize / 8; // bits to bytes
589+
590+
var decryptor = new ZipAESTransform(password, saltBytes, blockSize, false);
591+
byte[] pwdVerifyCalc = decryptor.PwdVerifier;
592+
if (pwdVerifyCalc[0] != pwdVerifyRead[0] || pwdVerifyCalc[1] != pwdVerifyRead[1])
593+
throw new ZipException("Invalid password for AES");
594+
595+
// The AES data has saltLen+AESPasswordVerifyLength bytes as a header, and AESAuthCodeLength bytes
596+
// as a footer.
597+
csize -= (saltLen + ZipConstants.AESPasswordVerifyLength + ZipConstants.AESAuthCodeLength);
598+
inputBuffer.CryptoTransform = decryptor;
523599
}
524600
}
525601
else

0 commit comments

Comments
 (0)