Search code examples
c#encryptionzipcompressionaes

Zip then encrypt multiple files into a single archive


As per the title I am wanting to take multiple files, zip them up and encrypt them using AES256 in to a single archive in a single pass. Is this possible?

I have managed to achieve a single file in to a single archive using the following code:

internal static void Encrypt(string filePath, string zipPath, byte[] key)
{
    // Create the streams used for encryption.
    using (var outputStream = new FileStream(zipPath, FileMode.Create, FileAccess.Write))
    {
        using (var aes = Aes.Create())
        {
            aes.BlockSize = 128;
            aes.Mode = CipherMode.CBC;
            aes.Padding = PaddingMode.PKCS7;
            aes.KeySize = 256;

            aes.Key = key;
            aes.GenerateIV();

            // Write the IV in to the start of the file first.
            outputStream.Write(aes.IV, 0, aes.IV.Length);

            using (ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV))
            using (CryptoStream csEncrypt = new CryptoStream(outputStream, encryptor, CryptoStreamMode.Write))
            {
                using (DeflateStream zip = new DeflateStream(csEncrypt, CompressionMode.Compress, true))
                {
                    byte[] buffer = new byte[8192];

                    using (var dataStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
                    {
                        int bytesRead = dataStream.Read(buffer, 0, buffer.Length);

                        // Read the data in to a buffer to limit memory usage.
                        while (bytesRead > 0)
                        {
                            zip.Write(buffer, 0, bytesRead);

                            bytesRead = dataStream.Read(buffer, 0, buffer.Length);
                        }
                    }
                }

                //Flush after DeflateStream is disposed.
                csEncrypt.FlushFinalBlock();
            }
        }
    }
}

I have then tried to take this further using ZipArchive to include multiple files using the following:

internal static void EncryptArchive(string filePath, string zipPath, byte[] key)
{
    // Create the streams used for encryption.
    using (var outputStream = new FileStream(zipPath, FileMode.Create, FileAccess.Write))
    {
        using (var aes = Aes.Create())
        {
            aes.BlockSize = 128;
            aes.Mode = CipherMode.CBC;
            aes.Padding = PaddingMode.PKCS7;
            aes.KeySize = 256;

            aes.Key = key;
            aes.GenerateIV();

            // Write the IV in to the start of the file first.
            outputStream.Write(aes.IV, 0, aes.IV.Length);

            using (ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV))
            using (CryptoStream csEncrypt = new CryptoStream(outputStream, encryptor, CryptoStreamMode.Write))
            {
                using (var zip = new ZipArchive(csEncrypt, ZipArchiveMode.Create, false))
                {
                    byte[] buffer = new byte[8192];

                    var entry = zip.CreateEntry(Path.GetFileName(filePath));

                    using (var dataStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
                    using (var entryStream = entry.Open())
                    {
                        int bytesRead = dataStream.Read(buffer, 0, buffer.Length);

                        // Read the data in to a buffer to limit memory usage.
                        while (bytesRead > 0)
                        {
                            entryStream.Write(buffer, 0, bytesRead);

                            bytesRead = dataStream.Read(buffer, 0, buffer.Length);
                        }
                    }
                }

                //Flush after DeflateStream is disposed.
                csEncrypt.FlushFinalBlock();
            }
        }
    }
}

Now although the method is not set up to handle multiple files yet it currently can't even handle 1. As soon as I try to write to entryStream an exception is thrown stating that the stream does not support CanSeek.

I assume ZIPs need to support seeking in order to store whatever positional metadata available for applications like Explorer to display their contents. This requirement does not fit in to our use case so I am happy to workaround it if needed.

I have looked in to other libraries however DotNetZip seems to have some rather bad reviews more recently and not a lot of activity on it. SharpZipLib does support AES 256 but looking at the source code it is built around using ECB which we all know is pretty much useless. This has currently left me believing that my only choice may be to write the multiple files in to the zip along with some metadata that identifies where in the stream they are positioned (I obviously won't be able to Seek to find these but that is not an issue).

To hopefully help clarify what I want to achieve:

I have multiple files (A, B, C), I want to create a zip archive (D) and then encrypt it in the same pass (De). All this should be done without storing D on disk. I am currently using FileStreams as a test, it is most likely that these will be MemoryStreams later on to prevent unencrypted data touching the file system.

Have I missed anything? Is this possible?


Solution

  • To provide an answer I have in fact gone with @JamesKPolks suggestion of writing my own library to do the zipping and encryption of multiple files.

    I am not at liberty to provide the actual code based solution plus it has been built tailored to our specific needs so might not be the most useful to others. However in short the following approach was taken.

    The file is split into 2 main parts:

    1. Plaintext

    IV

    2. Encrypted

    This is then split in to 2 parts:

    2.1. Metadata

    Names of the entries to be stored, padded at the end to aid the reading based on fixed block sizes.

    2.2. Contents

    The data entries themselves marked at the start by a hash of the name entry in 2.1 Metadata. The contents are also written in a fixed block size with padding.

    Summary

    The resulting library has been fairly trivial to write, however it has taken a fair amount of thought upfront. That being said there is still a little bit left to finish off with allowing streaming out of the archive.

    I would hope to have released the code but I am not at liberty at present. Should anyone need assistance with a similar task I would be more than happy to offer help.