TZipFile with Password Encryption (Part 2)

The first part of this article describes a simple but limited way to read password encrypted zip files with a derived TZipFile class. The limitations are

  1. it can only read but not create encrypted zip files
  2. the size of the zip files is limited by the available memory

While this solution does the job in the scenario it was built for, I cannot say that I was really satisfied with that. So I investigated ways how these limitations can be overcome.

It turned out that it was not that easy as I anticipated.

As we found out in the first part TZipFile exposes some hooks for us to intercept the reading and inject our encryption routine. For unknown reasons (at least to me) the corresponding hooks for writing the zip file are missing.

My first idea was to replace the compression handler with a routine similar to the decompression handler to add these hooks, but the underlying field holding these handlers (FCompressionHandler) is private. At least I could register an alternative pair of compression/decompression handler for the deflate method, even if I had to copy most of the original registration code. Luckily this allowed me to put my decompression hook directly into the code without using the CreateDecompressStreamCallBack – something that had already disturbed my feelings about the first implementation.

The new class constructor turns out to be a bit longer than the previous one where a large part is mostly just copied from the original class constructor.

As some of you may have noticed there is a little hack needed to get the encryption working: the Item parameter is declared as constant, but we need to manipulate the Flag field to indicate encryption. To outsmart persuade the compiler to let us change the Flag field we make use of the fact that a const parameter of a record type is nothing else than a pointer.

After all this was not that difficult as it looked in the first place, so we can now move on to the implementation of the encryption stream. Referring to the original link of the PKWARE APPNOTE.TXT was a bit disappointing. The encryption algorithm was not explained in the same detail as the decryption steps. Seems I had to step back and try to understand the decryption sequence in more detail to deduce the encryption steps from it.

Having a TDecryptStream and TEncryptStream class sort of demanded a common ancestor class. In addition I extracted the plain decryption and encryption steps into a separate TCryptor class (one class  – one purpose). This is the public interface:

The base crypto stream class looks like this:

Both methods Read and Write just raise in EZipInvalidOperation exception. The TEncryptStream and TDecryptStream each override one of these methods leaving the other one raising that exception.

One of the problems I encountered was the fact that the original TZipFile implementation uses Seek during decrytion to rewind the stream to a previous position. This didn’t any harm in the previous part as we used a memory stream there, but here we don’t have this luxury. Just moving the stream position will not work in this case because the decryption algorithm uses a state which is updated with each byte decrypted.

The current implementation uses a brute force approach and re-reads the whole stream from the beginning when a Seek wants to move backwards. This may be optimized for better performance in a future implementation.

I would like to note that things could be implemented much easier when done inside the original TZipFile class. Particularly the Seek operation would be one of the first I would try to eliminate. I might as well redesign the class var hooks approach into a more old fashioned virtual method or standard event approach (just more stress free in multi-thread scenarios). Perhaps someday we will see encryption being part of the stock implementation.

As before, the complete source can be downloaded from here:  EncryptedZipFile

BTW, please notify me about any bugs and issues you may find.

Author: Uwe Raabe

Addicted to Pascal/Delphi since the late 70's