encryptionsharpziplib

Extracting AES encrypted zip to MemoryStream


I am developing a process to compress and encrypt a byte array in my desktop application and send it via a WebMethod to my web application, then uncompress/unencrypt it back to a byte array. I am currently attempting to do this with SharpZipLib. The compression of the file seems to be working as expected. I am able to save the file to disk and extract it using 7zip without issue.

The problem I am having is when I receive the byte array on my web server and attempt to extract it.

I use the CompressData method to compress the data on the desktop side.

private byte[] CompressData(byte[] data, string password)
{
    MemoryStream input = new MemoryStream(data);
    MemoryStream ms = new MemoryStream();
    ZipOutputStream os = new ZipOutputStream(ms);
    os.SetLevel(9);
    if (!string.IsNullOrEmpty(password)) os.Password = password;
    ZipEntry entry = new ZipEntry("data")
    {
        DateTime = DateTime.Now
    };
    if (!string.IsNullOrEmpty(password)) entry.AESKeySize = 256;
    os.PutNextEntry(entry);
    StreamUtils.Copy(input, os, new byte[4096]);
    os.CloseEntry();
    os.IsStreamOwner = false;
    os.Close();
    ms.Position = 0;
    return ms.ToArray();
}

I am using the following code to extract the data on the server end (taken almost verbatim from the SharpZipLib examples):

    private byte[] DoRebuildData(byte[] data, string password)
    {
        MemoryStream inStream = new MemoryStream(data);

        MemoryStream outputMemStream = new MemoryStream();
        ZipOutputStream zipOut = new ZipOutputStream(outputMemStream)
        {
            IsStreamOwner = false  // False stops the Close also Closing the underlying stream.
        };

        zipOut.SetLevel(3);
        zipOut.Password = password;        // optional

        RecursiveExtractRebuild(inStream, zipOut);
        inStream.Close();

        // Must finish the ZipOutputStream to finalise output before using outputMemStream.
        zipOut.Close();

        outputMemStream.Position = 0;
        return outputMemStream.ToArray();
    }

    // Calls itself recursively if embedded zip
    //
    private void RecursiveExtractRebuild(Stream str, ZipOutputStream os)
    {

        ZipFile zipFile = new ZipFile(str)
        {
            IsStreamOwner = false
        };

        foreach (ZipEntry zipEntry in zipFile)
        {
            if (!zipEntry.IsFile)
                continue;
            String entryFileName = zipEntry.Name; // or Path.GetFileName(zipEntry.Name) to omit folder
                                                  // Specify any other filtering here.

            Stream zipStream = zipFile.GetInputStream(zipEntry);
            // Zips-within-zips are extracted. If you don't want this and wish to keep embedded zips as-is, just delete these 3 lines. 
            if (entryFileName.EndsWith(".zip", StringComparison.OrdinalIgnoreCase))
            {
                RecursiveExtractRebuild(zipStream, os);
            }
            else
            {
                ZipEntry newEntry = new ZipEntry(entryFileName);
                newEntry.DateTime = zipEntry.DateTime;
                newEntry.Size = zipEntry.Size;
                // Setting the Size will allow the zip to be unpacked by XP's built-in extractor and other older code.

                os.PutNextEntry(newEntry);

                StreamUtils.Copy(zipStream, os, new byte[4096]);
                os.CloseEntry();
            }
        }
    }

The expected result is to get back my original byte array on the server.

On the server, when it comes to the line:

Stream zipStream = zipFile.GetInputStream(zipEntry);

I receive the error 'No password available for AES encrypted stream.'

The only place I see to set a password is in the ZipOutputStream object, and I have checked at runtime, and this is set appropriately.


Solution

  • When unpacking, the password must be assigned to the password-property of the ZipFile-instance, i.e. it must be set in the RecursiveExtractRebuild-method (for this the password has to be added as an additional parameter):

    zipFile.Password = password;
    

    as shown in this example.

    It should be noted that the current DoRebuildData-method doesn't actually unpack the data, but re-packs it into a new zip. The (optional) line in the DoRebuildData-method:

    zipOut.Password = password;
    

    does not specify the password for the unpacking (i.e. for the old zip), but defines the password for the new zip.