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
| Feature | Value |
|---|---|
| Full name | ZIP Archive (site name: Zipped File) |
| File extension | .zip |
| MIME type | application/zip |
| Format type | Compressed archive container (binary) |
| Developer | Phil Katz / PKWARE (originally PKZIP) |
| Introduced | 1989 (PKZIP 1.0) |
| Specification | PKWARE APPNOTE.TXT (public); ISO/IEC 21320-1:2015 defines a constrained ZIP profile |
| Open standard | Yes — public-domain format, freely implementable |
| Byte order | Little-endian |
| Compression method | DEFLATE (default); also STORE, BZIP2, LZMA, Zstandard |
| Magic number (hex) | 50 4B 03 04 (local file header, PK\x03\x04) |
| Empty-archive signature | 50 4B 05 06 (end-of-central-directory) |
| Integrity check | CRC-32 per entry |
| Encryption | Legacy ZipCrypto (weak); AES-128/192/256 (WinZip extension) |
| Maximum size | 4 GB per entry / 65,535 entries; ZIP64 raises limits to 16 EiB |
| Archive splitting | Supported (multi-volume: .z01, .z02…) |
| Folder storage | Yes — preserves file and folder hierarchy |
| Unicode file names | Yes (UTF-8 name flag) |
| Central directory | Index 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 URL | pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT |
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:
| Offset | Size | Field | Meaning |
|---|---|---|---|
| 0 | 4 | Signature | 50 4B 03 04 |
| 4 | 2 | Version needed | Minimum extractor version (e.g. 20 = 2.0 for DEFLATE, 45 = 4.5 for Zip64) |
| 6 | 2 | General-purpose bit flag | Encryption, streaming, UTF-8 name (bit 11) |
| 8 | 2 | Compression method | 0 = store, 8 = deflate, others below |
| 10 | 2 | Last mod file time | DOS time, 2-second resolution |
| 12 | 2 | Last mod file date | DOS date, epoch 1980 |
| 14 | 4 | CRC-32 | Checksum of the uncompressed data |
| 18 | 4 | Compressed size | Bytes of data that follow |
| 22 | 4 | Uncompressed size | Original size (0xFFFFFFFF triggers Zip64) |
| 26 | 2 | Filename length n | Length of the name field |
| 28 | 2 | Extra field length m | Length of the extra field |
| 30 | n | Filename | Path, forward slashes, no drive/leading slash |
| 30+n | m | Extra field | Tagged 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
chmodpermissions survive a round trip. - Relative offset of local header (4 bytes): the byte position of this entry’s
50 4B 03 04from 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
- PKWARE — APPNOTE.TXT (.ZIP File Format Specification)
- Microsoft — Zip and unzip files (Windows)
- 7-Zip — official site (free archiver)
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.