Learning & reference
File signatures: identify a file by its first bytes
The extension lies. The magic number at the start of a file is what really identifies the format — here is the hex for every format you are likely to test, plus the traps where signatures collide.
Why the extension is not the file type
A file name is just a label a user can change. The bytes at the start of the file - the
magic number or file signature - are what actually identify the format. This is how
file(1), browsers, virus scanners and well-written upload validators decide what they are
looking at.
The upload-validation rule
Never trust the extension or the Content-Type header - both are supplied by the client
and both are trivially faked. Read the first bytes and match a signature, then check the
extension agrees.
Renaming proves it
Rename photo.png to invoice.pdf and upload it. A validator that only looks
at the name accepts it; one that reads 89 50 4E 47 rejects it. That is the single most
common file-upload bug.
Signatures can collide
Every OOXML and OpenDocument file is a ZIP, so DOCX, XLSX, PPTX, ODT, EPUB, JAR and APK all begin
PK 03 04. You must look inside the archive to tell them apart.
File signature table
Offset is where the magic starts. .. means "any byte".
| Format | Extension | Hex signature | ASCII | Offset | Notes |
|---|---|---|---|---|---|
| PNG | .png | 89 50 4E 47 0D 0A 1A 0A | .PNG.... | 0 | The CRLF/EOF bytes are a deliberate trap that detects a corrupted text-mode transfer. |
| JPEG | .jpg .jpeg | FF D8 FF | ÿØÿ | 0 | Ends with FF D9. A truncated JPEG still has a valid header. |
| GIF (87a) | .gif | 47 49 46 38 37 61 | GIF87a | 0 | Original 1987 spec, no animation. |
| GIF (89a) | .gif | 47 49 46 38 39 61 | GIF89a | 0 | Adds animation and transparency. |
.pdf | 25 50 44 46 2D | %PDF- | 0 | Version follows, e.g. %PDF-1.7. Ends with %%EOF. | |
| ZIP | .zip | 50 4B 03 04 | PK.. | 0 | PK 05 06 = empty archive, PK 07 08 = spanned. |
| GZIP | .gz | 1F 8B 08 | .. | 0 | Third byte is the compression method (08 = deflate). |
| BZIP2 | .bz2 | 42 5A 68 | BZh | 0 | Next byte is the block-size digit 1-9. |
| 7-Zip | .7z | 37 7A BC AF 27 1C | 7z... | 0 | |
| RAR | .rar | 52 61 72 21 1A 07 00 | Rar!... | 0 | RAR5 appends 01 00. |
| TAR | .tar | 75 73 74 61 72 | ustar | 257 | Not at byte 0 - the magic sits 257 bytes in, inside the first header block. |
| BMP | .bmp | 42 4D | BM | 0 | |
| TIFF (LE) | .tif .tiff | 49 49 2A 00 | II*. | 0 | Intel byte order. |
| TIFF (BE) | .tif .tiff | 4D 4D 00 2A | MM.* | 0 | Motorola byte order. |
| WebP | .webp | 52 49 46 46 .. .. .. .. 57 45 42 50 | RIFF....WEBP | 0 and 8 | RIFF container - bytes 4-7 are the length. |
| WAV | .wav | 52 49 46 46 .. .. .. .. 57 41 56 45 | RIFF....WAVE | 0 and 8 | Same RIFF container as WebP/AVI. |
| AVI | .avi | 52 49 46 46 .. .. .. .. 41 56 49 20 | RIFF....AVI | 0 and 8 | |
| ICO | .ico | 00 00 01 00 | .... | 0 | Third byte is the type: 1 = icon, 2 = cursor. |
| MP3 (ID3) | .mp3 | 49 44 33 | ID3 | 0 | Tagged files start with ID3; raw frames start FF FB. |
| MP4 / MOV | .mp4 .m4a .mov | 66 74 79 70 | ftyp | 4 | Offset 4 - the first 4 bytes are the box length. |
| OGG | .ogg .opus | 4F 67 67 53 | OggS | 0 | |
| FLAC | .flac | 66 4C 61 43 | fLaC | 0 | |
| Photoshop | .psd | 38 42 50 53 | 8BPS | 0 | |
| SQLite DB | .db .sqlite | 53 51 4C 69 74 65 20 66 6F 72 6D 61 74 20 33 00 | SQLite format 3. | 0 | 16 bytes including the trailing NUL. |
| ELF binary | (none) | 7F 45 4C 46 | .ELF | 0 | Linux/Unix executables and shared objects. |
| Windows EXE/DLL | .exe .dll | 4D 5A | MZ | 0 | The DOS stub; initials of Mark Zbikowski. |
| Java class | .class | CA FE BA BE | .... | 0 | The original "cafe babe". |
| WOFF | .woff | 77 4F 46 46 | wOFF | 0 | Web font, a compressed sfnt wrapper. |
| WOFF2 | .woff2 | 77 4F 46 32 | wOF2 | 0 | Brotli-compressed successor. |
| TrueType font | .ttf | 00 01 00 00 00 | ..... | 0 | |
| OpenType (CFF) | .otf | 4F 54 54 4F | OTTO | 0 | |
| RTF | .rtf | 7B 5C 72 74 66 | {\rtf | 0 | |
| XML | .xml .svg | 3C 3F 78 6D 6C | <?xml | 0 | May be preceded by a UTF-8 BOM. |
The ZIP-family trap
These all share one signature. To identify them you have to open the container and look for a marker entry.
| Format | Signature | How to actually tell |
|---|---|---|
| DOCX / XLSX / PPTX | 50 4B 03 04 | Contains [Content_Types].xml; the part names say which Office app. |
| ODT / ODS / ODP | 50 4B 03 04 | First entry is an uncompressed mimetype file naming the format. |
| EPUB | 50 4B 03 04 | First entry is mimetype containing application/epub+zip. |
| JAR / APK | 50 4B 03 04 | Contains META-INF/MANIFEST.MF; APK adds AndroidManifest.xml. |
QA trap: a scanner that identifies by signature alone will label every one of these
"ZIP archive". A policy that blocks .zip uploads but allows .docx is therefore not
really blocking anything.
Byte-order marks (BOM)
A BOM is a signature for text encoding. It is invisible in an editor and breaks parsers that do not strip it.
| Encoding | BOM bytes | Notes |
|---|---|---|
| UTF-8 | EF BB BF | Optional and often unwanted - it shows up as in the first field of a CSV. |
| UTF-16 LE | FF FE | Windows "Unicode" default. |
| UTF-16 BE | FE FF | |
| UTF-32 LE | FF FE 00 00 | Ambiguous with UTF-16 LE unless you check 4 bytes. |
QA trap: a UTF-8 BOM makes the first CSV header id instead of
id, so a lookup for the "id" column fails on a file that looks perfect on screen.
Check a signature yourself
Linux / macOS
file myfile.bin identifies by signature.
xxd -l 16 myfile.bin shows the first 16 bytes.
Windows PowerShell
Format-Hex -Path myfile.bin -Count 16
In your browser
Drop a file into the file hash verifier - it reads the bytes locally and never uploads anything.
Test it with real files
The catalog has valid and deliberately-corrupted files for most formats above - ideal for proving your validator reads bytes, not names.