How to create and read encrypted zip files with C#

If you ever find the need to create a password-protected zip file using C#, you’ll quickly discover that the options are much more limited than you might expect. The built-in System.IO.Compression .NET types (ZipArchive and ZipFile classes) unfortunately do not support any kind of encryption mechanism. Therefore, if you need to produce a zip archive that is protected by a password (something that is a common requirement when handling sensitive files such as exports or backups), you will need to reach for a third-party library.

In my recent experience, I’ve found that SharpZipLib is the most mature and widely-used option in the .NET ecosystem for this use case. It supports both standard zip encryption and AES-256 (essential for a strong level of security), and it integrates nicely with async/await patterns.

Something else I discovered is that it’s surprisingly difficult to find a complete, working code example for encrypted zip archive creation and decryption. Most examples online seem to either cover only one side of the operation, use outdated synchronous APIs, or miss a critical detail regarding the necessary type to use when reading an encrypted archive. In this post, I aim to fill that gap with a simple example that works and can be easily adapted to suit your needs.

Setup

Before getting into the code, please note the assumed development environment.

The code examples from this article have been tested using .NET 10; however, the approach should be compatible with most recent versions of .NET (the latest version of SharpZipLib requires .NET 6 or greater).

Aside from this, a simple Console App along with the SharpZipLib NuGet package is all that’s needed to get started.

To install the latest version of SharpZipLib, run the following command from the terminal.

dotnet add package SharpZipLib

Alternatively, within Visual Studio, right-click on your project and select ‘Manage NuGet Packages…’, then search for and install SharpZipLib.

With the above in place, you are now ready to follow along with the sections below.

Creating an Encrypted Zip

Let’s start by seeing how we can create an encrypted zip file using SharpZipLib.

For the demonstration, I’ll create simple static methods that we can add to the Program.cs file of a Console App. However, the methods could be added to a helper class or adapted into a service class as an instance method instead.

The method below takes a byte array of data for the zip file contents, a filename for the zip entry, and a password string as input. The encrypted zip file data is returned as a byte array to the caller; this is particularly useful in scenarios where you are working entirely in memory. For example, when creating an email attachment or uploading the file to a cloud storage account.

static async Task<byte[]> CreateEncryptedZip(byte[] fileData, string fileName, string password)
{
    using var memoryStream = new MemoryStream();
 
    using var zipStream = new ZipOutputStream(memoryStream);
    zipStream.Password = password;
    zipStream.SetLevel(9); // Maximum compression level.
 
    var entry = new ZipEntry(fileName)
    {
        DateTime = DateTime.UtcNow,
        Size = fileData.Length,
        AESKeySize = 256
    };
 
    await zipStream.PutNextEntryAsync(entry);
    await zipStream.WriteAsync(fileData, 0, fileData.Length);
    await zipStream.CloseEntryAsync(CancellationToken.None);
    await zipStream.FinishAsync(CancellationToken.None);
 
    return memoryStream.ToArray();
}

There are a few things worth drawing attention to here.

The SetLevel method controls the compression level on a scale of 0 to 9, where 9 represents maximum compression. For most use cases, a value somewhere in the middle (e.g. 6) is a reasonable trade-off between size and CPU cost, but the right value will depend on your specific scenario. For a small amount of data, maximum compression will not affect performance overly much.

Setting AESKeySize = 256 on the ZipEntry is an essential element of the code, as it enables AES-256 encryption. If the AESKeySize property is not set, the older ZipCrypto encryption mode will be used, which is widely known to be highly insecure and should be avoided for anything sensitive. AES-256, on the other hand, provides a very strong level of encryption.

Note also that FinishAsync must be called before reading from the underlying MemoryStream. It flushes the central directory and end-of-central-directory records to the stream, completing the zip structure. Without this call, the resulting byte array will not represent a valid zip file.

Reading an Encrypted Zip

Now, let’s look at how to read an encrypted zip file using SharpZipLib; this is the part where things get a little non-obvious.

If we try to read a SharpZipLib-encrypted zip using the ZipInputStream class (which seems like the natural counterpart to the ZipOutputStream used to create the zip archive), we’ll find that AES-encrypted entries are not supported. ZipInputStream only supports the legacy ZipCrypto encryption format.

To correctly decrypt an AES-encrypted zip archive, we must use the ZipFile class instead.

The method below takes the encrypted zip data and a password string for decryption as input.

static async Task<byte[]> DecryptZip(byte[] encryptedZipData, string password)
{
    using var memoryStream = new MemoryStream(encryptedZipData);
    using var zipFile = new ZipFile(memoryStream);
 
    zipFile.Password = password;
 
    if (zipFile.Count == 0)
    {
        throw new InvalidOperationException("No entries found in the zip file.");
    }
 
    ZipEntry entry = zipFile[0];
 
    using var inputStream = zipFile.GetInputStream(entry);
    using var outputStream = new MemoryStream();
    await inputStream.CopyToAsync(outputStream);
 
    return outputStream.ToArray();
}

The ZipFile class provides random access to the entries within a zip archive, and it is the only type in SharpZipLib that correctly handles AES-encrypted entries.

The GetInputStream method returns a decrypted stream for the specified entry, which can then be read in the usual way.

The zipFile.Count check guards against cases where the archive is empty or the password is incorrect. In these situations, it’s a good idea to surface a clear error as shown in this example, rather than attempting to dereference an entry at index 0.

It’s also worth noting that ZipFile implements IDisposable, so the using statement is important here to ensure that the underlying stream is released cleanly.

Putting It Together

Here is a brief example of calling both methods in sequence to round-trip some data through an encrypted zip archive.

using ICSharpCode.SharpZipLib.Zip;
using System.Text;
 
string plainText = "id,name,amount\n1,Alice,100\n2,Bob,200";
byte[] plainData = Encoding.UTF8.GetBytes(plainText);
string password = "s3cur3P@ssw0rd!";
string fileName = "export.csv";
 
// Create the encrypted zip.
byte[] encryptedZip = await CreateEncryptedZip(plainData, fileName, password);
 
// Decrypt and read the zip back.
byte[] decryptedData = await DecryptZip(encryptedZip, password);
 
string decryptedText = Encoding.UTF8.GetString(decryptedData);
 
Console.WriteLine(decryptedText);

Running the above should print the original CSV content to the console, confirming that the round-trip has worked correctly.

If you want to try out opening the zip file to ensure it is encrypted and check that it can be decrypted successfully, add the following code to the end of your Program.cs file.

await File.WriteAllBytesAsync("export.zip", encryptedZip);

After running the program, if you double-click the created zip file using File Explorer on Windows, you’ll be able to see that the zip archive contains the ‘export.csv’ file. However, if you try to open the CSV file, you’ll receive the following error due to the encryption.

File Explorer - Zip extraction error
File Explorer – Zip extraction error

If, instead, you use a program that supports zip archive encryption and decryption, such as 7-Zip, you’ll be prompted to enter a password when you attempt to extract something from the encrypted zip archive.

7-Zip - Password Prompt
7-Zip – Password Prompt

Provided you enter the correct password, you’ll find that the zip entries are extracted successfully, and you can open any files contained within the zip archive. Otherwise, you’ll receive a ‘Wrong password’ message.

Summary

The System.IO.Compression types built into .NET do not support zip file encryption or decryption, so, in my opinion, SharpZipLib is currently the go-to choice in the .NET ecosystem when you need this capability.

The key things to take away from this post in regard to using SharpZipLib for zip file encryption/decryption are as follows.

  • Use ZipOutputStream with AESKeySize = 256 set on the ZipEntry to produce an AES-256 encrypted zip.
  • Always call FinishAsync before reading from the underlying stream.
  • Use ZipFile when decrypting an AES-encrypted zip archive. ZipInputStream does not support AES-encrypted entries.

Hopefully, this article saves you the time it took me to piece together a clean, working example of how to encrypt a zip archive using a strong encryption algorithm and successfully decrypt it again with C# and SharpZipLib.


💡 Thanks for reading! I love sharing what I learn and always respond to comments and questions.

If this post saved you time or solved a problem, consider supporting me with a coffee — it helps keep the blog running and the content flowing 🚀

Comments