上传文件
  1. base64hub
  2. 学习 Base64
  3. 常见 Base64 开头
参考

常见 Base64 开头:从前几个字符识别文件类型

大多数文件格式都以固定的魔数字节开头,所以它们的 Base64 也以固定的字符开头。记住十几个开头,不用解码就能分清 PNG 和 PDF。

开头对照表

每一行都用该格式真实的魔数字节编码验证过。表中的“固定开头”是该格式中永远不变的部分,后面的字符取决于文件的其余内容。

Base64 开头魔数字节(hex)格式打开工具
iVBORw0KGgo89 50 4E 47 0D 0A 1A 0APNGBase64 转 PNG
/9j/FF D8 FFJPEGBase64 转 JPG
R0lGOD47 49 46 38(GIF8)GIF(R0lGODlh = GIF89a,R0lGODdh = GIF87a)Base64 转 GIF
UklGR52 49 46 46(RIFF)WebP、WAV、AVIBase64 转 WebP、Base64 转 WAV
JVBERi025 50 44 46 2D(%PDF-)PDFBase64 转 PDF
UEsDB50 4B 03 04(PK..)ZIP、DOCX、XLSX、PPTX、JAR、APK、EPUBBase64 转 ZIP、Word、Excel
H4sI1F 8B 08gzip解码 gzip Base64
eJ、eN、eF、eA78 9C、78 DA、78 5E、78 01zlibZlib 与 Base64
eyJ7B 22({")JSON 对象、JWTBase64 转 JSON、JWT 解码器
PD94bWwg3C 3F 78 6D 6C 20(<?xml )XML、带声明的 SVGBase64 转 XML
PHN2Zy3C 73 76 67 20(<svg )不带声明的 SVG(<svg 后接换行时为 PHN2Zw)Base64 转 SVG
MII30 82DER:证书、密钥、CSR证书解码器
AAAA00 00 00SSH 公钥数据块,或前导零SSH 密钥指纹
SUQz49 44 33(ID3)带 ID3v2 标签的 MP3Base64 转 MP3
AAABAA00 00 01 00ICOBase64 转 ICO
d09GMg77 4F 46 32 00(wOF2)使用 TrueType 轮廓的 WOFF2 字体(CFF 字体以 d09GMk9UVE8 开头)Base64 转字体
TVqQ4D 5A 90(MZ)Windows EXE 或 DLL(某些链接器写入 MZP,得到 TVpQ)Base64 转文件
f0VMR7F 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 转文件工具会解码数据,并用解码后的字节去比对许多签名。

继续阅读