WinZip AE-2 Archive Encryption

WinZip AE-1 and AE-2 are the AES encryption schemes used inside .zip archives, replacing the original, weak PKZIP 2.0 stream cipher. Every function is in sgcCrypto_Zip_AE2 and works on one archive entry at a time; building the surrounding .zip container itself is outside the scope of this unit.

Layout

Key material comes from PBKDF2-HMAC-SHA1 over the password, 1000 iterations, producing an encryption key, an authentication key and a 2-byte password verifier in one call. The salt length and the key length both depend on the AES strength chosen for the entry.


function sgcZipAE2_SaltLength(aStrength: TsgcZipAESStrength): Integer;
function sgcZipAE2_KeyLength(aStrength: TsgcZipAESStrength): Integer;
procedure sgcZipAE2_DeriveKeys(const aPassword, aSalt: TBytes; aStrength: TsgcZipAESStrength;
  out aEncKey, aAuthKey, aVerifier: TBytes);

Encrypting and decrypting an entry


function sgcZipAE2_Encrypt(const aPassword, aSalt, aPlain: TBytes; aStrength: TsgcZipAESStrength;
  out aVerifier, aAuthCode: TBytes): TBytes;
function sgcZipAE2_Decrypt(const aPassword, aSalt, aCipher, aVerifier, aAuthCode: TBytes;
  aStrength: TsgcZipAESStrength; out aPlain: TBytes): Boolean;

var
  oSalt, oCipher, oVerifier, oAuth, oPlain: TBytes;
begin
  oSalt := sgcRandomBytes(sgcZipAE2_SaltLength(zasAES256));
  oCipher := sgcZipAE2_Encrypt(oPassword, oSalt, oPlain, zasAES256, oVerifier, oAuth);
  if sgcZipAE2_Decrypt(oPassword, oSalt, oCipher, oVerifier, oAuth, zasAES256, oPlain) then
    { correct password, untampered data };
end;

The 2-byte verifier catches most wrong passwords immediately, and the 10-byte HMAC-SHA1 authentication code that aAuthCode carries catches the rest, along with any tampering with the encrypted contents.

What AE-2 does not protect

The authentication code covers only the file contents of one entry. It does not cover the file name, the timestamps, or the archive layout, so an attacker who cannot read the contents can still rename, reorder or drop entries in the archive without detection. It also does not protect against a weak password: the key derivation uses only 1000 PBKDF2 rounds, far fewer than Argon2id or scrypt would use, because the format predates those. An AE-2 archive is only as strong as the password chosen for it.

The extra field

A .zip entry marks itself as AE encrypted by setting its compression method to 99 and adding a small extra field (header ID 0x9901) that records the real compression method underneath, the AE version (1 or 2) and the AES strength, which is what a reader needs before it can even attempt a password.


function sgcZipAE2_BuildExtraField(aStrength: TsgcZipAESStrength; aCompressionMethod: Word;
  aVersion: TsgcZipAEVersion = zavAE2): TBytes;
function sgcZipAE2_ParseExtraField(const aData: TBytes; out aStrength: TsgcZipAESStrength;
  out aCompressionMethod: Word; out aVersion: TsgcZipAEVersion): Boolean;

Two convenience functions pack and unpack the salt / verifier / ciphertext / authentication code layout that follows the local file header, for callers assembling or reading a full entry rather than only its cryptography:


function sgcZipAE2_PackEntry(const aSalt, aVerifier, aCipher, aAuthCode: TBytes): TBytes;
function sgcZipAE2_UnpackEntry(const aEntry: TBytes; aStrength: TsgcZipAESStrength;
  out aSalt, aVerifier, aCipher, aAuthCode: TBytes): Boolean;