ZIP has three encryption systems, told apart by flag bits and the method field. All of them encrypt each member after compressing it, so names and sizes stay visible unless the central directory is encrypted as well.
| System | Flags | Method field | Extra field |
|---|---|---|---|
| Traditional PKWARE | Bit 0 | The real method | None |
| WinZip AES | Bit 0 | 99 | 0x9901, with the real method |
| PKWARE strong encryption | Bits 0 and 6 | The real method | 0x0017, central header only |
The explorer identifies the kind of encryption and shows the parts of the data, but it does not decrypt.
Traditional PKWARE encryption
A stream cipher driven by a password. It is weak by modern standards and should not be used for anything secret. Version needed is 2.0. The 12 encryption header bytes come at the start of the file data and count in the compressed size.
Keys
Three 32-bit keys start at fixed values and are updated with each byte. The password primes them.
key0 = 305419896 // 0x12345678
key1 = 591751049 // 0x23456789
key2 = 878082192 // 0x34567890
for each byte p of the password:
update_keys(p)
update_keys(b):
key0 = crc32(key0, b)
key1 = key1 + (key0 & 0xFF)
key1 = key1 × 134775813 + 1 // keep the low 32 bits
key2 = crc32(key2, key1 >> 24)
crc32(crc, b):
return (crc >> 8) ^ table[(crc ^ b) & 0xFF]
The table is the same as the one for CRC-32, but the initial and final inversions are not used here.
Each encrypted byte is XORed with a keystream byte, and the plaintext is fed back:
decrypt_byte():
t = key2 | 2 // use the low 16 bits
return ((t × (t ^ 1)) >> 8) & 0xFF
decrypt(cipher):
plain = cipher ^ decrypt_byte()
update_keys(plain)
return plain
The 12-byte header
Decrypt the first 12 bytes with decrypt. They are random bytes, but the
last byte (or last two, before PKZIP 2.0) is a check value, to tell a wrong password at
once:
- The check byte is the high byte of the member’s CRC-32.
- When flag bit 3 is set, the CRC-32
is not known when the header is written, so the check byte is the high byte of the
MS-DOS time instead. Python’s
zipfiledoes this.
A wrong password can pass this check by chance, in about 1 case in 256, so a reader must still test the CRC-32 of the output. Decrypting everything after the 12 bytes in the same way gives the compressed data.
WinZip AES (AE-1 and AE-2)
Bit 0 is set, the method field is 99 and the real method is in a 0x9901 extra block, in both headers. Version needed stays what it would be for the same file unencrypted. Each file may have its own password and strength.
WinZip’s documentation names AES-128, AES-192 and AES-256, and says that at the time WinZip did not encrypt with 192-bit keys, although it could decrypt them.
Stored layout
| Part | Size | Notes |
|---|---|---|
| Salt | 8, 12 or 16 bytes | For strength 1, 2 or 3. Must be unique per file. Not encrypted. |
| Password verification value | 2 bytes | Not encrypted. |
| Encrypted data | The length of the compressed data | Encrypted after compression. |
| Authentication code | 10 bytes | Not encrypted. |
The compressed size field covers all four parts. A zero-length file has no encrypted data but keeps the salt, verifier and authentication code, and WinZip recommends encrypting such files. It recommends leaving folder entries unencrypted.
Key derivation
PBKDF2 (RFC 2898) with HMAC-SHA1 and 1000 iterations, over the password and salt, produces one output split in order:
- The AES key: 16, 24 or 32 bytes for strength 1, 2 or 3.
- The authentication key, the same length as the AES key.
- The 2-byte password verification value.
WinZip’s page does not state the length of the authentication key. Implementations such as libzip and minizip-ng use the AES key length.
Cipher and authentication
Data is encrypted with AES in counter mode, byte for byte, so the length does not change. Each 16-byte block is XORed with the AES of a counter. WinZip’s page does not give the counter details; implementations start at 1 and use a little-endian counter.
The authentication code is an HMAC-SHA1 of the encrypted data, using the authentication key, truncated to the first 10 bytes (80 bits). A reader should compute it over the ciphertext before trusting anything it decrypts.
AE-1 and AE-2
| Version | CRC-32 field | Use |
|---|---|---|
AE-1 (0x0001) | The normal CRC-32 of the plaintext. Readers should check it. | WinZip 11 and later use AE-1 for most files. |
AE-2 (0x0002) | Zero. Authentication replaces it. | WinZip 9 and 10 used AE-2 for all files; later versions for files under 20 bytes and for bzip2 files. |
APPNOTE’s Appendix E covers the same system, as “AE-x”.
PKWARE strong encryption
Bits 0 and 6 are set and the method field holds the real method. APPNOTE says this
system and patching are PKWARE’s proprietary technology and require a separate licence.
A 0x0017 extra block is in the central header and a decryption header comes
before the file data, in the stored data. The compressed size includes it.
Decryption header
| Size | Name | Description |
|---|---|---|
| 2 | IVSize | The size of the initialisation vector. If 0, the IV is the CRC-32 followed by the 64-bit uncompressed size, little-endian. |
| IVSize | IVData | |
| 4 | Size | The size of the rest of the header. |
| 2 | Format | 3. |
| 2 | AlgID | The algorithm: see the table below. |
| 2 | Bitlen | The key length, 32 to 448. |
| 2 | Flags | Processing flags: see below. |
| 2 | ErdSize | The size of the encrypted random data. |
| ErdSize | ErdData | Decrypted with AlgID, or with 3DES if flag 0x4000 is set. |
| 4 | Reserved1 | With certificates, the recipient count. |
| varies | Reserved2 | Present only if Reserved1 is non-zero. With certificates, the hash algorithm, hash size and recipient list. |
| 2 | VSize | Includes the 4-byte CRC, and must be a multiple of the block size. |
| VSize − 4 | VData | |
| 4 | VCRC32 | The CRC-32 of VData. |
All block ciphers use CBC mode. SHA-1 is used for key derivation.
Algorithms and flags
| AlgId | Algorithm |
|---|---|
0x6601 | DES |
0x6602 | RC2 (before version 5.2) |
0x6603 | 3DES 168 |
0x6609 | 3DES 112 |
0x660E | AES 128 |
0x660F | AES 192 |
0x6610 | AES 256 |
0x6702 | RC2 (version 5.2 and later) |
0x6720 | Blowfish |
0x6721 | Twofish |
0x6801 | RC4 |
0xFFFF | Unknown |
Flags in the 0x0017 block are 0x0001 password required,
0x0002 certificates only and 0x0003 either; values above
0x0003 are reserved for certificate processing. 0x0100 means
non-OAEP key wrapping (version needed at least 61) and 0x4000 that ErdData
uses 3DES-168.
The 0x0017 block
| Size | Name | Description |
|---|---|---|
| 2 | Format | |
| 2 | AlgId | As above. |
| 2 | Bitlen | |
| 2 | Flags | As above. |
| varies | Certificate data | The rest of the block, when certificates are used. |
Central directory encryption
Hides names and sizes as well as contents. It needs version needed at least 62, and the version 2 ZIP64 record, which is always present in these archives. The central directory may also be compressed.
- Local header masking. Flag bit 13 is set, and the method, time, date,
CRC-32 and both sizes in each local header are set to zero. The name is replaced by a
sequential hexadecimal value starting at 1, with the name length set to its length.
ZIP64 fields holding
0xFFFFor0xFFFFFFFFare not masked. APPNOTE’s text also says that, as of 6.2, method and compressed size are not yet masked. - Archive decryption header. At the offset given by the “start of central directory” field of the ZIP64 record. It has the same layout as the file decryption header.
- Not encrypted: the central directory signature, the ZIP64 record and locator, the end record and the archive comment.
- Data descriptors. Optional, and if present their values are zeros. The per-disk entry count is zero.
- Digital signature. The digital signature record is neither compressed nor encrypted.
Archive extra data record
Present only when certificates are used, immediately before the central directory.
It holds extra blocks 0x0014, 0x0016 and 0x0019.
Its size counts in the central directory size, and it is compressed and encrypted with
the directory.
| Offset | Size | Name | Description |
|---|---|---|---|
0 | 4 | Signature | 50 4B 06 08, the value 0x08064B50. |
4 | 4 | Length | The length of the data that follows. |
8 | length | Data | Extra blocks. |