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

14 thoughts on “TZipFile with Password Encryption (Part 2)”

    1. To be honest: I didn’t test it, but I see no reason why it should not. At least no less than the standard TZipFile.

  1. Thank you Uwe, i faced the same problem. I tried your code but i can’t extract correctly my zip file, i get the “invalid password” error even if the password is the correct one. The exception is raised in the “InitHeader” procedure, can you help me?

    Thank you

    1. I’m not sure how I can help here. How was the zip file created? Perhaps it uses a different encryption algorithm as the standard one?

  2. Hi, in RO version if I comment out where it validates the header and just return true, it will read my testzip file. In the general version, if I do the same in the InitHeader, and just let it return true, then I eventually get a read error. If we could get this issues sorted out, it would be so nice to be able to use this. Note, that I can create the zip with the general version and it still does not pass the header test.

          1. Actually I have no idea. There is a bit set in the Flags field of the local header that seems to indicate a different handling, but that is not implemented.
            As I am currently pretty busy with another project I am not able to invest any more time here now. I will get back on that when there is a bit more free capacity.

  3. Thank you Uwe, i faced the same problem. I tried your code but i can’t extract correctly my zip file, i get the “invalid password” error even if the password is the correct one. The exception is raised in the “InitHeader” procedure, when I trace the code, I found there is “obviously” error here: In TEncryptStream.InitHeader,
    header[11] := (ZipHeader.CRC32 shr 24);
    The “ZipHeader.CRC32” calculated in System.Zip line 1330, it is later than you call here.

    can you help me?

    Sorry for my poor English. 🙂

    1. There seem to be more settings involved as described in the simple encryption method. If you can provide the test data I will have a look into this when I find some time – probably in a few weeks.

Comments are closed.