开头对照表
每一行都用该格式真实的魔数字节编码验证过。表中的“固定开头”是该格式中永远不变的部分,后面的字符取决于文件的其余内容。
| Base64 开头 | 魔数字节(hex) | 格式 | 打开工具 |
|---|---|---|---|
iVBORw0KGgo | 89 50 4E 47 0D 0A 1A 0A | PNG | Base64 转 PNG |
/9j/ | FF D8 FF | JPEG | Base64 转 JPG |
R0lGOD | 47 49 46 38(GIF8) | GIF(R0lGODlh = GIF89a,R0lGODdh = GIF87a) | Base64 转 GIF |
UklGR | 52 49 46 46(RIFF) | WebP、WAV、AVI | Base64 转 WebP、Base64 转 WAV |
JVBERi0 | 25 50 44 46 2D(%PDF-) | Base64 转 PDF | |
UEsDB | 50 4B 03 04(PK..) | ZIP、DOCX、XLSX、PPTX、JAR、APK、EPUB | Base64 转 ZIP、Word、Excel |
H4sI | 1F 8B 08 | gzip | 解码 gzip Base64 |
eJ、eN、eF、eA | 78 9C、78 DA、78 5E、78 01 | zlib | Zlib 与 Base64 |
eyJ | 7B 22({") | JSON 对象、JWT | Base64 转 JSON、JWT 解码器 |
PD94bWwg | 3C 3F 78 6D 6C 20(<?xml ) | XML、带声明的 SVG | Base64 转 XML |
PHN2Zy | 3C 73 76 67 20(<svg ) | 不带声明的 SVG(<svg 后接换行时为 PHN2Zw) | Base64 转 SVG |
MII | 30 82 | DER:证书、密钥、CSR | 证书解码器 |
AAAA | 00 00 00 | SSH 公钥数据块,或前导零 | SSH 密钥指纹 |
SUQz | 49 44 33(ID3) | 带 ID3v2 标签的 MP3 | Base64 转 MP3 |
AAABAA | 00 00 01 00 | ICO | Base64 转 ICO |
d09GMg | 77 4F 46 32 00(wOF2) | 使用 TrueType 轮廓的 WOFF2 字体(CFF 字体以 d09GMk9UVE8 开头) | Base64 转字体 |
TVqQ | 4D 5A 90(MZ) | Windows EXE 或 DLL(某些链接器写入 MZP,得到 TVpQ) | Base64 转文件 |
f0VMR | 7F 45 4C 46(.ELF) | Linux ELF 可执行文件 | Base64 转文件 |
有几行值得补充说明:
- RIFF 是一种容器格式。格式名位于第 8 到 11 个字节,其中第 9 到 11 个字节编码为第 13 到 16 个字符:WebP(
EBP)是RUJQ,WAV(AVE)是QVZF,AVI(VI)是Vkkg。格式名的第一个字母和文件大小共用一个 3 字节分组,所以不会产生固定的字符。 - zlib 没有唯一的开头。第二个字节记录压缩级别:默认级别为
eJ,级别 7 到 9 为eN,2 到 5 为eF,0 和 1 为eA(以 Python 的zlib输出为准)。 - JSON 只有在前两个字符是不带空格的
{"时才以eyJ开头。{ "a": 1 }以eyAi开头,格式化输出的{加换行以ewo开头,数组[则以W开头。 - DER 结构以 SEQUENCE 标签
30开头,总是得到M。两字节长度(30 82)得到MII,下一个字符随长度变化:RSA 2048 公钥是MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA,常见的证书是MIIC、MIID或MIIE。较短的结构使用单字节长度:Ed25519 私钥以MC4C开头,P-256 公钥以MFkw开头。 - SSH 公钥以 4 字节大端序长度开头,所以数据块以
AAAA开头。接下来的字符拼出密钥类型:AAAAC3NzaC1lZDI1NTE5是ssh-ed25519,AAAAB3NzaC1yc2E是ssh-rsa,AAAAE2VjZHNh是ecdsa。在 SSH 以外,一串A表示零字节。 - MP3 文件如果没有 ID3 标签,会直接以帧头开始。帧头的同步位
FF Fx会得到开头的//,例如FF FB 90对应//uQ。 - Base64URL 替换了
+和/,所以 JPEG 的开头变成_9j_。表中其他开头都不含这两个字符,在两种字母表中看起来一样。
为什么开头是固定的
Base64 以 3 个字节为一组进行处理。每组 3 个字节恰好变成 4 个字符,而这 4 个字符只取决于这 3 个字节,文件后面的内容不会影响到前面。所以,如果某种格式总以相同的 3 个字节开头,它的 Base64 就总以相同的 4 个字符开头。
bytes: FF D8 FF
bits: 11111111 11011000 11111111
groups: 111111 111101 100011 111111
index: 63 61 35 63
chars: / 9 j /
如果固定的魔数长度不是 3 的倍数,最后一组就会把已知的比特和文件其余部分的比特混在一起。以 5 字节的 %PDF- 为例:前 3 个字节给出 4 个固定字符(JVBE),后 2 个字节再给出 2 个(Ri),第七个字符则混合了 - 的最后 4 个比特和版本号数字的前 2 个比特。所有数字的前两个比特都是 00,所以实际上这个字符总是 0。在 JVBERi0 之后就能看出版本:JVBERi0xLjQ 是 %PDF-1.4,JVBERi0xLjc 是 %PDF-1.7,JVBERi0yLjA 是 %PDF-2.0。
PNG 签名有 8 个字节,给出 10 个固定字符。第十一个字符混合了签名的最后一个字节和 IHDR 块长度的第一个字节,而后者总是 0,所以 iVBORw0KGgo 是成立的。由于每个 PNG 后面都紧跟这个 IHDR 块,真实的 PNG 文件共享一个更长的开头:iVBORw0KGgoAAAANSUhEUg。
开头发生偏移的情况
只有当魔数字节位于偏移 0、3、6 等位置时,这个规律才成立。前面多一个字节,所有分组都会错位,字符就完全变了:
| 输入 | Base64 开头 |
|---|---|
| PNG 签名 | iVBORw0KGgoAAAA |
| 1 字节 + PNG 签名 | AIlQTkcNChoKAAAA |
| 2 字节 + PNG 签名 | AACJUE5HDQoaCgAA |
| 3 字节 + PNG 签名 | AAAAiVBORw0KGgoA |
多出 3 个字节时,对齐恢复,熟悉的开头会在 4 个字符之后重新出现。开头偏移或消失的常见原因:
- 字节顺序标记(BOM)。带 BOM 的 UTF-8 文本以
77u/(EF BB BF)开头。BOM 是 3 个字节,所以后面的 JSON 仍然显示为eyJ:77u/eyJ…。带 BOM 的 UTF-16 文本以//4或//5(小端序)或/v8(大端序)开头。 - UTF-16 文本。保存为 UTF-16LE 的文本会在每个 ASCII 字符后放一个零字节,于是出现大量
A:Hello变成SABlAGwAbABvAA==。PowerShell 的-EncodedCommand就是这个样子。可以用 PowerShell 解码器或 Base64 转 UTF-16。 - 压缩或加密。经过 gzip 压缩的 PNG 以
H4sI开头,加密后的文件则完全没有可识别的开头。 - data URI。
data:image/png;base64,是头部,Base64 从逗号之后开始。data URI 解析器可以帮你拆开。 - 换行和空白。它们不会让字节错位,但如果字符串以换行或引号开头,用
startswith搜索开头就会失败。先去掉首尾空白,或者用 修复 Base64 处理一下。
在代码中检查开头
可以直接按字符串匹配开头几个字符,但比较解码后的字节更不容易出错:魔数直接来自各格式的规范,你也不必去推算哪些字符是固定的。只解码前 12 个字符就能得到前 9 个字节,足够识别大多数签名:
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
Base64 转文件工具会用一份更长的列表做同样的检查,并在浏览器能渲染该格式时显示预览。关于 6 比特分组如何映射到字符,见 Base64 的工作原理和字母表。
常见问题
以 iVBORw0KGgo 开头的 Base64 字符串是什么?
是一张 PNG 图片。iVBORw0KGgo 是 8 字节 PNG 签名 89 50 4E 47 0D 0A 1A 0A 的 Base64 形式。粘贴到 Base64 转 PNG 就能看到图片。
Base64 字符串开头的 /9j/ 是什么?
是 JPEG。每个 JPEG 文件开头的字节 FF D8 FF 编码后就是 /9j/。在 Base64URL 中,同样的开头写作 _9j_。
为什么 JWT 总是以 eyJ 开头?
JWT 的头部是一个 JSON 对象,所以以 {" 开头,这两个字节编码后正是 eyJ。载荷部分出于同样的原因也以 eyJ 开头。
我的 Base64 和已知的开头都对不上,怎么办?
数据前面可能多了字节,也可能经过压缩或加密,或者只是普通文本。Base64 转文件工具会解码数据,并用解码后的字节去比对许多签名。