Skip to content

Add password to ZipArchive #1545

Description

@neridonk

ZIP Archive Encryption API Proposal

Background and Motivation

This feature introduces password support for ZIP archives, enabling encryption and decryption of archive entries. The proposal adds support for industry-standard WinZip AES encryption and support for legacy ZipCrypto method.

  • A single archive can contain entries encrypted with different passwords and/or encryption methods
  • Both synchronous and asynchronous operations are supported
  • Supports industry-standard WinZip AES encryption methods (128-bit, 192-bit, and 256-bit variants) and for compatibility reasons also ZipCrypto
  • Password parameters use ReadOnlySpan<char> (sync) and ReadOnlyMemory<char> (async) to enable safer handling of sensitive data.
  • No defaults provided for Encryption methods, users need to always specify the encryption method they want themselves.
  • All existing helper methods are currently considered, but to avoid API bloat, I believe it is also reasonable that the password/encryption overload is added only to the most parameter-rich overload.

Platform Notes:

  • Since an AES cryptography algorithm is required, WinZip AES encryption is supported only on platforms where AES is available. That means it is not supported on browser platforms.

Archive Structure:

  • A single archive can contain a mix of plain (unencrypted) entries, entries encrypted with different passwords, and entries encrypted with different encryption methods.
  • ExtractToDirectory can only work with archives where all encrypted entries use the same password. Mixed password scenarios require entry-by-entry handling via ZipArchiveEntry.Open(password).

API Proposal

namespaceSystem.IO.Compression;// This enum is intended purely as a user-friendly way to specify the encryption method.publicenumEncryptionMethod{None=0,ZipCrypto=1,// or possibly LegacyZipCrypto to make it clear we don't recommend itAes128=2,Aes192=3,Aes256=4}publicpartialclassZipArchiveEntry{// ExistingpublicStreamOpen();publicStreamOpen(FileAccessaccess);publicTask<Stream>OpenAsync(CancellationTokencancellationToken=default);publicTask<Stream>OpenAsync(FileAccessaccess,CancellationTokencancellationToken=default);// New (decryption)// Opens an encrypted entry in read or update mode.// Throws InvalidDataException if the entry is not encrypted or the password is wrong.// Throws InvalidOperationException in Create mode (no existing entry to decrypt).publicStreamOpen(ReadOnlySpan<char>password);publicStreamOpen(FileAccessaccess,ReadOnlySpan<char>password);publicTask<Stream>OpenAsync(ReadOnlyMemory<char>password,CancellationTokencancellationToken=default);publicTask<Stream>OpenAsync(FileAccessaccess,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default);// New (encryption)// Opens a new entry for writing with encryption.// Only valid in Create mode; throws InvalidOperationException in Read mode and InvalidDataException in Update mode.// Throws ArgmentException if password is empty.// Throws PlatuformNotSupportedException for AES methods on browser platforms.publicStreamOpen(ReadOnlySpan<char>password,ZipEncryptionMethodencryptionMethod);publicStreamOpen(FileAccessaccess,ReadOnlySpan<char>password,ZipEncryptionMethodencryptionMethod);publicTask<Stream>OpenAsync(ReadOnlyMemory<char>password,ZipEncryptionMethodencryptionMethod,CancellationTokencancellationToken=default);publicTask<Stream>OpenAsync(FileAccessaccess,ReadOnlyMemory<char>password,ZipEncryptionMethodencryptionMethod,CancellationTokencancellationToken=default);}publicstaticclassZipFileExtensions{// ExtractToDirectory (file-based, sync) exsitingpublicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,booloverwriteFiles){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles){}// ExtractToDirectory (file-based, sync) new// Decrypts encrypted entries with the given password.// Throws InvalidDataException if any encrypted entry fails decryption.// If password is empty, falls back to passwordless extraction.publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,ReadOnlySpan<char>password){}// ExtractToDirectory (file-based, async) exsitingpublicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,booloverwriteFiles,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,CancellationTokencancellationToken=default){}// ExtractToDirectory (file-based, async) exsitingpublicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}// ExtractToDirectory (stream-based, sync) existingpublicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,booloverwriteFiles){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles){}// ExtractToDirectory (stream-based, sync) newpublicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,ReadOnlySpan<char>password){}// ExtractToDirectory (stream-based, async) existingpublicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,booloverwriteFiles,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,CancellationTokencancellationToken=default){}// ExtractToDirectory (stream-based, async) newpublicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}// CreateEntryFromFile, sync, existingpublicstaticZipArchiveEntryCreateEntryFromFile(ZipArchivedestination,stringsourceFileName,stringentryName){}publicstaticZipArchiveEntryCreateEntryFromFile(ZipArchivedestination,stringsourceFileName,stringentryName,CompressionLevelcompressionLevel){}// CreateEntryFromFile, sync, newpublicstaticZipArchiveEntryCreateEntryFromFile(ZipArchivedestination,stringsourceFileName,stringentryName,ReadOnlySpan<char>password,EncryptionMethodencryption){}publicstaticZipArchiveEntryCreateEntryFromFile(ZipArchivedestination,stringsourceFileName,stringentryName,CompressionLevelcompressionLevel,ReadOnlySpan<char>password,EncryptionMethodencryption){}// CreateEntryFromFile, async, existingpublicstaticTask<ZipArchiveEntry>CreateEntryFromFileAsync(ZipArchivedestination,stringsourceFileName,stringentryName,CancellationTokencancellationToken=default);publicstaticTask<ZipArchiveEntry>CreateEntryFromFileAsync(ZipArchivedestination,stringsourceFileName,stringentryName,CompressionLevelcompressionLevel,CancellationTokencancellationToken=default);// CreateEntryFromFile, async, newpublicstaticTask<ZipArchiveEntry>CreateEntryFromFileAsync(ZipArchivedestination,stringsourceFileName,stringentryName,ReadOnlyMemory<char>password,EncryptionMethodencryption,CancellationTokencancellationToken=default){}publicstaticTask<ZipArchiveEntry>CreateEntryFromFileAsync(ZipArchivedestination,stringsourceFileName,stringentryName,CompressionLevelcompressionLevel,ReadOnlyMemory<char>password,EncryptionMethodencryption,CancellationTokencancellationToken=default){}// ExtractToFile, sync, existingpublicstaticvoidExtractToFile(thisZipArchiveEntrysource,stringdestinationFileName){}publicstaticvoidExtractToFile(thisZipArchiveEntrysource,stringdestinationFileName,booloverwrite){}// ExtractToFile, sync, newpublicstaticvoidExtractToFile(thisZipArchiveEntrysource,stringdestinationFileName,ReadOnlySpan<char>password){}publicstaticvoidExtractToFile(thisZipArchiveEntrysource,stringdestinationFileName,booloverwrite,ReadOnlySpan<char>password){}// ExtractToFile, async, existingpublicstaticTaskExtractToFileAsync(ZipArchiveEntrysource,stringdestinationFileName,CancellationTokencancellationToken=default);publicstaticTaskExtractToFileAsync(ZipArchiveEntrysource,stringdestinationFileName,booloverwrite,CancellationTokencancellationToken=default);// ExtractToFile, async, newpublicstaticTaskExtractToFileAsync(thisZipArchiveEntrysource,stringdestinationFileName,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToFileAsync(thisZipArchiveEntrysource,stringdestinationFileName,booloverwrite,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}// ExtractToDirectory (ZipArchive extension), sync, exisitngpublicstaticvoidExtractToDirectory(thisZipArchivesource,stringdestinationDirectoryName){}publicstaticvoidExtractToDirectory(thisZipArchivesource,stringdestinationDirectoryName,booloverwriteFiles){}// ExtractToDirectory (ZipArchive extension), sync, newpublicstaticvoidExtractToDirectory(thisZipArchivesource,stringdestinationDirectoryName,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(thisZipArchivesource,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlySpan<char>password){}// ExtractToDirectory (ZipArchive extension), async, exisitngpublicstaticTaskExtractToDirectoryAsync(thisZipArchivesource,stringdestinationDirectoryName,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(thisZipArchivesource,stringdestinationDirectoryName,booloverwriteFiles,CancellationTokencancellationToken=default){}// ExtractToDirectory (ZipArchive extension), async, newpublicstaticTaskExtractToDirectoryAsync(thisZipArchivesource,stringdestinationDirectoryName,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(thisZipArchivesource,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}}

Open Questions

  1. Lax Password Handling: When an archive contains mixed plain and encrypted entries, and ExtractToDirectory(password) is called with a single password:

    • Should it succeed, extracting plain entries normally and decrypting encrypted entries with the provided password?
    • Or should it throw an exception because not all entries are encrypted?
  2. Open(password) on Unencrypted Entry: When calling ZipArchiveEntry.Open(password) on an entry that is not encrypted:

    • Currently throws ArgumentException. Should this behavior be more lenient (e.g., just open the entry unencrypted)?
    • Or keep the strict behavior to catch user mistakes?

API Usage

Creating a password-protected archive:

usingvararchive=ZipFile.Open("protected.zip",ZipArchiveMode.Create);ZipFileExtensions.CreateEntryFromFile(archive,"document.txt","document.txt",CompressionLevel.Optimal,"myPassword123",EncryptionMethod.Aes256);

Extracting a password-protected archive:

ZipFile.ExtractToDirectory("protected.zip","output",entryNameEncoding:null,overwriteFiles:true,password:"myPassword123");

Opening individual encrypted entries:

usingvararchive=ZipFile.OpenRead("protected.zip");varentry=archive.GetEntry("document.txt");usingvarstream=entry.Open(FileAccess.Read,"myPassword123");

Async extraction with password:

awaitZipFile.ExtractToDirectoryAsync("protected.zip","output",overwriteFiles:true,password:"myPassword123".AsMemory());

Using char arrays for zero-copy password handling:

char[]password=GetPasswordFromSecureSource();try{// Sync — implicit ReadOnlySpan<char> conversionentry.Open(password);// Async — explicit ReadOnlyMemory<char> conversionawaitentry.OpenAsync(password.AsMemory());}finally{// Clear sensitive data from memoryArray.Clear(password);}

Metadata

Metadata

Labels

api-approvedAPI was approved in API review, it can be implementedarea-System.IO.Compressionin-prThere is an active PR which will close this issue when it is merged

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    , 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
     blocks
    (function() {
    function addCopyButtons() {
    document.querySelectorAll('pre code').forEach(function(codeBlock) {
    if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
    codeBlock.parentElement.setAttribute('data-copy-added', 'true');
    var btn = document.createElement('button');
    btn.textContent = 'Copy';
    btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
    btn.onmouseover = function() { this.style.opacity = '1'; };
    btn.onmouseout = function() { this.style.opacity = '0.7'; };
    btn.onclick = function() {
    navigator.clipboard.writeText(codeBlock.textContent).then(function() {
    btn.textContent = 'Copied!';
    setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
    });
    };
    codeBlock.parentElement.style.position = 'relative';
    codeBlock.parentElement.appendChild(btn);
    });
    }
    addCopyButtons();
    // Re-run on dynamic content
    var observer = new MutationObserver(addCopyButtons);
    observer.observe(document.body, { childList: true, subtree: true });
    })();
    }
    } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
    })();
    (function(){
    try {
    var __m = "github.com";
    var __re = new RegExp('^' + "github\\.com" + '
    Add password to ZipArchive · Issue #1545 · dotnet/runtime · GitHub
    Skip to content

    Add password to ZipArchive #1545

    Description

    @neridonk

    ZIP Archive Encryption API Proposal

    Background and Motivation

    This feature introduces password support for ZIP archives, enabling encryption and decryption of archive entries. The proposal adds support for industry-standard WinZip AES encryption and support for legacy ZipCrypto method.

    • A single archive can contain entries encrypted with different passwords and/or encryption methods
    • Both synchronous and asynchronous operations are supported
    • Supports industry-standard WinZip AES encryption methods (128-bit, 192-bit, and 256-bit variants) and for compatibility reasons also ZipCrypto
    • Password parameters use ReadOnlySpan<char> (sync) and ReadOnlyMemory<char> (async) to enable safer handling of sensitive data.
    • No defaults provided for Encryption methods, users need to always specify the encryption method they want themselves.
    • All existing helper methods are currently considered, but to avoid API bloat, I believe it is also reasonable that the password/encryption overload is added only to the most parameter-rich overload.

    Platform Notes:

    • Since an AES cryptography algorithm is required, WinZip AES encryption is supported only on platforms where AES is available. That means it is not supported on browser platforms.

    Archive Structure:

    • A single archive can contain a mix of plain (unencrypted) entries, entries encrypted with different passwords, and entries encrypted with different encryption methods.
    • ExtractToDirectory can only work with archives where all encrypted entries use the same password. Mixed password scenarios require entry-by-entry handling via ZipArchiveEntry.Open(password).

    API Proposal

    namespaceSystem.IO.Compression;// This enum is intended purely as a user-friendly way to specify the encryption method.publicenumEncryptionMethod{None=0,ZipCrypto=1,// or possibly LegacyZipCrypto to make it clear we don't recommend itAes128=2,Aes192=3,Aes256=4}publicpartialclassZipArchiveEntry{// ExistingpublicStreamOpen();publicStreamOpen(FileAccessaccess);publicTask<Stream>OpenAsync(CancellationTokencancellationToken=default);publicTask<Stream>OpenAsync(FileAccessaccess,CancellationTokencancellationToken=default);// New (decryption)// Opens an encrypted entry in read or update mode.// Throws InvalidDataException if the entry is not encrypted or the password is wrong.// Throws InvalidOperationException in Create mode (no existing entry to decrypt).publicStreamOpen(ReadOnlySpan<char>password);publicStreamOpen(FileAccessaccess,ReadOnlySpan<char>password);publicTask<Stream>OpenAsync(ReadOnlyMemory<char>password,CancellationTokencancellationToken=default);publicTask<Stream>OpenAsync(FileAccessaccess,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default);// New (encryption)// Opens a new entry for writing with encryption.// Only valid in Create mode; throws InvalidOperationException in Read mode and InvalidDataException in Update mode.// Throws ArgmentException if password is empty.// Throws PlatuformNotSupportedException for AES methods on browser platforms.publicStreamOpen(ReadOnlySpan<char>password,ZipEncryptionMethodencryptionMethod);publicStreamOpen(FileAccessaccess,ReadOnlySpan<char>password,ZipEncryptionMethodencryptionMethod);publicTask<Stream>OpenAsync(ReadOnlyMemory<char>password,ZipEncryptionMethodencryptionMethod,CancellationTokencancellationToken=default);publicTask<Stream>OpenAsync(FileAccessaccess,ReadOnlyMemory<char>password,ZipEncryptionMethodencryptionMethod,CancellationTokencancellationToken=default);}publicstaticclassZipFileExtensions{// ExtractToDirectory (file-based, sync) exsitingpublicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,booloverwriteFiles){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles){}// ExtractToDirectory (file-based, sync) new// Decrypts encrypted entries with the given password.// Throws InvalidDataException if any encrypted entry fails decryption.// If password is empty, falls back to passwordless extraction.publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,ReadOnlySpan<char>password){}// ExtractToDirectory (file-based, async) exsitingpublicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,booloverwriteFiles,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,CancellationTokencancellationToken=default){}// ExtractToDirectory (file-based, async) exsitingpublicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}// ExtractToDirectory (stream-based, sync) existingpublicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,booloverwriteFiles){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles){}// ExtractToDirectory (stream-based, sync) newpublicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,ReadOnlySpan<char>password){}// ExtractToDirectory (stream-based, async) existingpublicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,booloverwriteFiles,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,CancellationTokencancellationToken=default){}// ExtractToDirectory (stream-based, async) newpublicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}// CreateEntryFromFile, sync, existingpublicstaticZipArchiveEntryCreateEntryFromFile(ZipArchivedestination,stringsourceFileName,stringentryName){}publicstaticZipArchiveEntryCreateEntryFromFile(ZipArchivedestination,stringsourceFileName,stringentryName,CompressionLevelcompressionLevel){}// CreateEntryFromFile, sync, newpublicstaticZipArchiveEntryCreateEntryFromFile(ZipArchivedestination,stringsourceFileName,stringentryName,ReadOnlySpan<char>password,EncryptionMethodencryption){}publicstaticZipArchiveEntryCreateEntryFromFile(ZipArchivedestination,stringsourceFileName,stringentryName,CompressionLevelcompressionLevel,ReadOnlySpan<char>password,EncryptionMethodencryption){}// CreateEntryFromFile, async, existingpublicstaticTask<ZipArchiveEntry>CreateEntryFromFileAsync(ZipArchivedestination,stringsourceFileName,stringentryName,CancellationTokencancellationToken=default);publicstaticTask<ZipArchiveEntry>CreateEntryFromFileAsync(ZipArchivedestination,stringsourceFileName,stringentryName,CompressionLevelcompressionLevel,CancellationTokencancellationToken=default);// CreateEntryFromFile, async, newpublicstaticTask<ZipArchiveEntry>CreateEntryFromFileAsync(ZipArchivedestination,stringsourceFileName,stringentryName,ReadOnlyMemory<char>password,EncryptionMethodencryption,CancellationTokencancellationToken=default){}publicstaticTask<ZipArchiveEntry>CreateEntryFromFileAsync(ZipArchivedestination,stringsourceFileName,stringentryName,CompressionLevelcompressionLevel,ReadOnlyMemory<char>password,EncryptionMethodencryption,CancellationTokencancellationToken=default){}// ExtractToFile, sync, existingpublicstaticvoidExtractToFile(thisZipArchiveEntrysource,stringdestinationFileName){}publicstaticvoidExtractToFile(thisZipArchiveEntrysource,stringdestinationFileName,booloverwrite){}// ExtractToFile, sync, newpublicstaticvoidExtractToFile(thisZipArchiveEntrysource,stringdestinationFileName,ReadOnlySpan<char>password){}publicstaticvoidExtractToFile(thisZipArchiveEntrysource,stringdestinationFileName,booloverwrite,ReadOnlySpan<char>password){}// ExtractToFile, async, existingpublicstaticTaskExtractToFileAsync(ZipArchiveEntrysource,stringdestinationFileName,CancellationTokencancellationToken=default);publicstaticTaskExtractToFileAsync(ZipArchiveEntrysource,stringdestinationFileName,booloverwrite,CancellationTokencancellationToken=default);// ExtractToFile, async, newpublicstaticTaskExtractToFileAsync(thisZipArchiveEntrysource,stringdestinationFileName,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToFileAsync(thisZipArchiveEntrysource,stringdestinationFileName,booloverwrite,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}// ExtractToDirectory (ZipArchive extension), sync, exisitngpublicstaticvoidExtractToDirectory(thisZipArchivesource,stringdestinationDirectoryName){}publicstaticvoidExtractToDirectory(thisZipArchivesource,stringdestinationDirectoryName,booloverwriteFiles){}// ExtractToDirectory (ZipArchive extension), sync, newpublicstaticvoidExtractToDirectory(thisZipArchivesource,stringdestinationDirectoryName,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(thisZipArchivesource,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlySpan<char>password){}// ExtractToDirectory (ZipArchive extension), async, exisitngpublicstaticTaskExtractToDirectoryAsync(thisZipArchivesource,stringdestinationDirectoryName,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(thisZipArchivesource,stringdestinationDirectoryName,booloverwriteFiles,CancellationTokencancellationToken=default){}// ExtractToDirectory (ZipArchive extension), async, newpublicstaticTaskExtractToDirectoryAsync(thisZipArchivesource,stringdestinationDirectoryName,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(thisZipArchivesource,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}}

    Open Questions

    1. Lax Password Handling: When an archive contains mixed plain and encrypted entries, and ExtractToDirectory(password) is called with a single password:

      • Should it succeed, extracting plain entries normally and decrypting encrypted entries with the provided password?
      • Or should it throw an exception because not all entries are encrypted?
    2. Open(password) on Unencrypted Entry: When calling ZipArchiveEntry.Open(password) on an entry that is not encrypted:

      • Currently throws ArgumentException. Should this behavior be more lenient (e.g., just open the entry unencrypted)?
      • Or keep the strict behavior to catch user mistakes?

    API Usage

    Creating a password-protected archive:

    usingvararchive=ZipFile.Open("protected.zip",ZipArchiveMode.Create);ZipFileExtensions.CreateEntryFromFile(archive,"document.txt","document.txt",CompressionLevel.Optimal,"myPassword123",EncryptionMethod.Aes256);

    Extracting a password-protected archive:

    ZipFile.ExtractToDirectory("protected.zip","output",entryNameEncoding:null,overwriteFiles:true,password:"myPassword123");

    Opening individual encrypted entries:

    usingvararchive=ZipFile.OpenRead("protected.zip");varentry=archive.GetEntry("document.txt");usingvarstream=entry.Open(FileAccess.Read,"myPassword123");

    Async extraction with password:

    awaitZipFile.ExtractToDirectoryAsync("protected.zip","output",overwriteFiles:true,password:"myPassword123".AsMemory());

    Using char arrays for zero-copy password handling:

    char[]password=GetPasswordFromSecureSource();try{// Sync — implicit ReadOnlySpan<char> conversionentry.Open(password);// Async — explicit ReadOnlyMemory<char> conversionawaitentry.OpenAsync(password.AsMemory());}finally{// Clear sensitive data from memoryArray.Clear(password);}

    Metadata

    Metadata

    Labels

    api-approvedAPI was approved in API review, it can be implementedarea-System.IO.Compressionin-prThere is an active PR which will close this issue when it is merged

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Add password to ZipArchive · Issue #1545 · dotnet/runtime · GitHub
      Skip to content

      Add password to ZipArchive #1545

      Description

      @neridonk

      ZIP Archive Encryption API Proposal

      Background and Motivation

      This feature introduces password support for ZIP archives, enabling encryption and decryption of archive entries. The proposal adds support for industry-standard WinZip AES encryption and support for legacy ZipCrypto method.

      • A single archive can contain entries encrypted with different passwords and/or encryption methods
      • Both synchronous and asynchronous operations are supported
      • Supports industry-standard WinZip AES encryption methods (128-bit, 192-bit, and 256-bit variants) and for compatibility reasons also ZipCrypto
      • Password parameters use ReadOnlySpan<char> (sync) and ReadOnlyMemory<char> (async) to enable safer handling of sensitive data.
      • No defaults provided for Encryption methods, users need to always specify the encryption method they want themselves.
      • All existing helper methods are currently considered, but to avoid API bloat, I believe it is also reasonable that the password/encryption overload is added only to the most parameter-rich overload.

      Platform Notes:

      • Since an AES cryptography algorithm is required, WinZip AES encryption is supported only on platforms where AES is available. That means it is not supported on browser platforms.

      Archive Structure:

      • A single archive can contain a mix of plain (unencrypted) entries, entries encrypted with different passwords, and entries encrypted with different encryption methods.
      • ExtractToDirectory can only work with archives where all encrypted entries use the same password. Mixed password scenarios require entry-by-entry handling via ZipArchiveEntry.Open(password).

      API Proposal

      namespaceSystem.IO.Compression;// This enum is intended purely as a user-friendly way to specify the encryption method.publicenumEncryptionMethod{None=0,ZipCrypto=1,// or possibly LegacyZipCrypto to make it clear we don't recommend itAes128=2,Aes192=3,Aes256=4}publicpartialclassZipArchiveEntry{// ExistingpublicStreamOpen();publicStreamOpen(FileAccessaccess);publicTask<Stream>OpenAsync(CancellationTokencancellationToken=default);publicTask<Stream>OpenAsync(FileAccessaccess,CancellationTokencancellationToken=default);// New (decryption)// Opens an encrypted entry in read or update mode.// Throws InvalidDataException if the entry is not encrypted or the password is wrong.// Throws InvalidOperationException in Create mode (no existing entry to decrypt).publicStreamOpen(ReadOnlySpan<char>password);publicStreamOpen(FileAccessaccess,ReadOnlySpan<char>password);publicTask<Stream>OpenAsync(ReadOnlyMemory<char>password,CancellationTokencancellationToken=default);publicTask<Stream>OpenAsync(FileAccessaccess,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default);// New (encryption)// Opens a new entry for writing with encryption.// Only valid in Create mode; throws InvalidOperationException in Read mode and InvalidDataException in Update mode.// Throws ArgmentException if password is empty.// Throws PlatuformNotSupportedException for AES methods on browser platforms.publicStreamOpen(ReadOnlySpan<char>password,ZipEncryptionMethodencryptionMethod);publicStreamOpen(FileAccessaccess,ReadOnlySpan<char>password,ZipEncryptionMethodencryptionMethod);publicTask<Stream>OpenAsync(ReadOnlyMemory<char>password,ZipEncryptionMethodencryptionMethod,CancellationTokencancellationToken=default);publicTask<Stream>OpenAsync(FileAccessaccess,ReadOnlyMemory<char>password,ZipEncryptionMethodencryptionMethod,CancellationTokencancellationToken=default);}publicstaticclassZipFileExtensions{// ExtractToDirectory (file-based, sync) exsitingpublicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,booloverwriteFiles){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles){}// ExtractToDirectory (file-based, sync) new// Decrypts encrypted entries with the given password.// Throws InvalidDataException if any encrypted entry fails decryption.// If password is empty, falls back to passwordless extraction.publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,ReadOnlySpan<char>password){}// ExtractToDirectory (file-based, async) exsitingpublicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,booloverwriteFiles,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,CancellationTokencancellationToken=default){}// ExtractToDirectory (file-based, async) exsitingpublicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}// ExtractToDirectory (stream-based, sync) existingpublicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,booloverwriteFiles){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles){}// ExtractToDirectory (stream-based, sync) newpublicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,ReadOnlySpan<char>password){}// ExtractToDirectory (stream-based, async) existingpublicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,booloverwriteFiles,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,CancellationTokencancellationToken=default){}// ExtractToDirectory (stream-based, async) newpublicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}// CreateEntryFromFile, sync, existingpublicstaticZipArchiveEntryCreateEntryFromFile(ZipArchivedestination,stringsourceFileName,stringentryName){}publicstaticZipArchiveEntryCreateEntryFromFile(ZipArchivedestination,stringsourceFileName,stringentryName,CompressionLevelcompressionLevel){}// CreateEntryFromFile, sync, newpublicstaticZipArchiveEntryCreateEntryFromFile(ZipArchivedestination,stringsourceFileName,stringentryName,ReadOnlySpan<char>password,EncryptionMethodencryption){}publicstaticZipArchiveEntryCreateEntryFromFile(ZipArchivedestination,stringsourceFileName,stringentryName,CompressionLevelcompressionLevel,ReadOnlySpan<char>password,EncryptionMethodencryption){}// CreateEntryFromFile, async, existingpublicstaticTask<ZipArchiveEntry>CreateEntryFromFileAsync(ZipArchivedestination,stringsourceFileName,stringentryName,CancellationTokencancellationToken=default);publicstaticTask<ZipArchiveEntry>CreateEntryFromFileAsync(ZipArchivedestination,stringsourceFileName,stringentryName,CompressionLevelcompressionLevel,CancellationTokencancellationToken=default);// CreateEntryFromFile, async, newpublicstaticTask<ZipArchiveEntry>CreateEntryFromFileAsync(ZipArchivedestination,stringsourceFileName,stringentryName,ReadOnlyMemory<char>password,EncryptionMethodencryption,CancellationTokencancellationToken=default){}publicstaticTask<ZipArchiveEntry>CreateEntryFromFileAsync(ZipArchivedestination,stringsourceFileName,stringentryName,CompressionLevelcompressionLevel,ReadOnlyMemory<char>password,EncryptionMethodencryption,CancellationTokencancellationToken=default){}// ExtractToFile, sync, existingpublicstaticvoidExtractToFile(thisZipArchiveEntrysource,stringdestinationFileName){}publicstaticvoidExtractToFile(thisZipArchiveEntrysource,stringdestinationFileName,booloverwrite){}// ExtractToFile, sync, newpublicstaticvoidExtractToFile(thisZipArchiveEntrysource,stringdestinationFileName,ReadOnlySpan<char>password){}publicstaticvoidExtractToFile(thisZipArchiveEntrysource,stringdestinationFileName,booloverwrite,ReadOnlySpan<char>password){}// ExtractToFile, async, existingpublicstaticTaskExtractToFileAsync(ZipArchiveEntrysource,stringdestinationFileName,CancellationTokencancellationToken=default);publicstaticTaskExtractToFileAsync(ZipArchiveEntrysource,stringdestinationFileName,booloverwrite,CancellationTokencancellationToken=default);// ExtractToFile, async, newpublicstaticTaskExtractToFileAsync(thisZipArchiveEntrysource,stringdestinationFileName,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToFileAsync(thisZipArchiveEntrysource,stringdestinationFileName,booloverwrite,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}// ExtractToDirectory (ZipArchive extension), sync, exisitngpublicstaticvoidExtractToDirectory(thisZipArchivesource,stringdestinationDirectoryName){}publicstaticvoidExtractToDirectory(thisZipArchivesource,stringdestinationDirectoryName,booloverwriteFiles){}// ExtractToDirectory (ZipArchive extension), sync, newpublicstaticvoidExtractToDirectory(thisZipArchivesource,stringdestinationDirectoryName,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(thisZipArchivesource,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlySpan<char>password){}// ExtractToDirectory (ZipArchive extension), async, exisitngpublicstaticTaskExtractToDirectoryAsync(thisZipArchivesource,stringdestinationDirectoryName,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(thisZipArchivesource,stringdestinationDirectoryName,booloverwriteFiles,CancellationTokencancellationToken=default){}// ExtractToDirectory (ZipArchive extension), async, newpublicstaticTaskExtractToDirectoryAsync(thisZipArchivesource,stringdestinationDirectoryName,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(thisZipArchivesource,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}}

      Open Questions

      1. Lax Password Handling: When an archive contains mixed plain and encrypted entries, and ExtractToDirectory(password) is called with a single password:

        • Should it succeed, extracting plain entries normally and decrypting encrypted entries with the provided password?
        • Or should it throw an exception because not all entries are encrypted?
      2. Open(password) on Unencrypted Entry: When calling ZipArchiveEntry.Open(password) on an entry that is not encrypted:

        • Currently throws ArgumentException. Should this behavior be more lenient (e.g., just open the entry unencrypted)?
        • Or keep the strict behavior to catch user mistakes?

      API Usage

      Creating a password-protected archive:

      usingvararchive=ZipFile.Open("protected.zip",ZipArchiveMode.Create);ZipFileExtensions.CreateEntryFromFile(archive,"document.txt","document.txt",CompressionLevel.Optimal,"myPassword123",EncryptionMethod.Aes256);

      Extracting a password-protected archive:

      ZipFile.ExtractToDirectory("protected.zip","output",entryNameEncoding:null,overwriteFiles:true,password:"myPassword123");

      Opening individual encrypted entries:

      usingvararchive=ZipFile.OpenRead("protected.zip");varentry=archive.GetEntry("document.txt");usingvarstream=entry.Open(FileAccess.Read,"myPassword123");

      Async extraction with password:

      awaitZipFile.ExtractToDirectoryAsync("protected.zip","output",overwriteFiles:true,password:"myPassword123".AsMemory());

      Using char arrays for zero-copy password handling:

      char[]password=GetPasswordFromSecureSource();try{// Sync — implicit ReadOnlySpan<char> conversionentry.Open(password);// Async — explicit ReadOnlyMemory<char> conversionawaitentry.OpenAsync(password.AsMemory());}finally{// Clear sensitive data from memoryArray.Clear(password);}

      Metadata

      Metadata

      Labels

      api-approvedAPI was approved in API review, it can be implementedarea-System.IO.Compressionin-prThere is an active PR which will close this issue when it is merged

      Projects

      No projects

        Milestone

        Relationships

        None yet

        Development

        No branches or pull requests

        Issue actions

        , 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Add password to ZipArchive · Issue #1545 · dotnet/runtime · GitHub
        Skip to content

        Add password to ZipArchive #1545

        Description

        @neridonk

        ZIP Archive Encryption API Proposal

        Background and Motivation

        This feature introduces password support for ZIP archives, enabling encryption and decryption of archive entries. The proposal adds support for industry-standard WinZip AES encryption and support for legacy ZipCrypto method.

        • A single archive can contain entries encrypted with different passwords and/or encryption methods
        • Both synchronous and asynchronous operations are supported
        • Supports industry-standard WinZip AES encryption methods (128-bit, 192-bit, and 256-bit variants) and for compatibility reasons also ZipCrypto
        • Password parameters use ReadOnlySpan<char> (sync) and ReadOnlyMemory<char> (async) to enable safer handling of sensitive data.
        • No defaults provided for Encryption methods, users need to always specify the encryption method they want themselves.
        • All existing helper methods are currently considered, but to avoid API bloat, I believe it is also reasonable that the password/encryption overload is added only to the most parameter-rich overload.

        Platform Notes:

        • Since an AES cryptography algorithm is required, WinZip AES encryption is supported only on platforms where AES is available. That means it is not supported on browser platforms.

        Archive Structure:

        • A single archive can contain a mix of plain (unencrypted) entries, entries encrypted with different passwords, and entries encrypted with different encryption methods.
        • ExtractToDirectory can only work with archives where all encrypted entries use the same password. Mixed password scenarios require entry-by-entry handling via ZipArchiveEntry.Open(password).

        API Proposal

        namespaceSystem.IO.Compression;// This enum is intended purely as a user-friendly way to specify the encryption method.publicenumEncryptionMethod{None=0,ZipCrypto=1,// or possibly LegacyZipCrypto to make it clear we don't recommend itAes128=2,Aes192=3,Aes256=4}publicpartialclassZipArchiveEntry{// ExistingpublicStreamOpen();publicStreamOpen(FileAccessaccess);publicTask<Stream>OpenAsync(CancellationTokencancellationToken=default);publicTask<Stream>OpenAsync(FileAccessaccess,CancellationTokencancellationToken=default);// New (decryption)// Opens an encrypted entry in read or update mode.// Throws InvalidDataException if the entry is not encrypted or the password is wrong.// Throws InvalidOperationException in Create mode (no existing entry to decrypt).publicStreamOpen(ReadOnlySpan<char>password);publicStreamOpen(FileAccessaccess,ReadOnlySpan<char>password);publicTask<Stream>OpenAsync(ReadOnlyMemory<char>password,CancellationTokencancellationToken=default);publicTask<Stream>OpenAsync(FileAccessaccess,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default);// New (encryption)// Opens a new entry for writing with encryption.// Only valid in Create mode; throws InvalidOperationException in Read mode and InvalidDataException in Update mode.// Throws ArgmentException if password is empty.// Throws PlatuformNotSupportedException for AES methods on browser platforms.publicStreamOpen(ReadOnlySpan<char>password,ZipEncryptionMethodencryptionMethod);publicStreamOpen(FileAccessaccess,ReadOnlySpan<char>password,ZipEncryptionMethodencryptionMethod);publicTask<Stream>OpenAsync(ReadOnlyMemory<char>password,ZipEncryptionMethodencryptionMethod,CancellationTokencancellationToken=default);publicTask<Stream>OpenAsync(FileAccessaccess,ReadOnlyMemory<char>password,ZipEncryptionMethodencryptionMethod,CancellationTokencancellationToken=default);}publicstaticclassZipFileExtensions{// ExtractToDirectory (file-based, sync) exsitingpublicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,booloverwriteFiles){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles){}// ExtractToDirectory (file-based, sync) new// Decrypts encrypted entries with the given password.// Throws InvalidDataException if any encrypted entry fails decryption.// If password is empty, falls back to passwordless extraction.publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,ReadOnlySpan<char>password){}// ExtractToDirectory (file-based, async) exsitingpublicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,booloverwriteFiles,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,CancellationTokencancellationToken=default){}// ExtractToDirectory (file-based, async) exsitingpublicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}// ExtractToDirectory (stream-based, sync) existingpublicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,booloverwriteFiles){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles){}// ExtractToDirectory (stream-based, sync) newpublicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,ReadOnlySpan<char>password){}// ExtractToDirectory (stream-based, async) existingpublicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,booloverwriteFiles,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,CancellationTokencancellationToken=default){}// ExtractToDirectory (stream-based, async) newpublicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}// CreateEntryFromFile, sync, existingpublicstaticZipArchiveEntryCreateEntryFromFile(ZipArchivedestination,stringsourceFileName,stringentryName){}publicstaticZipArchiveEntryCreateEntryFromFile(ZipArchivedestination,stringsourceFileName,stringentryName,CompressionLevelcompressionLevel){}// CreateEntryFromFile, sync, newpublicstaticZipArchiveEntryCreateEntryFromFile(ZipArchivedestination,stringsourceFileName,stringentryName,ReadOnlySpan<char>password,EncryptionMethodencryption){}publicstaticZipArchiveEntryCreateEntryFromFile(ZipArchivedestination,stringsourceFileName,stringentryName,CompressionLevelcompressionLevel,ReadOnlySpan<char>password,EncryptionMethodencryption){}// CreateEntryFromFile, async, existingpublicstaticTask<ZipArchiveEntry>CreateEntryFromFileAsync(ZipArchivedestination,stringsourceFileName,stringentryName,CancellationTokencancellationToken=default);publicstaticTask<ZipArchiveEntry>CreateEntryFromFileAsync(ZipArchivedestination,stringsourceFileName,stringentryName,CompressionLevelcompressionLevel,CancellationTokencancellationToken=default);// CreateEntryFromFile, async, newpublicstaticTask<ZipArchiveEntry>CreateEntryFromFileAsync(ZipArchivedestination,stringsourceFileName,stringentryName,ReadOnlyMemory<char>password,EncryptionMethodencryption,CancellationTokencancellationToken=default){}publicstaticTask<ZipArchiveEntry>CreateEntryFromFileAsync(ZipArchivedestination,stringsourceFileName,stringentryName,CompressionLevelcompressionLevel,ReadOnlyMemory<char>password,EncryptionMethodencryption,CancellationTokencancellationToken=default){}// ExtractToFile, sync, existingpublicstaticvoidExtractToFile(thisZipArchiveEntrysource,stringdestinationFileName){}publicstaticvoidExtractToFile(thisZipArchiveEntrysource,stringdestinationFileName,booloverwrite){}// ExtractToFile, sync, newpublicstaticvoidExtractToFile(thisZipArchiveEntrysource,stringdestinationFileName,ReadOnlySpan<char>password){}publicstaticvoidExtractToFile(thisZipArchiveEntrysource,stringdestinationFileName,booloverwrite,ReadOnlySpan<char>password){}// ExtractToFile, async, existingpublicstaticTaskExtractToFileAsync(ZipArchiveEntrysource,stringdestinationFileName,CancellationTokencancellationToken=default);publicstaticTaskExtractToFileAsync(ZipArchiveEntrysource,stringdestinationFileName,booloverwrite,CancellationTokencancellationToken=default);// ExtractToFile, async, newpublicstaticTaskExtractToFileAsync(thisZipArchiveEntrysource,stringdestinationFileName,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToFileAsync(thisZipArchiveEntrysource,stringdestinationFileName,booloverwrite,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}// ExtractToDirectory (ZipArchive extension), sync, exisitngpublicstaticvoidExtractToDirectory(thisZipArchivesource,stringdestinationDirectoryName){}publicstaticvoidExtractToDirectory(thisZipArchivesource,stringdestinationDirectoryName,booloverwriteFiles){}// ExtractToDirectory (ZipArchive extension), sync, newpublicstaticvoidExtractToDirectory(thisZipArchivesource,stringdestinationDirectoryName,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(thisZipArchivesource,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlySpan<char>password){}// ExtractToDirectory (ZipArchive extension), async, exisitngpublicstaticTaskExtractToDirectoryAsync(thisZipArchivesource,stringdestinationDirectoryName,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(thisZipArchivesource,stringdestinationDirectoryName,booloverwriteFiles,CancellationTokencancellationToken=default){}// ExtractToDirectory (ZipArchive extension), async, newpublicstaticTaskExtractToDirectoryAsync(thisZipArchivesource,stringdestinationDirectoryName,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(thisZipArchivesource,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}}

        Open Questions

        1. Lax Password Handling: When an archive contains mixed plain and encrypted entries, and ExtractToDirectory(password) is called with a single password:

          • Should it succeed, extracting plain entries normally and decrypting encrypted entries with the provided password?
          • Or should it throw an exception because not all entries are encrypted?
        2. Open(password) on Unencrypted Entry: When calling ZipArchiveEntry.Open(password) on an entry that is not encrypted:

          • Currently throws ArgumentException. Should this behavior be more lenient (e.g., just open the entry unencrypted)?
          • Or keep the strict behavior to catch user mistakes?

        API Usage

        Creating a password-protected archive:

        usingvararchive=ZipFile.Open("protected.zip",ZipArchiveMode.Create);ZipFileExtensions.CreateEntryFromFile(archive,"document.txt","document.txt",CompressionLevel.Optimal,"myPassword123",EncryptionMethod.Aes256);

        Extracting a password-protected archive:

        ZipFile.ExtractToDirectory("protected.zip","output",entryNameEncoding:null,overwriteFiles:true,password:"myPassword123");

        Opening individual encrypted entries:

        usingvararchive=ZipFile.OpenRead("protected.zip");varentry=archive.GetEntry("document.txt");usingvarstream=entry.Open(FileAccess.Read,"myPassword123");

        Async extraction with password:

        awaitZipFile.ExtractToDirectoryAsync("protected.zip","output",overwriteFiles:true,password:"myPassword123".AsMemory());

        Using char arrays for zero-copy password handling:

        char[]password=GetPasswordFromSecureSource();try{// Sync — implicit ReadOnlySpan<char> conversionentry.Open(password);// Async — explicit ReadOnlyMemory<char> conversionawaitentry.OpenAsync(password.AsMemory());}finally{// Clear sensitive data from memoryArray.Clear(password);}

        Metadata

        Metadata

        Labels

        api-approvedAPI was approved in API review, it can be implementedarea-System.IO.Compressionin-prThere is an active PR which will close this issue when it is merged

        Projects

        No projects

          Milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Add password to ZipArchive · Issue #1545 · dotnet/runtime · GitHub
          Skip to content

          Add password to ZipArchive #1545

          Description

          @neridonk

          ZIP Archive Encryption API Proposal

          Background and Motivation

          This feature introduces password support for ZIP archives, enabling encryption and decryption of archive entries. The proposal adds support for industry-standard WinZip AES encryption and support for legacy ZipCrypto method.

          • A single archive can contain entries encrypted with different passwords and/or encryption methods
          • Both synchronous and asynchronous operations are supported
          • Supports industry-standard WinZip AES encryption methods (128-bit, 192-bit, and 256-bit variants) and for compatibility reasons also ZipCrypto
          • Password parameters use ReadOnlySpan<char> (sync) and ReadOnlyMemory<char> (async) to enable safer handling of sensitive data.
          • No defaults provided for Encryption methods, users need to always specify the encryption method they want themselves.
          • All existing helper methods are currently considered, but to avoid API bloat, I believe it is also reasonable that the password/encryption overload is added only to the most parameter-rich overload.

          Platform Notes:

          • Since an AES cryptography algorithm is required, WinZip AES encryption is supported only on platforms where AES is available. That means it is not supported on browser platforms.

          Archive Structure:

          • A single archive can contain a mix of plain (unencrypted) entries, entries encrypted with different passwords, and entries encrypted with different encryption methods.
          • ExtractToDirectory can only work with archives where all encrypted entries use the same password. Mixed password scenarios require entry-by-entry handling via ZipArchiveEntry.Open(password).

          API Proposal

          namespaceSystem.IO.Compression;// This enum is intended purely as a user-friendly way to specify the encryption method.publicenumEncryptionMethod{None=0,ZipCrypto=1,// or possibly LegacyZipCrypto to make it clear we don't recommend itAes128=2,Aes192=3,Aes256=4}publicpartialclassZipArchiveEntry{// ExistingpublicStreamOpen();publicStreamOpen(FileAccessaccess);publicTask<Stream>OpenAsync(CancellationTokencancellationToken=default);publicTask<Stream>OpenAsync(FileAccessaccess,CancellationTokencancellationToken=default);// New (decryption)// Opens an encrypted entry in read or update mode.// Throws InvalidDataException if the entry is not encrypted or the password is wrong.// Throws InvalidOperationException in Create mode (no existing entry to decrypt).publicStreamOpen(ReadOnlySpan<char>password);publicStreamOpen(FileAccessaccess,ReadOnlySpan<char>password);publicTask<Stream>OpenAsync(ReadOnlyMemory<char>password,CancellationTokencancellationToken=default);publicTask<Stream>OpenAsync(FileAccessaccess,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default);// New (encryption)// Opens a new entry for writing with encryption.// Only valid in Create mode; throws InvalidOperationException in Read mode and InvalidDataException in Update mode.// Throws ArgmentException if password is empty.// Throws PlatuformNotSupportedException for AES methods on browser platforms.publicStreamOpen(ReadOnlySpan<char>password,ZipEncryptionMethodencryptionMethod);publicStreamOpen(FileAccessaccess,ReadOnlySpan<char>password,ZipEncryptionMethodencryptionMethod);publicTask<Stream>OpenAsync(ReadOnlyMemory<char>password,ZipEncryptionMethodencryptionMethod,CancellationTokencancellationToken=default);publicTask<Stream>OpenAsync(FileAccessaccess,ReadOnlyMemory<char>password,ZipEncryptionMethodencryptionMethod,CancellationTokencancellationToken=default);}publicstaticclassZipFileExtensions{// ExtractToDirectory (file-based, sync) exsitingpublicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,booloverwriteFiles){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles){}// ExtractToDirectory (file-based, sync) new// Decrypts encrypted entries with the given password.// Throws InvalidDataException if any encrypted entry fails decryption.// If password is empty, falls back to passwordless extraction.publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,ReadOnlySpan<char>password){}// ExtractToDirectory (file-based, async) exsitingpublicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,booloverwriteFiles,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,CancellationTokencancellationToken=default){}// ExtractToDirectory (file-based, async) exsitingpublicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}// ExtractToDirectory (stream-based, sync) existingpublicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,booloverwriteFiles){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles){}// ExtractToDirectory (stream-based, sync) newpublicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,ReadOnlySpan<char>password){}// ExtractToDirectory (stream-based, async) existingpublicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,booloverwriteFiles,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,CancellationTokencancellationToken=default){}// ExtractToDirectory (stream-based, async) newpublicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}// CreateEntryFromFile, sync, existingpublicstaticZipArchiveEntryCreateEntryFromFile(ZipArchivedestination,stringsourceFileName,stringentryName){}publicstaticZipArchiveEntryCreateEntryFromFile(ZipArchivedestination,stringsourceFileName,stringentryName,CompressionLevelcompressionLevel){}// CreateEntryFromFile, sync, newpublicstaticZipArchiveEntryCreateEntryFromFile(ZipArchivedestination,stringsourceFileName,stringentryName,ReadOnlySpan<char>password,EncryptionMethodencryption){}publicstaticZipArchiveEntryCreateEntryFromFile(ZipArchivedestination,stringsourceFileName,stringentryName,CompressionLevelcompressionLevel,ReadOnlySpan<char>password,EncryptionMethodencryption){}// CreateEntryFromFile, async, existingpublicstaticTask<ZipArchiveEntry>CreateEntryFromFileAsync(ZipArchivedestination,stringsourceFileName,stringentryName,CancellationTokencancellationToken=default);publicstaticTask<ZipArchiveEntry>CreateEntryFromFileAsync(ZipArchivedestination,stringsourceFileName,stringentryName,CompressionLevelcompressionLevel,CancellationTokencancellationToken=default);// CreateEntryFromFile, async, newpublicstaticTask<ZipArchiveEntry>CreateEntryFromFileAsync(ZipArchivedestination,stringsourceFileName,stringentryName,ReadOnlyMemory<char>password,EncryptionMethodencryption,CancellationTokencancellationToken=default){}publicstaticTask<ZipArchiveEntry>CreateEntryFromFileAsync(ZipArchivedestination,stringsourceFileName,stringentryName,CompressionLevelcompressionLevel,ReadOnlyMemory<char>password,EncryptionMethodencryption,CancellationTokencancellationToken=default){}// ExtractToFile, sync, existingpublicstaticvoidExtractToFile(thisZipArchiveEntrysource,stringdestinationFileName){}publicstaticvoidExtractToFile(thisZipArchiveEntrysource,stringdestinationFileName,booloverwrite){}// ExtractToFile, sync, newpublicstaticvoidExtractToFile(thisZipArchiveEntrysource,stringdestinationFileName,ReadOnlySpan<char>password){}publicstaticvoidExtractToFile(thisZipArchiveEntrysource,stringdestinationFileName,booloverwrite,ReadOnlySpan<char>password){}// ExtractToFile, async, existingpublicstaticTaskExtractToFileAsync(ZipArchiveEntrysource,stringdestinationFileName,CancellationTokencancellationToken=default);publicstaticTaskExtractToFileAsync(ZipArchiveEntrysource,stringdestinationFileName,booloverwrite,CancellationTokencancellationToken=default);// ExtractToFile, async, newpublicstaticTaskExtractToFileAsync(thisZipArchiveEntrysource,stringdestinationFileName,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToFileAsync(thisZipArchiveEntrysource,stringdestinationFileName,booloverwrite,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}// ExtractToDirectory (ZipArchive extension), sync, exisitngpublicstaticvoidExtractToDirectory(thisZipArchivesource,stringdestinationDirectoryName){}publicstaticvoidExtractToDirectory(thisZipArchivesource,stringdestinationDirectoryName,booloverwriteFiles){}// ExtractToDirectory (ZipArchive extension), sync, newpublicstaticvoidExtractToDirectory(thisZipArchivesource,stringdestinationDirectoryName,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(thisZipArchivesource,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlySpan<char>password){}// ExtractToDirectory (ZipArchive extension), async, exisitngpublicstaticTaskExtractToDirectoryAsync(thisZipArchivesource,stringdestinationDirectoryName,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(thisZipArchivesource,stringdestinationDirectoryName,booloverwriteFiles,CancellationTokencancellationToken=default){}// ExtractToDirectory (ZipArchive extension), async, newpublicstaticTaskExtractToDirectoryAsync(thisZipArchivesource,stringdestinationDirectoryName,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(thisZipArchivesource,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}}

          Open Questions

          1. Lax Password Handling: When an archive contains mixed plain and encrypted entries, and ExtractToDirectory(password) is called with a single password:

            • Should it succeed, extracting plain entries normally and decrypting encrypted entries with the provided password?
            • Or should it throw an exception because not all entries are encrypted?
          2. Open(password) on Unencrypted Entry: When calling ZipArchiveEntry.Open(password) on an entry that is not encrypted:

            • Currently throws ArgumentException. Should this behavior be more lenient (e.g., just open the entry unencrypted)?
            • Or keep the strict behavior to catch user mistakes?

          API Usage

          Creating a password-protected archive:

          usingvararchive=ZipFile.Open("protected.zip",ZipArchiveMode.Create);ZipFileExtensions.CreateEntryFromFile(archive,"document.txt","document.txt",CompressionLevel.Optimal,"myPassword123",EncryptionMethod.Aes256);

          Extracting a password-protected archive:

          ZipFile.ExtractToDirectory("protected.zip","output",entryNameEncoding:null,overwriteFiles:true,password:"myPassword123");

          Opening individual encrypted entries:

          usingvararchive=ZipFile.OpenRead("protected.zip");varentry=archive.GetEntry("document.txt");usingvarstream=entry.Open(FileAccess.Read,"myPassword123");

          Async extraction with password:

          awaitZipFile.ExtractToDirectoryAsync("protected.zip","output",overwriteFiles:true,password:"myPassword123".AsMemory());

          Using char arrays for zero-copy password handling:

          char[]password=GetPasswordFromSecureSource();try{// Sync — implicit ReadOnlySpan<char> conversionentry.Open(password);// Async — explicit ReadOnlyMemory<char> conversionawaitentry.OpenAsync(password.AsMemory());}finally{// Clear sensitive data from memoryArray.Clear(password);}

          Metadata

          Metadata

          Labels

          api-approvedAPI was approved in API review, it can be implementedarea-System.IO.Compressionin-prThere is an active PR which will close this issue when it is merged

          Projects

          No projects

            Milestone

            Relationships

            None yet

            Development

            No branches or pull requests

            Issue actions

            , 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Add password to ZipArchive · Issue #1545 · dotnet/runtime · GitHub
            Skip to content

            Add password to ZipArchive #1545

            Description

            @neridonk

            ZIP Archive Encryption API Proposal

            Background and Motivation

            This feature introduces password support for ZIP archives, enabling encryption and decryption of archive entries. The proposal adds support for industry-standard WinZip AES encryption and support for legacy ZipCrypto method.

            • A single archive can contain entries encrypted with different passwords and/or encryption methods
            • Both synchronous and asynchronous operations are supported
            • Supports industry-standard WinZip AES encryption methods (128-bit, 192-bit, and 256-bit variants) and for compatibility reasons also ZipCrypto
            • Password parameters use ReadOnlySpan<char> (sync) and ReadOnlyMemory<char> (async) to enable safer handling of sensitive data.
            • No defaults provided for Encryption methods, users need to always specify the encryption method they want themselves.
            • All existing helper methods are currently considered, but to avoid API bloat, I believe it is also reasonable that the password/encryption overload is added only to the most parameter-rich overload.

            Platform Notes:

            • Since an AES cryptography algorithm is required, WinZip AES encryption is supported only on platforms where AES is available. That means it is not supported on browser platforms.

            Archive Structure:

            • A single archive can contain a mix of plain (unencrypted) entries, entries encrypted with different passwords, and entries encrypted with different encryption methods.
            • ExtractToDirectory can only work with archives where all encrypted entries use the same password. Mixed password scenarios require entry-by-entry handling via ZipArchiveEntry.Open(password).

            API Proposal

            namespaceSystem.IO.Compression;// This enum is intended purely as a user-friendly way to specify the encryption method.publicenumEncryptionMethod{None=0,ZipCrypto=1,// or possibly LegacyZipCrypto to make it clear we don't recommend itAes128=2,Aes192=3,Aes256=4}publicpartialclassZipArchiveEntry{// ExistingpublicStreamOpen();publicStreamOpen(FileAccessaccess);publicTask<Stream>OpenAsync(CancellationTokencancellationToken=default);publicTask<Stream>OpenAsync(FileAccessaccess,CancellationTokencancellationToken=default);// New (decryption)// Opens an encrypted entry in read or update mode.// Throws InvalidDataException if the entry is not encrypted or the password is wrong.// Throws InvalidOperationException in Create mode (no existing entry to decrypt).publicStreamOpen(ReadOnlySpan<char>password);publicStreamOpen(FileAccessaccess,ReadOnlySpan<char>password);publicTask<Stream>OpenAsync(ReadOnlyMemory<char>password,CancellationTokencancellationToken=default);publicTask<Stream>OpenAsync(FileAccessaccess,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default);// New (encryption)// Opens a new entry for writing with encryption.// Only valid in Create mode; throws InvalidOperationException in Read mode and InvalidDataException in Update mode.// Throws ArgmentException if password is empty.// Throws PlatuformNotSupportedException for AES methods on browser platforms.publicStreamOpen(ReadOnlySpan<char>password,ZipEncryptionMethodencryptionMethod);publicStreamOpen(FileAccessaccess,ReadOnlySpan<char>password,ZipEncryptionMethodencryptionMethod);publicTask<Stream>OpenAsync(ReadOnlyMemory<char>password,ZipEncryptionMethodencryptionMethod,CancellationTokencancellationToken=default);publicTask<Stream>OpenAsync(FileAccessaccess,ReadOnlyMemory<char>password,ZipEncryptionMethodencryptionMethod,CancellationTokencancellationToken=default);}publicstaticclassZipFileExtensions{// ExtractToDirectory (file-based, sync) exsitingpublicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,booloverwriteFiles){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles){}// ExtractToDirectory (file-based, sync) new// Decrypts encrypted entries with the given password.// Throws InvalidDataException if any encrypted entry fails decryption.// If password is empty, falls back to passwordless extraction.publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,ReadOnlySpan<char>password){}// ExtractToDirectory (file-based, async) exsitingpublicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,booloverwriteFiles,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,CancellationTokencancellationToken=default){}// ExtractToDirectory (file-based, async) exsitingpublicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}// ExtractToDirectory (stream-based, sync) existingpublicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,booloverwriteFiles){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles){}// ExtractToDirectory (stream-based, sync) newpublicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,ReadOnlySpan<char>password){}// ExtractToDirectory (stream-based, async) existingpublicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,booloverwriteFiles,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,CancellationTokencancellationToken=default){}// ExtractToDirectory (stream-based, async) newpublicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}// CreateEntryFromFile, sync, existingpublicstaticZipArchiveEntryCreateEntryFromFile(ZipArchivedestination,stringsourceFileName,stringentryName){}publicstaticZipArchiveEntryCreateEntryFromFile(ZipArchivedestination,stringsourceFileName,stringentryName,CompressionLevelcompressionLevel){}// CreateEntryFromFile, sync, newpublicstaticZipArchiveEntryCreateEntryFromFile(ZipArchivedestination,stringsourceFileName,stringentryName,ReadOnlySpan<char>password,EncryptionMethodencryption){}publicstaticZipArchiveEntryCreateEntryFromFile(ZipArchivedestination,stringsourceFileName,stringentryName,CompressionLevelcompressionLevel,ReadOnlySpan<char>password,EncryptionMethodencryption){}// CreateEntryFromFile, async, existingpublicstaticTask<ZipArchiveEntry>CreateEntryFromFileAsync(ZipArchivedestination,stringsourceFileName,stringentryName,CancellationTokencancellationToken=default);publicstaticTask<ZipArchiveEntry>CreateEntryFromFileAsync(ZipArchivedestination,stringsourceFileName,stringentryName,CompressionLevelcompressionLevel,CancellationTokencancellationToken=default);// CreateEntryFromFile, async, newpublicstaticTask<ZipArchiveEntry>CreateEntryFromFileAsync(ZipArchivedestination,stringsourceFileName,stringentryName,ReadOnlyMemory<char>password,EncryptionMethodencryption,CancellationTokencancellationToken=default){}publicstaticTask<ZipArchiveEntry>CreateEntryFromFileAsync(ZipArchivedestination,stringsourceFileName,stringentryName,CompressionLevelcompressionLevel,ReadOnlyMemory<char>password,EncryptionMethodencryption,CancellationTokencancellationToken=default){}// ExtractToFile, sync, existingpublicstaticvoidExtractToFile(thisZipArchiveEntrysource,stringdestinationFileName){}publicstaticvoidExtractToFile(thisZipArchiveEntrysource,stringdestinationFileName,booloverwrite){}// ExtractToFile, sync, newpublicstaticvoidExtractToFile(thisZipArchiveEntrysource,stringdestinationFileName,ReadOnlySpan<char>password){}publicstaticvoidExtractToFile(thisZipArchiveEntrysource,stringdestinationFileName,booloverwrite,ReadOnlySpan<char>password){}// ExtractToFile, async, existingpublicstaticTaskExtractToFileAsync(ZipArchiveEntrysource,stringdestinationFileName,CancellationTokencancellationToken=default);publicstaticTaskExtractToFileAsync(ZipArchiveEntrysource,stringdestinationFileName,booloverwrite,CancellationTokencancellationToken=default);// ExtractToFile, async, newpublicstaticTaskExtractToFileAsync(thisZipArchiveEntrysource,stringdestinationFileName,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToFileAsync(thisZipArchiveEntrysource,stringdestinationFileName,booloverwrite,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}// ExtractToDirectory (ZipArchive extension), sync, exisitngpublicstaticvoidExtractToDirectory(thisZipArchivesource,stringdestinationDirectoryName){}publicstaticvoidExtractToDirectory(thisZipArchivesource,stringdestinationDirectoryName,booloverwriteFiles){}// ExtractToDirectory (ZipArchive extension), sync, newpublicstaticvoidExtractToDirectory(thisZipArchivesource,stringdestinationDirectoryName,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(thisZipArchivesource,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlySpan<char>password){}// ExtractToDirectory (ZipArchive extension), async, exisitngpublicstaticTaskExtractToDirectoryAsync(thisZipArchivesource,stringdestinationDirectoryName,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(thisZipArchivesource,stringdestinationDirectoryName,booloverwriteFiles,CancellationTokencancellationToken=default){}// ExtractToDirectory (ZipArchive extension), async, newpublicstaticTaskExtractToDirectoryAsync(thisZipArchivesource,stringdestinationDirectoryName,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(thisZipArchivesource,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}}

            Open Questions

            1. Lax Password Handling: When an archive contains mixed plain and encrypted entries, and ExtractToDirectory(password) is called with a single password:

              • Should it succeed, extracting plain entries normally and decrypting encrypted entries with the provided password?
              • Or should it throw an exception because not all entries are encrypted?
            2. Open(password) on Unencrypted Entry: When calling ZipArchiveEntry.Open(password) on an entry that is not encrypted:

              • Currently throws ArgumentException. Should this behavior be more lenient (e.g., just open the entry unencrypted)?
              • Or keep the strict behavior to catch user mistakes?

            API Usage

            Creating a password-protected archive:

            usingvararchive=ZipFile.Open("protected.zip",ZipArchiveMode.Create);ZipFileExtensions.CreateEntryFromFile(archive,"document.txt","document.txt",CompressionLevel.Optimal,"myPassword123",EncryptionMethod.Aes256);

            Extracting a password-protected archive:

            ZipFile.ExtractToDirectory("protected.zip","output",entryNameEncoding:null,overwriteFiles:true,password:"myPassword123");

            Opening individual encrypted entries:

            usingvararchive=ZipFile.OpenRead("protected.zip");varentry=archive.GetEntry("document.txt");usingvarstream=entry.Open(FileAccess.Read,"myPassword123");

            Async extraction with password:

            awaitZipFile.ExtractToDirectoryAsync("protected.zip","output",overwriteFiles:true,password:"myPassword123".AsMemory());

            Using char arrays for zero-copy password handling:

            char[]password=GetPasswordFromSecureSource();try{// Sync — implicit ReadOnlySpan<char> conversionentry.Open(password);// Async — explicit ReadOnlyMemory<char> conversionawaitentry.OpenAsync(password.AsMemory());}finally{// Clear sensitive data from memoryArray.Clear(password);}

            Metadata

            Metadata

            Labels

            api-approvedAPI was approved in API review, it can be implementedarea-System.IO.Compressionin-prThere is an active PR which will close this issue when it is merged

            Projects

            No projects

              Milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Add password to ZipArchive · Issue #1545 · dotnet/runtime · GitHub
              Skip to content

              Add password to ZipArchive #1545

              Description

              @neridonk

              ZIP Archive Encryption API Proposal

              Background and Motivation

              This feature introduces password support for ZIP archives, enabling encryption and decryption of archive entries. The proposal adds support for industry-standard WinZip AES encryption and support for legacy ZipCrypto method.

              • A single archive can contain entries encrypted with different passwords and/or encryption methods
              • Both synchronous and asynchronous operations are supported
              • Supports industry-standard WinZip AES encryption methods (128-bit, 192-bit, and 256-bit variants) and for compatibility reasons also ZipCrypto
              • Password parameters use ReadOnlySpan<char> (sync) and ReadOnlyMemory<char> (async) to enable safer handling of sensitive data.
              • No defaults provided for Encryption methods, users need to always specify the encryption method they want themselves.
              • All existing helper methods are currently considered, but to avoid API bloat, I believe it is also reasonable that the password/encryption overload is added only to the most parameter-rich overload.

              Platform Notes:

              • Since an AES cryptography algorithm is required, WinZip AES encryption is supported only on platforms where AES is available. That means it is not supported on browser platforms.

              Archive Structure:

              • A single archive can contain a mix of plain (unencrypted) entries, entries encrypted with different passwords, and entries encrypted with different encryption methods.
              • ExtractToDirectory can only work with archives where all encrypted entries use the same password. Mixed password scenarios require entry-by-entry handling via ZipArchiveEntry.Open(password).

              API Proposal

              namespaceSystem.IO.Compression;// This enum is intended purely as a user-friendly way to specify the encryption method.publicenumEncryptionMethod{None=0,ZipCrypto=1,// or possibly LegacyZipCrypto to make it clear we don't recommend itAes128=2,Aes192=3,Aes256=4}publicpartialclassZipArchiveEntry{// ExistingpublicStreamOpen();publicStreamOpen(FileAccessaccess);publicTask<Stream>OpenAsync(CancellationTokencancellationToken=default);publicTask<Stream>OpenAsync(FileAccessaccess,CancellationTokencancellationToken=default);// New (decryption)// Opens an encrypted entry in read or update mode.// Throws InvalidDataException if the entry is not encrypted or the password is wrong.// Throws InvalidOperationException in Create mode (no existing entry to decrypt).publicStreamOpen(ReadOnlySpan<char>password);publicStreamOpen(FileAccessaccess,ReadOnlySpan<char>password);publicTask<Stream>OpenAsync(ReadOnlyMemory<char>password,CancellationTokencancellationToken=default);publicTask<Stream>OpenAsync(FileAccessaccess,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default);// New (encryption)// Opens a new entry for writing with encryption.// Only valid in Create mode; throws InvalidOperationException in Read mode and InvalidDataException in Update mode.// Throws ArgmentException if password is empty.// Throws PlatuformNotSupportedException for AES methods on browser platforms.publicStreamOpen(ReadOnlySpan<char>password,ZipEncryptionMethodencryptionMethod);publicStreamOpen(FileAccessaccess,ReadOnlySpan<char>password,ZipEncryptionMethodencryptionMethod);publicTask<Stream>OpenAsync(ReadOnlyMemory<char>password,ZipEncryptionMethodencryptionMethod,CancellationTokencancellationToken=default);publicTask<Stream>OpenAsync(FileAccessaccess,ReadOnlyMemory<char>password,ZipEncryptionMethodencryptionMethod,CancellationTokencancellationToken=default);}publicstaticclassZipFileExtensions{// ExtractToDirectory (file-based, sync) exsitingpublicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,booloverwriteFiles){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles){}// ExtractToDirectory (file-based, sync) new// Decrypts encrypted entries with the given password.// Throws InvalidDataException if any encrypted entry fails decryption.// If password is empty, falls back to passwordless extraction.publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,ReadOnlySpan<char>password){}// ExtractToDirectory (file-based, async) exsitingpublicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,booloverwriteFiles,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,CancellationTokencancellationToken=default){}// ExtractToDirectory (file-based, async) exsitingpublicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}// ExtractToDirectory (stream-based, sync) existingpublicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,booloverwriteFiles){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles){}// ExtractToDirectory (stream-based, sync) newpublicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,ReadOnlySpan<char>password){}// ExtractToDirectory (stream-based, async) existingpublicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,booloverwriteFiles,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,CancellationTokencancellationToken=default){}// ExtractToDirectory (stream-based, async) newpublicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}// CreateEntryFromFile, sync, existingpublicstaticZipArchiveEntryCreateEntryFromFile(ZipArchivedestination,stringsourceFileName,stringentryName){}publicstaticZipArchiveEntryCreateEntryFromFile(ZipArchivedestination,stringsourceFileName,stringentryName,CompressionLevelcompressionLevel){}// CreateEntryFromFile, sync, newpublicstaticZipArchiveEntryCreateEntryFromFile(ZipArchivedestination,stringsourceFileName,stringentryName,ReadOnlySpan<char>password,EncryptionMethodencryption){}publicstaticZipArchiveEntryCreateEntryFromFile(ZipArchivedestination,stringsourceFileName,stringentryName,CompressionLevelcompressionLevel,ReadOnlySpan<char>password,EncryptionMethodencryption){}// CreateEntryFromFile, async, existingpublicstaticTask<ZipArchiveEntry>CreateEntryFromFileAsync(ZipArchivedestination,stringsourceFileName,stringentryName,CancellationTokencancellationToken=default);publicstaticTask<ZipArchiveEntry>CreateEntryFromFileAsync(ZipArchivedestination,stringsourceFileName,stringentryName,CompressionLevelcompressionLevel,CancellationTokencancellationToken=default);// CreateEntryFromFile, async, newpublicstaticTask<ZipArchiveEntry>CreateEntryFromFileAsync(ZipArchivedestination,stringsourceFileName,stringentryName,ReadOnlyMemory<char>password,EncryptionMethodencryption,CancellationTokencancellationToken=default){}publicstaticTask<ZipArchiveEntry>CreateEntryFromFileAsync(ZipArchivedestination,stringsourceFileName,stringentryName,CompressionLevelcompressionLevel,ReadOnlyMemory<char>password,EncryptionMethodencryption,CancellationTokencancellationToken=default){}// ExtractToFile, sync, existingpublicstaticvoidExtractToFile(thisZipArchiveEntrysource,stringdestinationFileName){}publicstaticvoidExtractToFile(thisZipArchiveEntrysource,stringdestinationFileName,booloverwrite){}// ExtractToFile, sync, newpublicstaticvoidExtractToFile(thisZipArchiveEntrysource,stringdestinationFileName,ReadOnlySpan<char>password){}publicstaticvoidExtractToFile(thisZipArchiveEntrysource,stringdestinationFileName,booloverwrite,ReadOnlySpan<char>password){}// ExtractToFile, async, existingpublicstaticTaskExtractToFileAsync(ZipArchiveEntrysource,stringdestinationFileName,CancellationTokencancellationToken=default);publicstaticTaskExtractToFileAsync(ZipArchiveEntrysource,stringdestinationFileName,booloverwrite,CancellationTokencancellationToken=default);// ExtractToFile, async, newpublicstaticTaskExtractToFileAsync(thisZipArchiveEntrysource,stringdestinationFileName,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToFileAsync(thisZipArchiveEntrysource,stringdestinationFileName,booloverwrite,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}// ExtractToDirectory (ZipArchive extension), sync, exisitngpublicstaticvoidExtractToDirectory(thisZipArchivesource,stringdestinationDirectoryName){}publicstaticvoidExtractToDirectory(thisZipArchivesource,stringdestinationDirectoryName,booloverwriteFiles){}// ExtractToDirectory (ZipArchive extension), sync, newpublicstaticvoidExtractToDirectory(thisZipArchivesource,stringdestinationDirectoryName,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(thisZipArchivesource,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlySpan<char>password){}// ExtractToDirectory (ZipArchive extension), async, exisitngpublicstaticTaskExtractToDirectoryAsync(thisZipArchivesource,stringdestinationDirectoryName,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(thisZipArchivesource,stringdestinationDirectoryName,booloverwriteFiles,CancellationTokencancellationToken=default){}// ExtractToDirectory (ZipArchive extension), async, newpublicstaticTaskExtractToDirectoryAsync(thisZipArchivesource,stringdestinationDirectoryName,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(thisZipArchivesource,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}}

              Open Questions

              1. Lax Password Handling: When an archive contains mixed plain and encrypted entries, and ExtractToDirectory(password) is called with a single password:

                • Should it succeed, extracting plain entries normally and decrypting encrypted entries with the provided password?
                • Or should it throw an exception because not all entries are encrypted?
              2. Open(password) on Unencrypted Entry: When calling ZipArchiveEntry.Open(password) on an entry that is not encrypted:

                • Currently throws ArgumentException. Should this behavior be more lenient (e.g., just open the entry unencrypted)?
                • Or keep the strict behavior to catch user mistakes?

              API Usage

              Creating a password-protected archive:

              usingvararchive=ZipFile.Open("protected.zip",ZipArchiveMode.Create);ZipFileExtensions.CreateEntryFromFile(archive,"document.txt","document.txt",CompressionLevel.Optimal,"myPassword123",EncryptionMethod.Aes256);

              Extracting a password-protected archive:

              ZipFile.ExtractToDirectory("protected.zip","output",entryNameEncoding:null,overwriteFiles:true,password:"myPassword123");

              Opening individual encrypted entries:

              usingvararchive=ZipFile.OpenRead("protected.zip");varentry=archive.GetEntry("document.txt");usingvarstream=entry.Open(FileAccess.Read,"myPassword123");

              Async extraction with password:

              awaitZipFile.ExtractToDirectoryAsync("protected.zip","output",overwriteFiles:true,password:"myPassword123".AsMemory());

              Using char arrays for zero-copy password handling:

              char[]password=GetPasswordFromSecureSource();try{// Sync — implicit ReadOnlySpan<char> conversionentry.Open(password);// Async — explicit ReadOnlyMemory<char> conversionawaitentry.OpenAsync(password.AsMemory());}finally{// Clear sensitive data from memoryArray.Clear(password);}

              Metadata

              Metadata

              Labels

              api-approvedAPI was approved in API review, it can be implementedarea-System.IO.Compressionin-prThere is an active PR which will close this issue when it is merged

              Projects

              No projects

                Milestone

                Relationships

                None yet

                Development

                No branches or pull requests

                Issue actions

                , 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Add password to ZipArchive · Issue #1545 · dotnet/runtime · GitHub
                Skip to content

                Add password to ZipArchive #1545

                Description

                @neridonk

                ZIP Archive Encryption API Proposal

                Background and Motivation

                This feature introduces password support for ZIP archives, enabling encryption and decryption of archive entries. The proposal adds support for industry-standard WinZip AES encryption and support for legacy ZipCrypto method.

                • A single archive can contain entries encrypted with different passwords and/or encryption methods
                • Both synchronous and asynchronous operations are supported
                • Supports industry-standard WinZip AES encryption methods (128-bit, 192-bit, and 256-bit variants) and for compatibility reasons also ZipCrypto
                • Password parameters use ReadOnlySpan<char> (sync) and ReadOnlyMemory<char> (async) to enable safer handling of sensitive data.
                • No defaults provided for Encryption methods, users need to always specify the encryption method they want themselves.
                • All existing helper methods are currently considered, but to avoid API bloat, I believe it is also reasonable that the password/encryption overload is added only to the most parameter-rich overload.

                Platform Notes:

                • Since an AES cryptography algorithm is required, WinZip AES encryption is supported only on platforms where AES is available. That means it is not supported on browser platforms.

                Archive Structure:

                • A single archive can contain a mix of plain (unencrypted) entries, entries encrypted with different passwords, and entries encrypted with different encryption methods.
                • ExtractToDirectory can only work with archives where all encrypted entries use the same password. Mixed password scenarios require entry-by-entry handling via ZipArchiveEntry.Open(password).

                API Proposal

                namespaceSystem.IO.Compression;// This enum is intended purely as a user-friendly way to specify the encryption method.publicenumEncryptionMethod{None=0,ZipCrypto=1,// or possibly LegacyZipCrypto to make it clear we don't recommend itAes128=2,Aes192=3,Aes256=4}publicpartialclassZipArchiveEntry{// ExistingpublicStreamOpen();publicStreamOpen(FileAccessaccess);publicTask<Stream>OpenAsync(CancellationTokencancellationToken=default);publicTask<Stream>OpenAsync(FileAccessaccess,CancellationTokencancellationToken=default);// New (decryption)// Opens an encrypted entry in read or update mode.// Throws InvalidDataException if the entry is not encrypted or the password is wrong.// Throws InvalidOperationException in Create mode (no existing entry to decrypt).publicStreamOpen(ReadOnlySpan<char>password);publicStreamOpen(FileAccessaccess,ReadOnlySpan<char>password);publicTask<Stream>OpenAsync(ReadOnlyMemory<char>password,CancellationTokencancellationToken=default);publicTask<Stream>OpenAsync(FileAccessaccess,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default);// New (encryption)// Opens a new entry for writing with encryption.// Only valid in Create mode; throws InvalidOperationException in Read mode and InvalidDataException in Update mode.// Throws ArgmentException if password is empty.// Throws PlatuformNotSupportedException for AES methods on browser platforms.publicStreamOpen(ReadOnlySpan<char>password,ZipEncryptionMethodencryptionMethod);publicStreamOpen(FileAccessaccess,ReadOnlySpan<char>password,ZipEncryptionMethodencryptionMethod);publicTask<Stream>OpenAsync(ReadOnlyMemory<char>password,ZipEncryptionMethodencryptionMethod,CancellationTokencancellationToken=default);publicTask<Stream>OpenAsync(FileAccessaccess,ReadOnlyMemory<char>password,ZipEncryptionMethodencryptionMethod,CancellationTokencancellationToken=default);}publicstaticclassZipFileExtensions{// ExtractToDirectory (file-based, sync) exsitingpublicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,booloverwriteFiles){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles){}// ExtractToDirectory (file-based, sync) new// Decrypts encrypted entries with the given password.// Throws InvalidDataException if any encrypted entry fails decryption.// If password is empty, falls back to passwordless extraction.publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,ReadOnlySpan<char>password){}// ExtractToDirectory (file-based, async) exsitingpublicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,booloverwriteFiles,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,CancellationTokencancellationToken=default){}// ExtractToDirectory (file-based, async) exsitingpublicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(stringsourceArchiveFileName,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}// ExtractToDirectory (stream-based, sync) existingpublicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,booloverwriteFiles){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles){}// ExtractToDirectory (stream-based, sync) newpublicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,ReadOnlySpan<char>password){}// ExtractToDirectory (stream-based, async) existingpublicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,booloverwriteFiles,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,CancellationTokencancellationToken=default){}// ExtractToDirectory (stream-based, async) newpublicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(Streamsource,stringdestinationDirectoryName,Encoding?entryNameEncoding,booloverwriteFiles,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}// CreateEntryFromFile, sync, existingpublicstaticZipArchiveEntryCreateEntryFromFile(ZipArchivedestination,stringsourceFileName,stringentryName){}publicstaticZipArchiveEntryCreateEntryFromFile(ZipArchivedestination,stringsourceFileName,stringentryName,CompressionLevelcompressionLevel){}// CreateEntryFromFile, sync, newpublicstaticZipArchiveEntryCreateEntryFromFile(ZipArchivedestination,stringsourceFileName,stringentryName,ReadOnlySpan<char>password,EncryptionMethodencryption){}publicstaticZipArchiveEntryCreateEntryFromFile(ZipArchivedestination,stringsourceFileName,stringentryName,CompressionLevelcompressionLevel,ReadOnlySpan<char>password,EncryptionMethodencryption){}// CreateEntryFromFile, async, existingpublicstaticTask<ZipArchiveEntry>CreateEntryFromFileAsync(ZipArchivedestination,stringsourceFileName,stringentryName,CancellationTokencancellationToken=default);publicstaticTask<ZipArchiveEntry>CreateEntryFromFileAsync(ZipArchivedestination,stringsourceFileName,stringentryName,CompressionLevelcompressionLevel,CancellationTokencancellationToken=default);// CreateEntryFromFile, async, newpublicstaticTask<ZipArchiveEntry>CreateEntryFromFileAsync(ZipArchivedestination,stringsourceFileName,stringentryName,ReadOnlyMemory<char>password,EncryptionMethodencryption,CancellationTokencancellationToken=default){}publicstaticTask<ZipArchiveEntry>CreateEntryFromFileAsync(ZipArchivedestination,stringsourceFileName,stringentryName,CompressionLevelcompressionLevel,ReadOnlyMemory<char>password,EncryptionMethodencryption,CancellationTokencancellationToken=default){}// ExtractToFile, sync, existingpublicstaticvoidExtractToFile(thisZipArchiveEntrysource,stringdestinationFileName){}publicstaticvoidExtractToFile(thisZipArchiveEntrysource,stringdestinationFileName,booloverwrite){}// ExtractToFile, sync, newpublicstaticvoidExtractToFile(thisZipArchiveEntrysource,stringdestinationFileName,ReadOnlySpan<char>password){}publicstaticvoidExtractToFile(thisZipArchiveEntrysource,stringdestinationFileName,booloverwrite,ReadOnlySpan<char>password){}// ExtractToFile, async, existingpublicstaticTaskExtractToFileAsync(ZipArchiveEntrysource,stringdestinationFileName,CancellationTokencancellationToken=default);publicstaticTaskExtractToFileAsync(ZipArchiveEntrysource,stringdestinationFileName,booloverwrite,CancellationTokencancellationToken=default);// ExtractToFile, async, newpublicstaticTaskExtractToFileAsync(thisZipArchiveEntrysource,stringdestinationFileName,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToFileAsync(thisZipArchiveEntrysource,stringdestinationFileName,booloverwrite,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}// ExtractToDirectory (ZipArchive extension), sync, exisitngpublicstaticvoidExtractToDirectory(thisZipArchivesource,stringdestinationDirectoryName){}publicstaticvoidExtractToDirectory(thisZipArchivesource,stringdestinationDirectoryName,booloverwriteFiles){}// ExtractToDirectory (ZipArchive extension), sync, newpublicstaticvoidExtractToDirectory(thisZipArchivesource,stringdestinationDirectoryName,ReadOnlySpan<char>password){}publicstaticvoidExtractToDirectory(thisZipArchivesource,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlySpan<char>password){}// ExtractToDirectory (ZipArchive extension), async, exisitngpublicstaticTaskExtractToDirectoryAsync(thisZipArchivesource,stringdestinationDirectoryName,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(thisZipArchivesource,stringdestinationDirectoryName,booloverwriteFiles,CancellationTokencancellationToken=default){}// ExtractToDirectory (ZipArchive extension), async, newpublicstaticTaskExtractToDirectoryAsync(thisZipArchivesource,stringdestinationDirectoryName,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}publicstaticTaskExtractToDirectoryAsync(thisZipArchivesource,stringdestinationDirectoryName,booloverwriteFiles,ReadOnlyMemory<char>password,CancellationTokencancellationToken=default){}}

                Open Questions

                1. Lax Password Handling: When an archive contains mixed plain and encrypted entries, and ExtractToDirectory(password) is called with a single password:

                  • Should it succeed, extracting plain entries normally and decrypting encrypted entries with the provided password?
                  • Or should it throw an exception because not all entries are encrypted?
                2. Open(password) on Unencrypted Entry: When calling ZipArchiveEntry.Open(password) on an entry that is not encrypted:

                  • Currently throws ArgumentException. Should this behavior be more lenient (e.g., just open the entry unencrypted)?
                  • Or keep the strict behavior to catch user mistakes?

                API Usage

                Creating a password-protected archive:

                usingvararchive=ZipFile.Open("protected.zip",ZipArchiveMode.Create);ZipFileExtensions.CreateEntryFromFile(archive,"document.txt","document.txt",CompressionLevel.Optimal,"myPassword123",EncryptionMethod.Aes256);

                Extracting a password-protected archive:

                ZipFile.ExtractToDirectory("protected.zip","output",entryNameEncoding:null,overwriteFiles:true,password:"myPassword123");

                Opening individual encrypted entries:

                usingvararchive=ZipFile.OpenRead("protected.zip");varentry=archive.GetEntry("document.txt");usingvarstream=entry.Open(FileAccess.Read,"myPassword123");

                Async extraction with password:

                awaitZipFile.ExtractToDirectoryAsync("protected.zip","output",overwriteFiles:true,password:"myPassword123".AsMemory());

                Using char arrays for zero-copy password handling:

                char[]password=GetPasswordFromSecureSource();try{// Sync — implicit ReadOnlySpan<char> conversionentry.Open(password);// Async — explicit ReadOnlyMemory<char> conversionawaitentry.OpenAsync(password.AsMemory());}finally{// Clear sensitive data from memoryArray.Clear(password);}

                Metadata

                Metadata

                Labels

                api-approvedAPI was approved in API review, it can be implementedarea-System.IO.Compressionin-prThere is an active PR which will close this issue when it is merged

                Projects

                No projects

                  Milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions