File “magic numbers” are fixed byte prefixes that identify a format. Documentation almost always prints them as hex because two digits per byte stay short and copy-paste friendly. Learning a small set of signatures is one of the fastest ways to debug corrupt downloads, mislabeled uploads, and “this isn’t a PNG” support tickets.
Decode unknown prefixes with Hex to String or inspect bits with Hex to Binary.
Why magic numbers show up as hex
Raw bytes are awkward in prose. Hex keeps tables readable and aligns with hex editors. Example: PNG begins with eight bytes commonly written as:
89 50 4E 47 0D 0A 1A 0A
50 4E 47 is ASCII PNG. The leading 89 is intentionally non-text so naive tools do not treat the file as plain text.
Signatures worth knowing
| Format | Hex prefix | Notes |
|---|---|---|
| JPEG | FF D8 FF | Ends with FF D9 |
| PNG | 89 50 4E 47 0D 0A 1A 0A | Includes ASCII PNG |
| GIF | 47 49 46 38 | ASCII GIF8 |
25 50 44 46 | ASCII %PDF | |
| ZIP | 50 4B 03 04 | ASCII PK (also jars, docx, apk containers) |
| ELF | 7F 45 4C 46 | Many Linux executables |
| UTF-8 BOM | EF BB BF | Hint for text, not a file type |
A reliable check procedure
- Read the first 8–16 bytes (more for formats with longer headers).
- Print them as spaced hex.
- Compare against a short signature table.
- If bytes look printable, also interpret as ASCII/UTF-8 for a second opinion.
- Remember: a matching prefix does not prove the whole file is intact.
from pathlib import Path
def head_hex(path: str, n: int = 16) -> str:
b = Path(path).read_bytes()[:n]
return b.hex(" ")
print(head_hex("sample.bin"))
<?php
$fh = fopen('sample.bin', 'rb');
$head = fread($fh, 16);
fclose($fh);
echo bin2hex($head);
Real-world failure modes
- HTML error page saved as .png: hex starts with
3c 21 64 6f 63...(<!doc...), not89 50 4E 47. - Truncated download: magic matches, decoder crashes later—use checksums for integrity.
- Container formats: a
.docxis a ZIP; magic looks like ZIP, not “DOCX-specific.” - Optional leading junk: uncommon, but some streams prepend length fields before the magic.
Magic vs content-type vs extension
Browsers and CDNs may trust Content-Type or the file extension. Those lie often. Magic bytes are closer to the truth, but still only a prefix check. Security-sensitive uploaders should combine:
- magic sniffing
- size limits
- safe decoding libraries
- storage outside the web root
FAQ
Is every file type identifiable this way?
No. Plain UTF-8 text has no required magic. CSV/JSON are conventions.
Should I store magic tables in my app?
For a few formats, yes. For broad detection, use a maintained library rather than hand-rolling hundreds of rules.
Related: hex encoding overview · Hex vs Base64 · colors as hex bytes: Hex to RGB.