什么是 Base64 编码,何时使用它
如果你跟 API、邮件系统或网页开发打交道,你就遇到过 Base64,只是可能没认出来。那些在邮件附件开头、CSS 中的 data: URL,或者 JWT 中间段里看上去乱七八糟的长串字母数字?那就是 Base64。它是互联网管道里最古老也最默默承重的一段之一,几乎你用的每一款软件都在某处依赖它。
Base64 简史
Base64 属于一族叫做「radix-64」或「可打印编码」的方案,它们的工作是用文本系统保证原样通过的那套小字母表,来表示任意字节。最早被广泛使用的成员是 uuencode,由 Mary Ann Horton 在 UC Berkeley 大约 1980 年写出,用于在那个 7 位 ASCII 以上一切都会被破坏的年代,通过 Usenet 和邮件传输二进制文件。
Base64 字母表本身最早在 RFC 989(1987)里为隐私增强邮件(PEM)标准化,后者是签名和加密邮件的早期尝试。PEM 死了,但它的编码方案活了下来,被 RFC 1421(1993)以及随后的 MIME 规范(1993 年的 RFC 1521 和 1522,1996 年修订为 RFC 2045 到 2049)正式收编。MIME 把 Base64 定为附加二进制文件到邮件的默认方式,从这里出发,这种编码扩散到了互联网上几乎所有纯文本传输通道。
2006 年,IETF 把分散的 Base64 定义整合进 RFC 4648,在同一份文档里定义了 Base64、Base32 和 Base16。RFC 4648 还在第 5 节定义了 URL 安全变种,把两个对 URL 不友好的字符(+ 和 /)换成 - 和 _。JSON Web Token(RFC 7519,2015)统一使用去掉填充的 URL 安全 Base64。今天,每一份邮件附件、每一张 PEM 编码证书、每一个 data: URL、每一枚 JWT、每一个 multipart 上传边界,都依赖 Base64。
Base64 如何工作:数学
Base64 取三个输入字节(24 位),用一个 64 符号的字母表把它们重写为四个输出字符(每个 6 位)。映射是固定的:
| 索引范围 | 字符 |
|---|---|
| 0-25 | A-Z |
| 26-51 | a-z |
| 52-61 | 0-9 |
| 62 | +(标准)或 -(URL 安全) |
| 63 | /(标准)或 _(URL 安全) |
所以 Hello 变成:
- ASCII 字节:
0x48 0x65 0x6C 0x6C 0x6F(5 字节) - 二进制:
01001000 01100101 01101100 01101100 01101111 - 按 6 位分组:
010010 000110 010101 101100 011011 000110 1111 - 最后一组不足,用 0 补齐:
010010 000110 010101 101100 011011 000110 111100 - 查表:
S G V s b G 8(6 组 6 位 = 36 位仅得 7 个字符,缺失的 4 位用填充补) - 填充:加
=把输出凑成 4 的倍数:SGVsbG8=
输出总是 4 个字符的倍数。如果输入长度模 3 为 1,你得到两个 = 填充字符;为 2 则一个;为 0 则没有。填充有时会被去掉(特别是 JWT 和 URL 片段),解码器需要容忍这种情况。
33 % 的体积开销正来自这一 3 比 4 的扩张:每 3 字节的输入变 4 个字符的输出,多三分之一。除非换字母表,否则无法缩小(Base85 / Ascii85 用 85 个可打印字符把扩张降到 25 %,代价是更复杂的编码器)。
常见用例
邮件附件。SMTP,这个承载 95 % 服务器间邮件的协议,1982 年设计时(RFC 821)是为 7 位 ASCII 设计的。你发的每一份二进制附件(图片、PDF、ZIP)都在传输前被你的邮件客户端编为 Base64,再被收件人的客户端解开。邮件里的 MIME 头告诉收件人哪些部分是 Base64,哪些是纯文本。
HTML 与 CSS 中的 data URL。形如 data:image/png;base64,iVBORw0KGgo... 的 URL 把一个二进制文件直接嵌进文档里。对 1-2 KB 以下的小图标有用,这时节省一次 HTTP 请求的收益超过 33 % 的开销和失去缓存的代价。
API 负载。当 JSON 或 XML API 需要接收二进制值(上传文件、签名、头像)时,标准做法是把字节编为 Base64,作为字符串字段发送。接收方在服务端解码。OpenAI 的图像输入是这样工作的,Stripe 接收文件上传也是,大多数云函数接收二进制输入还是。
HTTP Basic 认证。Authorization: Basic <token> 头携带一对以 Base64 编码的 username:password(RFC 7617)。这是编码,不是加密:任何看到头部的人都看到了密码。Basic Auth 因此要求 HTTPS。
证书与密钥。PEM 文件(-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----)把一团 DER 编码的 ASN.1 字节用 Base64 包起来。每一份 TLS 证书、每一个 SSH 密钥文件、每一份代码签名证书,都是 PEM 信封内的 Base64。
JWT 令牌。JWT 是用点分隔的三段 URL 安全 Base64:<header>.<payload>.<signature>。Base64 编码让 JWT 能安全地放进头部、URL 和 cookie 中传输。
如何编码与解码
- 选择编码或解码:选择转换方向。
- 粘贴文本或上传文件:直接输入文本或拖放文件(浏览器侧编码最多 5 MB)。
- 选择变种:邮件和证书用标准 Base64,JWT 和 URL 片段用 URL 安全。工具默认是标准。
- 复制结果:输出实时更新。复制到剪贴板,或者长输出用下载按钮。
Base64 的变种
针对特定场景存在若干类 Base64 的编码:
| 变种 | 差异 | 使用场合 |
|---|---|---|
| 标准(RFC 4648 §4) | A-Z、a-z、0-9、+、/、= 填充 | 邮件(MIME)、PEM、通用二进制转文本 |
| URL 安全(RFC 4648 §5) | + 变 -,/ 变 _ | JWT、URL 片段、文件名 |
| MIME(RFC 2045) | 每 76 字符一次换行 | 邮件正文、邮件头(配合 =?utf-8?B?...?=) |
| crypt(3) / htpasswd | 不同字母表(./0-9A-Za-z) | 老式 Unix 密码哈希(基于 DES) |
| 无填充 Base64Url | URL 安全无尾部 = | JWT(按 RFC 7515) |
| Base32(RFC 4648 §6) | 32 字符字母表,不区分大小写 | TOTP 密钥、Onion 地址 |
| Base58 | 58 字符字母表(不含 0、O、I、l) | 比特币地址、IPFS CID |
| Ascii85 / Base85 | 85 字符字母表,25 % 开销 | PDF、PostScript |
大多数时候你想要标准或 URL 安全 Base64。其它的会出现在特定协议里。
何时使用 Base64
合适的时候:
- 需要把一张小图(5 KB 以下)直接嵌进 HTML 或 CSS,以省下一次 HTTP 请求。
- API 要求在 JSON 或 XML 负载里以文本字符串形式提供二进制数据。
- 要通过只支持文本的系统(邮件、日志条目、查询参数)传递二进制数据。
- 在编码 JWT、证书、密钥或任何结构化的二进制块。
- 需要一种任何语言都能解码、确定性且自包含的字符串表示。
不合适的时候:
- 文件很大。Base64 增加 33 % 开销,使浏览器无法把二进制作为独立资源缓存,并强制整块通过页面解析器。
- 需要安全。Base64 不是加密,可以被轻易逆转。
- 能正常提供文件。对于超过几 KB 的内容,普通
<img src="photo.jpg">比 Base64 data URL 更高效。 - 需要紧凑表示。若大小无所谓,十六进制更简单;若在意大小,Base85 更密。
常见陷阱
- 把编码当加密。Base64 任何人在毫秒级就能逆转。把「秘密」过一遍 Base64 除了挡住偷瞄什么都防不住。
- 33 % 的体积代价。1 MB 的图片变成 1.33 MB 的字符串,而且行内 data URL 每次访问都会随父 HTML 一起下载,没有独立缓存。
- MIME Base64 的换行。MIME 变种每 76 字符插入
\r\n。如果你把 MIME Base64 粘进 JSON 值或 URL,会出错;先去掉换行。 - JWT 去掉填充。JWT 使用去掉
=填充的 URL 安全 Base64。严格要求填充的库会拒绝合法的 JWT;不产出填充的库会创建别的库拒绝的令牌。RFC 7515 规定 JWS 标准必须「无填充」。 - URL 安全与标准混淆。用标准解码器解 URL 安全字符串,会在
-和_上失败;反之亦然,在+和/上失败。 - Unicode 输入处理。Base64 处理的是字节,不是字符。如果你给一串 UTF-8 表情符号做 Base64,得先决定字节编码(几乎总是 UTF-8)。不同运行时默认不同,要显式指定。
- 部分流式解码器。正确实现的 Base64 流解码器会等够 4 个输入字符,再输出 3 个字节。每次只解一个字符的幼稚实现会出乱码。
- 末尾空白与 BOM。有些编辑器保存时追加换行或 UTF-8 BOM。这一个多余字节会改变 Base64 输出。看到意外不匹配时,把你的编码结果与上游源做 diff。
- URL 里的
+被当作空格。标准 Base64 的+在 URL 解析器做百分号解码时变成空格。这就是 URL 安全 Base64 存在的原因。
替代方案与相邻编码
Base64 是默认,但不是唯一选择。正确选择取决于通道和大小预算。
| 编码 | 开销 | 优点 | 最适合 |
|---|---|---|---|
| 十六进制(Base16) | 100 % | 易读,每字节两字符 | 调试输出、短标识符、颜色码 |
| Base32(RFC 4648) | 60 % | 不区分大小写,无相似字符 | TOTP 密钥、Onion 地址、口述 |
| 标准 Base64 | 33 % | 通用,每种语言都有 | 邮件、PEM、通用传输 |
| URL 安全 Base64 | 33 % | URL 与文件名安全 | JWT、URL 片段 |
| Base58 | ~37 % | 无 0/O/I/l 混淆,无特殊字符 | 比特币地址、IPFS CID |
| Ascii85 / Base85 | 25 % | 比 Base64 更密 | PDF、PostScript |
| Base91 | ~22 % | 更密,但更复杂 | 少见,小众压缩场景 |
| multipart 上传 | 0 % | HTTP 上的原生二进制传输 | 文件上传(浏览器替你做) |
| gzip + Base64 | 视情况 | 有时比原始 Base64 更小 | 预压缩负载 |
大多数日常工作的答案是 Base64(标准或 URL 安全)。HTTP 上的二进制文件上传,正确答案通常是 multipart/form-data,它根本不编码。
隐私与编码器
Base64 编码器和解码器完全在你的浏览器里运行。你输入的文本或文件由设备上的 JavaScript 处理,结果渲染到页面上,不向服务器发送任何东西。什么都不会被记录,什么都不会在你离开页面之后保留,也没有分析标签看到内容。对于你可能编码到 Base64 里的东西(PEM 证书、私钥、生产系统的 JWT 负载、含真实客户数据的 API 草稿请求),这种严格本地的流程才是正确的默认值。整套工具一旦页面加载完成就可以离线运行,你可以断网再编码同一份输入来验证这一点。
常见问题
Base64 会加密我的数据吗?
不会。Base64 是编码,而不是加密。任何人都能解码 Base64 字符串 · 它不提供安全性。如果您想保护数据,请使用真正的加密(AES、RSA 等)。
为什么 Base64 让文件变大?
Base64 编码将数据大小增加约 33%。三个二进制字节变为四个 Base64 字符。这是让二进制能作为文本安全传输的代价。
可以编码文件,不仅是文本吗?
可以。任何文件(图像、PDF、音频)都可编码为 Base64。这常用于将小图像作为 data URL 直接嵌入 HTML 或 CSS 中。
什么时候不要使用 Base64?
不要用于大文件。1 MB 的图像作为 Base64 文本会变成 1.33 MB,而且浏览器不能单独缓存它。对于超过几 KB 的任何内容,正常提供文件更高效。
What is the difference between standard Base64 and URL-safe Base64?
Standard Base64 (RFC 4648 section 4) uses the characters A-Z, a-z, 0-9, +, / with = padding. URL-safe Base64 (RFC 4648 section 5) swaps + for - and / for _ so the string is safe to drop into a URL or a filename without percent-encoding. JWT tokens use the URL-safe variant.
Why does Base64 sometimes have one or two = signs at the end?
The = is padding. Base64 encodes input in 3-byte groups; if the input length is not a multiple of 3, the last group is padded with zero bits and one or two = characters mark the missing bytes. One = means one missing byte, two = means two missing bytes.