As some of you might already know, Delphi (at least since XE2) comes with a built-in TZipFile class for reading and writing zip files. It is a rather basic class with only limited functionality, but this might just be enough for quite a couple of day-to-day tasks.

During a recent migration of a project from Delphi 7 to Delphi 10.1 Berlin I was facing the decision to either fix the current zip implementation (based on external DLLs) or switch to the stock implementation using TZipFile. The second option would also have the advantage of getting rid of distributing the DLLs. So I headed for that one.

Well, as always when you think you have found the right solution and start coding – reality steps in. Some of the zip files we had to read were password protected. Unfortunately TZipFile doesn’t support password encryption out of the box.

Getting rid of that external dependency was tempting and changing the non-encrypted use cases to use TZipFile was just breeze. Switching to another library (especially with evaluating the market first) seems like too much effort for so little gain. So I tinkered with the idea to implement the encryption part by myself. Honestly, how hard could it be?

Thankfully in this special scenario we only needed to read those encrypted zip files, so implementing the decryption part somehow would be sufficient. Investigating the available possibilities in TZipFile, I actually found instructions in the docs how to achieve this.

Actually TZipFile offers two kludges for that:

OnCreateDecompressStream for use with a standard event method handler

CreateDecompressStreamCallBack which takes a function reference

Both are class properties and such not tied to some instance of TZipFile. Instead they are valid for all TZipFile instances – similar to a global hook. We have to take this into account when using this properties in our implementation. Personally I would prefer a simple virtual method overriden by a derived class. The current class property approach can turn into a nightmare when it comes to multi-threading. I have no idea why the original developer actually choose to do it this way.

While the example in the docs is straight forward, I was looking for a more general case and went for a derived class hooking CreateDecompressStreamCallBack. The class TEncryptedZipFile is very light weight and merely introduces the Password property, which can also be initialized right with the constructor. The class constructor and destructor are responsible for hooking the CallBack. As that has to be persistent even outside any instantiation it is implemented as a class function.

type TEncryptedZipFile = class(TZipFile) strict private class constructor Create; class destructor Destroy; private FPassword: string; public constructor Create(const APassword: string); class function CreateDecompressStream(const InStream: TStream; const ZipFile: TZipFile; const Item: TZipHeader; IsEncrypted: Boolean): TStream; static; property Password: string read FPassword write FPassword; end; 1 2 3 4 5 6 7 8 9 10 11 12 13 type TEncryptedZipFile = class ( TZipFile ) strict private class constructor Create ; class destructor Destroy ; private FPassword : string ; public constructor Create ( const APassword : string ) ; class function CreateDecompressStream ( const InStream : TStream ; const ZipFile : TZipFile ; const Item : TZipHeader ; IsEncrypted : Boolean ) : TStream ; static ; property Password : string read FPassword write FPassword ; end ;

The implementation resembles that of the example, while the real decryption work is delegated to a separate class TDecryptStream.

class function TEncryptedZipFile.CreateDecompressStream(const InStream: TStream; const ZipFile: TZipFile; const Item: TZipHeader; IsEncrypted: Boolean): TStream; begin if IsEncrypted and (ZipFile is TEncryptedZipFile) then begin result := TDecryptStream.Create(InStream, TEncryptedZipFile(ZipFile).Password, Item); end else begin result := InStream; end; end; 1 2 3 4 5 6 7 8 9 10 class function TEncryptedZipFile . CreateDecompressStream ( const InStream : TStream ; const ZipFile : TZipFile ; const Item : TZipHeader ; IsEncrypted : Boolean ) : TStream ; begin if IsEncrypted and ( ZipFile is TEncryptedZipFile ) then begin result : = TDecryptStream . Create ( InStream , TEncryptedZipFile ( ZipFile ) . Password , Item ) ; end else begin result : = InStream ; end ; end ;

The implementation of TDecryptStream and the fact that it is derived from TBytesStream was heavily guided from one boundary condition specific to the expected use case: The zip files which are going to be decrypted are usually small and thus the whole decryption is done in memory directly inside the constructor.

constructor TDecryptStream.Create(AStream: TStream; const APassword: string; const ZipHeader: TZipHeader); var Buffer: TBytes; size: Cardinal; savePos: Int64; begin FPassword := APassword; if (FPassword = '') then raise EZipNoPassword.Create(SNoPassword); savePos := AStream.Position; try InitKeys; size := ZipHeader.CompressedSize; if not DecryptHeader(AStream, ZipHeader.CRC32, size) then raise EZipInvalidPassword.Create(SInvalidPassword); SetLength(Buffer, size); AStream.ReadBuffer(Buffer, size); DecryptBuffer(Buffer); inherited Create(Buffer); except AStream.Position := savePos; raise; end; end; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 constructor TDecryptStream . Create ( AStream : TStream ; const APassword : string ; const ZipHeader : TZipHeader ) ; var Buffer : TBytes ; size : Cardinal ; savePos : Int64 ; begin FPassword : = APassword ; if ( FPassword = '' ) then raise EZipNoPassword . Create ( SNoPassword ) ; savePos : = AStream . Position ; try InitKeys ; size : = ZipHeader . CompressedSize ; if not DecryptHeader ( AStream , ZipHeader . CRC32 , size ) then raise EZipInvalidPassword . Create ( SInvalidPassword ) ; SetLength ( Buffer , size ) ; AStream . ReadBuffer ( Buffer , size ) ; DecryptBuffer ( Buffer ) ; inherited Create ( Buffer ) ; except AStream . Position : = savePos ; raise ; end ; end ;

The steps used for decryption are a straight forward implementation of the general steps described in https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT chapter 6.1, so I won’t go into the ugly details here. Here is the complete unit for download: EncryptedZipFileRO

The next post will extend this with a more general approach that also allows writing password encrypted zip files.