ZIP File Documentation


Summary

A ZIP archive is a compressed container that bundles one or more files and folders into a single smaller .zip package, using the application/zip MIME type. Phil Katz created it in 1989 and put it in the public domain, so it works everywhere. You rarely need extra software: Windows, macOS, iOS and Android all open a ZIP by double-clicking or tapping “Extract”.

Technical details

FeatureValue
Full nameZIP Archive (site name: Zipped File)
File extension.zip
MIME typeapplication/zip
Format typeCompressed archive container (binary)
DeveloperPhil Katz / PKWARE (originally PKZIP)
Introduced1989 (PKZIP 1.0)
SpecificationPKWARE APPNOTE.TXT (public); ISO/IEC 21320-1:2015 defines a constrained ZIP profile
Open standardYes — public-domain format, freely implementable
Byte orderLittle-endian
Compression methodDEFLATE (default); also STORE, BZIP2, LZMA, Zstandard
Magic number (hex)50 4B 03 04 (local file header, PK\x03\x04)
Empty-archive signature50 4B 05 06 (end-of-central-directory)
Integrity checkCRC-32 per entry
EncryptionLegacy ZipCrypto (weak); AES-128/192/256 (WinZip extension)
Maximum size4 GB per entry / 65,535 entries; ZIP64 raises limits to 16 EiB
Archive splittingSupported (multi-volume: .z01, .z02…)
Folder storageYes — preserves file and folder hierarchy
Unicode file namesYes (UTF-8 name flag)
Central directoryIndex of all entries, stored at the end of the file
Used as a base by.docx, .xlsx, .pptx, .apk, .jar, .epub, .odt
Related extensions.zipx, .7z, .rar, .tar, .gz
Specification URLpkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
File signature (magic bytes)
50 4B 03 04

Offset 0, 4 bytes. In ASCII this reads P K \x03 \x04 — the PK being Phil Katz’s initials. It marks the first local file header. An empty archive instead starts with 50 4B 05 06 (the end-of-central-directory record), and split volumes with 50 4B 07 08. Each entry is stored with DEFLATE compression by default. The authoritative index — the central directory — sits at the end of the file, so a reader seeks backward from the tail. Because of that, a valid ZIP can carry leading bytes before the first PK signature, which is how a self-extracting .exe stub prepends its unpacking code.

What is a ZIP file?

A ZIP file is a compressed archive: a single .zip container that holds any number of files and folders, each compressed independently. Phil Katz wrote the format for his PKZIP program in 1989 and released it into the public domain, which is why it became universal rather than one vendor’s corner of the market. The MIME type is application/zip. The authoritative specification is a plain-text document called APPNOTE.TXT, maintained by PKWARE; a constrained subset was published as the international standard ISO/IEC 21320-1:2015. This article walks the on-disk structure record by record, in the order the bytes actually appear, following the field definitions in the PKWARE APPNOTE.

The important structural idea is that a ZIP is not one compressed blob. It is a run of per-file records at the front, followed by an index (the central directory) and a short pointer record at the very tail. Because the index lives at the end, an extractor reads the file backward: it seeks to the tail, finds the directory, and only then jumps forward to the entries it wants. Every multi-byte integer in the format is stored little-endian, and every record begins with a four-byte signature whose first two bytes are the ASCII letters PK (Phil Katz’s initials).

The overall byte layout

Laid out in order, a two-entry archive looks like this. Each local header is immediately followed by that entry’s compressed data; all of the entries come first, then the whole directory, then one closing record:

50 4B 03 04   local file header    → entry A metadata
..........    compressed data      → entry A bytes
[50 4B 07 08] data descriptor      → optional, only if bit 3 is set
50 4B 03 04   local file header    → entry B metadata
..........    compressed data      → entry B bytes
--- central directory (the index, read first by extractors) ---
50 4B 01 02   central directory header  → entry A: name, offset, attributes
50 4B 01 02   central directory header  → entry B: name, offset, attributes
[50 4B 06 06] Zip64 end of central directory   (only in Zip64 archives)
[50 4B 06 07] Zip64 EOCD locator               (only in Zip64 archives)
50 4B 05 06   end of central directory  → entry count + directory offset

Bytes may legally sit before the first 50 4B 03 04 signature: a self-extracting .exe stores its unpacking program there and appends a normal ZIP, and because the directory offsets are resolved relative to the archive, extraction still works. An empty archive is a lone end-of-central-directory record with zero entries.

The local file header

Every entry starts with a local file header carrying the signature 50 4B 03 04 (PK\x03\x04). The header is 30 fixed bytes, followed by the variable-length filename and extra field, then the compressed data. The fixed fields, with offsets measured from the signature:

OffsetSizeFieldMeaning
04Signature50 4B 03 04
42Version neededMinimum extractor version (e.g. 20 = 2.0 for DEFLATE, 45 = 4.5 for Zip64)
62General-purpose bit flagEncryption, streaming, UTF-8 name (bit 11)
82Compression method0 = store, 8 = deflate, others below
102Last mod file timeDOS time, 2-second resolution
122Last mod file dateDOS date, epoch 1980
144CRC-32Checksum of the uncompressed data
184Compressed sizeBytes of data that follow
224Uncompressed sizeOriginal size (0xFFFFFFFF triggers Zip64)
262Filename length nLength of the name field
282Extra field length mLength of the extra field
30nFilenamePath, forward slashes, no drive/leading slash
30+nmExtra fieldTagged records (Zip64, AES, timestamps)

A few of these fields deserve detail. The version needed field is a gate: an extractor compares it to its own capability and refuses the entry if it is too old, which is how a Zip64 or AES archive cleanly fails in a legacy tool instead of producing garbage.

The general-purpose bit flag is a 16-bit field whose individual bits change how the rest of the entry is read. Bit 0 marks the entry as encrypted. Bits 1–2 encode the DEFLATE compression level. Bit 11 signals that the filename and comment are UTF-8 rather than the historical IBM Code Page 437. The most structurally important is bit 3: when set, it means the CRC-32 and both size fields in this header were not known when the header was written (the encoder was streaming and had not finished reading the input), so those three fields hold zero and the real values appear after the compressed data in a separate record.

The last-modified time and date use the packed DOS format, not Unix time. The date word stores year (from 1980), month and day in bit-fields; the time word stores hours, minutes and seconds/2, which is why ZIP timestamps have only two-second resolution and cannot represent dates before 1980. Higher-resolution or Unix timestamps, when present, live in the extra field. The CRC-32 is computed over the uncompressed bytes using the standard polynomial 0xEDB88320; the extractor recomputes it after decompression and reports corruption on a mismatch. The extra field is a sequence of tagged sub-records, each a 2-byte ID plus a 2-byte length plus data, which is where Zip64 sizes (ID 0x0001), AES parameters (ID 0x9901) and extended timestamps attach without breaking older parsers.

The optional data descriptor

When bit 3 of the general-purpose flag is set, the compressed data is followed by a data descriptor holding the CRC-32, the compressed size and the uncompressed size that the header could not yet know. This record is optionally introduced by the signature 50 4B 07 08 (PK\x07\x08). The signature is technically optional in the original spec, which makes streamed archives awkward to parse forward, because a reader has to scan for the next signature; in practice the trailing values are also copied into the central directory, so a well-behaved extractor that reads the directory first never depends on the descriptor at all. Streaming is the whole reason this record exists: it lets a tool emit compressed bytes to a pipe or socket the moment it produces them, then patch in the checksum and lengths afterward.

The central directory file header

After all the entries comes the central directory: one header per entry, each carrying the signature 50 4B 01 02 (PK\x01\x02). A directory header repeats most of the local header (version needed, flags, method, DOS time/date, CRC-32, both sizes, name and extra) and adds several fields the local header lacks:

  • Version made by (2 bytes): the encoder version plus, in the high byte, the host operating system, which determines how the external attributes are interpreted.
  • File comment and its length: a per-entry text comment, stored only here.
  • Internal file attributes (2 bytes): mainly a text/binary hint in bit 0.
  • External file attributes (4 bytes): host-specific permission bits, for example the DOS attribute byte or, on Unix, the file mode packed into the high 16 bits so that chmod permissions survive a round trip.
  • Relative offset of local header (4 bytes): the byte position of this entry’s 50 4B 03 04 from the start of the archive. This is the pointer that lets an extractor seek straight to any file.
  • Disk number start: which volume of a split archive holds the entry.

The directory sits at the end of the file on purpose. Writing it last let PKZIP stream an archive, or span it across floppy disks, and finalise the index only once every entry was on disk. Reading it first gives an extractor the complete listing (names, sizes, offsets, attributes) without touching the compressed data, so listing a huge archive is instant and extracting one file needs a single seek. The cost is fragility at the tail: a truncated download loses the directory, and without it a standard extractor cannot enumerate the contents even though the entry data may be perfectly intact.

The end of central directory record

The archive closes with the end of central directory (EOCD) record, signature 50 4B 05 06 (PK\x05\x06). It is the anchor an extractor searches for first, scanning backward from the tail because a trailing archive comment of variable length may follow it. Its fields are:

50 4B 05 06   signature (4 bytes)
[2]  number of this disk
[2]  disk where the central directory starts
[2]  central directory entries on this disk
[2]  total central directory entries
[4]  size of the central directory in bytes
[4]  offset of the central directory from the start of the archive
[2]  archive comment length k
[k]  archive comment (arbitrary text)

The two fields that matter most are the total entry count and the central directory offset: together they tell the extractor how many directory headers to read and where they begin. Because the comment length is the last fixed field, a reader locates the EOCD by searching the final 64 KiB or so of the file for the signature and validating that the comment length lines up with the file end.

Zip64 and the 4 GiB limit

The classic records use 32-bit fields for sizes, offsets and counts, which caps each entry and the central-directory offset at 4 GiB (0xFFFFFFFF) and limits an archive to 65,535 entries (0xFFFF). Zip64 lifts those ceilings. When a value would overflow, the classic field is set to all-ones as a sentinel and the true 64-bit value is written in a Zip64 extra-field record (ID 0x0001) or in two new tail records: the Zip64 end of central directory record (50 4B 06 06, PK\x06\x06), which mirrors the EOCD with 8-byte counts and offsets, and the Zip64 EOCD locator (50 4B 06 07, PK\x06\x07), a short record that gives the byte offset of the Zip64 EOCD so a reader can find it. This raises the practical ceiling into the exabytes and lifts the entry count past 4 billion. The version-needed field is set to 45 (4.5), so extractors written before Zip64 support (roughly pre-2001) reject the archive rather than misread the sentinel values.

Compression methods in the method field

The 2-byte compression-method field lets each entry pick its own algorithm, so one archive can mix several. Method 0 (store) copies the bytes verbatim: it is used for data that is already compressed, such as a JPG or MP3 inside the archive, where re-compressing wastes time and can even grow the file. Method 8 (deflate) is the default: the LZ77-plus-Huffman algorithm defined in RFC 1951, the same one used by gzip and PNG. Later additions include BZIP2 (method 12), LZMA (method 14) and Zstandard (method 93); some vendors give archives that lean on these the alternate extension .zipx. Because the method is per-entry and gated by the version-needed field, an archive using a modern method simply fails to open in a tool that does not implement it, rather than corrupting output.

Encryption: ZipCrypto versus AES

ZIP encrypts per entry, not the whole archive, and the filenames in the central directory stay readable even when the data is encrypted. The original ZipCrypto scheme, built into the 1990 specification, prepends a 12-byte encryption header and uses a 96-bit stream cipher keyed from the password and the entry’s CRC. It is broken: a known-plaintext attack recovers the internal keys in seconds given a few known bytes, which archives often leak, so it protects nothing against a capable attacker. AES encryption, defined by WinZip’s extension and supported by 7-Zip and PeaZip, stores its parameters in the extra field under ID 0x9901, sets the compression-method field to 99 (with the real method recorded inside the AES record), and uses AES-128/192/256 with HMAC-SHA1 authentication. The trade-off is compatibility: Windows Explorer’s built-in handler speaks only ZipCrypto, so it prompts for a password on an AES entry and then fails to extract it.

Security: zip-slip and zip-bomb mechanics

The container format is passive data, but two attack classes exploit how extractors handle it, and both are worth understanding at the byte level.

Path traversal (“zip slip”)

An entry’s filename is an arbitrary string in the header, and nothing in the format forbids ../ segments or absolute paths. A malicious archive can store a name like ../../etc/crontab or ..\\..\\Windows\\System32\\evil.dll. An extractor that naively joins the stored name to the output directory writes outside that directory, overwriting system or configuration files. This bug class, nicknamed zip slip, has hit archive libraries across many languages. The fix is to resolve each entry’s target path to an absolute canonical path and reject any entry whose resolved path does not stay inside the intended output root, before opening the output file.

Zip bombs and amplification

A zip bomb weaponises the compression ratio. DEFLATE compresses a long run of a single repeated byte enormously, so a few kilobytes of stored data can declare a multi-gigabyte uncompressed size. The classic 42.zip is 42 KB and expands to roughly 4.5 PB through nested layers of archives-within-archives. Non-recursive bombs go further by overlapping entries: many central-directory headers point their local-header offsets at one shared, highly compressible data stream, so a single physical kernel of compressed bytes is decompressed thousands of times, reaching petabytes from one flat layer. A safe extractor reads the declared uncompressed size and the compression ratio from the headers, caps total output, and refuses entries whose ratio or declared size is implausible, rather than streaming to disk until it runs out of space.

Formats built on the ZIP container

Many everyday file types are ZIP archives with a fixed internal layout, which is why they all begin with the 50 4B 03 04 signature. Office Open XML documents — DOCX and XLSX, standardised as ISO/IEC 29500 — are ZIP containers of XML parts. Android packages (APK) and Java archives (JAR) are ZIPs of compiled code and resources, with the JAR manifest at the fixed path META-INF/MANIFEST.MF. E-books (EPUB) are ZIPs that add one rule of their own: the first entry must be an uncompressed mimetype file stored with method 0, so a reader can identify the book by reading a fixed byte offset without decompressing anything. Rename any of these to .zip and a standard extractor opens it and reveals the parts inside.

References