Prefix table
Each row was checked by encoding the real magic bytes of the format. “Stable prefix” is the part that never changes for that format. The characters after it depend on the rest of the file.
| Base64 prefix | Magic bytes (hex) | Format | Open with |
|---|---|---|---|
iVBORw0KGgo | 89 50 4E 47 0D 0A 1A 0A | PNG | Base64 to PNG |
/9j/ | FF D8 FF | JPEG | Base64 to JPG |
R0lGOD | 47 49 46 38 (GIF8) | GIF (R0lGODlh = GIF89a, R0lGODdh = GIF87a) | Base64 to GIF |
UklGR | 52 49 46 46 (RIFF) | WebP, WAV, AVI | Base64 to WebP, Base64 to WAV |
JVBERi0 | 25 50 44 46 2D (%PDF-) | Base64 to PDF | |
UEsDB | 50 4B 03 04 (PK..) | ZIP, DOCX, XLSX, PPTX, JAR, APK, EPUB | Base64 to ZIP, Word, Excel |
H4sI | 1F 8B 08 | gzip | Decode gzip Base64 |
eJ, eN, eF, eA | 78 9C, 78 DA, 78 5E, 78 01 | zlib | Zlib and Base64 |
eyJ | 7B 22 ({") | JSON object, JWT | Base64 to JSON, JWT decoder |
PD94bWwg | 3C 3F 78 6D 6C 20 (<?xml ) | XML, SVG with declaration | Base64 to XML |
PHN2Zy | 3C 73 76 67 20 (<svg ) | SVG without declaration (<svg plus a newline gives PHN2Zw) | Base64 to SVG |
MII | 30 82 | DER: certificates, keys, CSRs | certificate decoder |
AAAA | 00 00 00 | SSH public key blob, or leading zeros | SSH key fingerprint |
SUQz | 49 44 33 (ID3) | MP3 with ID3v2 tag | Base64 to MP3 |
AAABAA | 00 00 01 00 | ICO | Base64 to ICO |
d09GMg | 77 4F 46 32 00 (wOF2) | WOFF2 font with TrueType outlines (CFF fonts start d09GMk9UVE8) | Base64 to font |
TVqQ | 4D 5A 90 (MZ) | Windows EXE or DLL (some linkers write MZP, which gives TVpQ) | Base64 to file |
f0VMR | 7F 45 4C 46 (.ELF) | Linux ELF binary | Base64 to file |
A few rows deserve a note:
- RIFF is a container. The format name sits at bytes 8 to 11, and bytes 9 to 11 encode as the 13th to 16th characters:
RUJQfor WebP (EBP),QVZFfor WAV (AVE),Vkkgfor AVI (VI). The first letter of the format name shares a 3-byte group with the file size, so it does not produce a stable character. - zlib has no single prefix. The second byte records the compression level:
eJfor the default level,eNfor levels 7 to 9,eFfor 2 to 5,eAfor 0 and 1 (as written by Python’szlib). - JSON starts with
eyJonly when the first two characters are{"with no space.{ "a": 1 }starts witheyAi, pretty-printed{plus a newline starts withewo, and an array[starts withW. - DER structures start with a SEQUENCE tag
30, which always givesM. A two-byte length (30 82) givesMII, and the next character tracks the length: an RSA 2048 public key isMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA, a typical certificate isMIIC,MIIDorMIIE. Short structures use a one-byte length: an Ed25519 private key starts withMC4C, a P-256 public key withMFkw. - SSH public keys begin with a 4-byte big-endian length, so the blob starts with
AAAA. The next characters spell the key type:AAAAC3NzaC1lZDI1NTE5isssh-ed25519,AAAAB3NzaC1yc2Eisssh-rsa,AAAAE2VjZHNhisecdsa. Outside SSH, a run ofAmeans zero bytes. - MP3 files without an ID3 tag start directly with a frame header. Its sync bits
FF Fxgive a leading//, for example//uQforFF FB 90. - Base64URL replaces
+and/, so the JPEG prefix becomes_9j_. The other prefixes in the table contain neither character and look the same in both alphabets.
Why the prefix is stable
Base64 works on groups of 3 bytes. Each group of 3 bytes becomes exactly 4 characters, and those 4 characters depend only on those 3 bytes. Nothing from later in the file leaks backwards. So if a format always starts with the same 3 bytes, its Base64 always starts with the same 4 characters.
bytes: FF D8 FF
bits: 11111111 11011000 11111111
groups: 111111 111101 100011 111111
index: 63 61 35 63
chars: / 9 j /
When the fixed magic is not a multiple of 3 bytes long, the last group mixes known bits with bits from the rest of the file. Take the 5-byte %PDF-. The first 3 bytes give 4 stable characters (JVBE). The next 2 bytes give 2 more (Ri), and the seventh character mixes the last 4 bits of - with the first 2 bits of the version digit. Every digit starts with the bits 00, so in practice that character is always 0. After JVBERi0 the version shows through: JVBERi0xLjQ is %PDF-1.4, JVBERi0xLjc is %PDF-1.7, JVBERi0yLjA is %PDF-2.0.
The PNG signature is 8 bytes, which gives 10 stable characters. The eleventh mixes the last signature byte with the first byte of the IHDR chunk length, which is always 0, so iVBORw0KGgo holds. Since every PNG continues with that IHDR chunk, real PNGs share an even longer prefix: iVBORw0KGgoAAAANSUhEUg.
When the prefix shifts
The prefix only works when the magic bytes sit at offset 0, 3, 6 and so on. Add one byte in front and every group shifts, so the characters change completely:
| Input | Base64 starts with |
|---|---|
| PNG signature | iVBORw0KGgoAAAA |
| 1 byte + PNG signature | AIlQTkcNChoKAAAA |
| 2 bytes + PNG signature | AACJUE5HDQoaCgAA |
| 3 bytes + PNG signature | AAAAiVBORw0KGgoA |
With 3 extra bytes the alignment is restored and the familiar prefix reappears 4 characters later. Common reasons for a shifted or missing prefix:
- A byte order mark. UTF-8 text with a BOM starts with
77u/(EF BB BF). The BOM is 3 bytes, so JSON after it still showseyJ:77u/eyJ…. UTF-16 text with a BOM starts with//4or//5(little-endian) or/v8(big-endian). - UTF-16 text. Text saved as UTF-16LE puts a zero byte after every ASCII character, which produces a lot of
A:HellobecomesSABlAGwAbABvAA==. PowerShell’s-EncodedCommandlooks like this. Use the PowerShell decoder or Base64 to UTF-16. - Compression or encryption. A gzipped PNG starts with
H4sI; an encrypted file has no recognizable prefix at all. - A data URI.
data:image/png;base64,is a header; the Base64 starts after the comma. The data URI parser splits them for you. - Line breaks and whitespace. They do not shift the bytes, but a prefix search with
startswithwill fail if the string begins with a newline or a quote. Trim first, or run it through Repair Base64.
Checking a prefix in code
You can match the first characters as strings, but comparing decoded bytes is less error-prone: the magic numbers come straight from each format’s specification, and you never have to work out which characters are stable. Decoding only the first 12 characters gives the first 9 bytes, enough for most signatures:
import base64
SIGNATURES = {
b"\x89PNG\r\n\x1a\n": "png",
b"\xff\xd8\xff": "jpeg",
b"%PDF-": "pdf",
b"PK\x03\x04": "zip",
b"\x1f\x8b": "gzip",
}
def sniff(b64: str) -> str:
head = base64.b64decode(b64.strip()[:12])
for magic, name in SIGNATURES.items():
if head.startswith(magic):
return name
return "unknown"
print(sniff("iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB")) # png
print(sniff("JVBERi0xLjcKJcfsj6IK")) # pdf
The Base64 to file tool does the same check against a longer list and shows a preview when the browser can render the format. For how the 6-bit groups map to characters, see how Base64 works and the alphabet table.
FAQ
What does a Base64 string starting with iVBORw0KGgo mean?
It is a PNG image. iVBORw0KGgo is the Base64 form of the 8-byte PNG signature 89 50 4E 47 0D 0A 1A 0A. Paste it into Base64 to PNG to see it.
What is /9j/ at the start of a Base64 string?
A JPEG. The bytes FF D8 FF that open every JPEG file encode to /9j/. In Base64URL the same prefix is written _9j_.
Why do JWTs always start with eyJ?
A JWT header is a JSON object, so it begins with {", and those two bytes encode to eyJ. The payload part starts with eyJ for the same reason.
My Base64 does not match any known prefix. What now?
The data may have extra bytes in front, be compressed or encrypted, or be plain text. The Base64 to file tool decodes the data and checks the bytes against many signatures.