Skip to content

Add Zip Archives password support#122093

Open
alinpahontu2912 wants to merge 89 commits intodotnet:mainfrom
alinpahontu2912:zip_password
Open

Add Zip Archives password support#122093
alinpahontu2912 wants to merge 89 commits intodotnet:mainfrom
alinpahontu2912:zip_password

Conversation

@alinpahontu2912
Copy link
Copy Markdown
Member

@alinpahontu2912 alinpahontu2912 commented Dec 2, 2025

Fixes #1545

Big Milestones and status:

  • Read Encrypted Entries (ZipCrypto, WinZip AES)
  • Create Encrypted Entries (ZipCrypto, Winzip AES)
  • Update Mode
  • Add runtime assets for checking compat with WinRar/7zip/WinZip/etc
  • Security check for Cryptography methods
  • Final API design

@rzikm rzikm self-requested a review December 11, 2025 12:22
@alinpahontu2912 alinpahontu2912 marked this pull request as ready for review April 23, 2026 11:06
@alinpahontu2912 alinpahontu2912 changed the title [DRAFT][WIP]Add Zip Archives password support Add Zip Archives password support Apr 23, 2026
Copy link
Copy Markdown
Member

@rzikm rzikm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation has some duplication that I would like to see removed and streamlined, but otherwise looks reasonable.

Comment thread src/libraries/System.IO.Compression/src/System/IO/Compression/WinZipAesStream.cs Outdated
Comment thread src/libraries/System.IO.Compression/src/System/IO/Compression/WinZipAesStream.cs Outdated
Comment thread src/libraries/System.IO.Compression/src/System/IO/Compression/WinZipAesStream.cs Outdated
Comment thread src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.cs Outdated
Copilot AI review requested due to automatic review settings April 24, 2026 13:24
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 41 out of 41 changed files in this pull request and generated 2 comments.

Comment thread src/libraries/System.IO.Compression/src/System/IO/Compression/WinZipAesStream.cs Outdated
Comment thread src/libraries/System.IO.Compression/src/System/IO/Compression/WinZipAesStream.cs Outdated
@alinpahontu2912
Copy link
Copy Markdown
Member Author

Hey @bartonjs can you take a look again ?

Copilot AI review requested due to automatic review settings April 30, 2026 10:39
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 41 out of 41 changed files in this pull request and generated 2 comments.

Copilot AI review requested due to automatic review settings May 7, 2026 10:04
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 41 out of 41 changed files in this pull request and generated 4 comments.

Comment on lines +113 to +114
// Verify the salt matches — use constant-time comparison because the salt is
// derived from secret key material and a timing side-channel could leak information.
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
Copilot AI review requested due to automatic review settings May 7, 2026 13:27
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 42 out of 42 changed files in this pull request and generated 7 comments.

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
Comment on lines +300 to +309
public override int Read(Span<byte> destination)
{
if (_encrypting)
{
throw new NotSupportedException(SR.ReadingNotSupported);
}
int n = _base.Read(destination);
for (int i = 0; i < n; i++)
destination[i] = DecryptAndUpdateKeys(destination[i]);
return n;
Comment on lines +744 to +753
foreach (ZipGenericExtraField field in extraFields)
{
if (field.Tag == HeaderId && field.Size >= DataSize)
{
byte[] data = field.Data;
aesExtraField = new WinZipAesExtraField(
vendorVersion: BinaryPrimitives.ReadUInt16LittleEndian(data.AsSpan(0, 2)),
aesStrength: data[4],
compressionMethod: BinaryPrimitives.ReadUInt16LittleEndian(data.AsSpan(5, 2))
);
Comment on lines +775 to +789
ushort headerId = BinaryPrimitives.ReadUInt16LittleEndian(extraFieldData.Slice(offset, 2));
ushort fieldSize = BinaryPrimitives.ReadUInt16LittleEndian(extraFieldData.Slice(offset + 2, 2));

if (offset + 4 + fieldSize > extraFieldData.Length)
break; // Not enough data for this field

if (headerId == HeaderId && fieldSize >= DataSize)
{
ReadOnlySpan<byte> data = extraFieldData.Slice(offset + 4, fieldSize);
aesExtraField = new WinZipAesExtraField(
vendorVersion: BinaryPrimitives.ReadUInt16LittleEndian(data.Slice(0, 2)),
aesStrength: data[4],
compressionMethod: BinaryPrimitives.ReadUInt16LittleEndian(data.Slice(5, 2))
);
return true;
Comment on lines +142 to +164
if (overwrite && File.Exists(destinationFileName))
{
tempPath = Path.GetTempFileName();
extractPath = tempPath;
}

try
{
FileStream fs = new FileStream(extractPath, fileStreamOptions);
await using (fs.ConfigureAwait(false))
{
Stream es = await source.OpenAsync(password.Span, cancellationToken: cancellationToken).ConfigureAwait(false);
await using (es.ConfigureAwait(false))
{
await es.CopyToAsync(fs, cancellationToken).ConfigureAwait(false);
}
}

// Move the temporary file to the destination only after successful extraction
if (tempPath is not null)
{
File.Move(tempPath, destinationFileName, overwrite: true);
}
{
// WriteAsync requires Memory<byte>, so we must copy to a heap buffer for the async path
byte[] authCodeArray = authCode.Slice(0, 10).ToArray();
await _baseStream.WriteAsync(authCodeArray.AsMemory(0, 10), cancellationToken).ConfigureAwait(false);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems weird that you're doing bounded AsMemory after doing a bounded Slice.ToArray

}
else
{
// For decryption: HMAC first (on ciphertext), then XOR
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this data protocol correctly uses Encrypt-then-MAC (EtM), best practice (and internal security requirements) dictates that you should verify the MAC entirely before doing any decryption. As far as I can tell, this implementation allows decrypted data to be processed by a caller before the MAC is verified.

While that is the more performant approach, it will require a security process exception (ideally before the code is committed). Alternatively, change to verifying the MAC beforehand.

Maybe that discussion was already and and I'm not privy to it, but it's a flaw in implementation that I see here. cc: @blowdart @GrabYourPitchforks

Comment on lines +574 to +575
}
private async Task FinalizeEncryptionAsync(bool isAsync, CancellationToken cancellationToken)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
}
private async Task FinalizeEncryptionAsync(bool isAsync, CancellationToken cancellationToken)
}
private async Task FinalizeEncryptionAsync(bool isAsync, CancellationToken cancellationToken)

// Write Auth Code
WriteAuthCodeCoreAsync(false, CancellationToken.None).GetAwaiter().GetResult();

if (_baseStream.CanWrite)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In what circumstance would CanWrite be false while encrypting and WriteAuthCodeCoreAsync didn't throw?

Comment on lines +654 to +669
// Ensure header is written even for empty files
if (!_headerWritten)
{
await WriteHeaderAsync(CancellationToken.None).ConfigureAwait(false);
}

// Encrypt remaining partial data
await FinalizeEncryptionAsync(true, CancellationToken.None).ConfigureAwait(false);

// Write Auth Code
await WriteAuthCodeCoreAsync(true, CancellationToken.None).ConfigureAwait(false);

if (_baseStream.CanWrite)
{
await _baseStream.FlushAsync().ConfigureAwait(false);
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recommend factoring out the block here and in synchronous dispose to a FinishEncrypting(bool isAsync). 4 steps is on the fence between "we'll keep them in sync" and "we'll fail to". But the fact they both need the comment warning about empty files makes me think it's too complicated to be duplicated.

Comment on lines +685 to +686
}
public override bool CanRead => !_encrypting && !_disposed;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
}
public override bool CanRead => !_encrypting && !_disposed;
}
public override bool CanRead => !_encrypting && !_disposed;

Comment on lines +689 to +690
public override long Length => throw new NotSupportedException();
public override long Position
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public override long Length => throw new NotSupportedException();
public override long Position
public override long Length => throw new NotSupportedException();
public override long Position

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As Copilot has said twice: Remove the BOM.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add password to ZipArchive

7 participants