只要理解了「Base64 不过是给字节穿了件文本外衣」,日常工程里很多写法就说得通了。这篇文章带你走一遍真实系统里会撞见 Base64 的七个地方,每个都配上实际代码或配置,以及每个场景下最容易踩的坑。讲完七个用途,我会专门用一节聊聊「什么时候你该坚决别碰 Base64」——这个编码是有代价的,忽视了它,代价往往体现在账单或一次事故里。
1. JWT:三段 Base64url
JSON Web Token 是点号分隔的三段:header.payload.signature。前两段是 Base64url 编码的 JSON,第三段是对前两段的签名。把中间那段解出来,token 里装了什么一目了然——这也正是你绝不能把 JWT 的 payload 当成私密数据的原因。
解码步骤在任何语言里都几行:
const [, payload] = token.split('.');
const b64 = payload.replace(/-/g, '+').replace(/_/g, '/');
const data = JSON.parse(atob(b64));
console.log(data.sub, data.exp);
为什么这里用的是 Base64url 而不是普通 Base64?因为 token 会经过 URL、请求头和 Cookie。标准字母表里的 + 和 / 在这些语境里都有特殊含义,所以 JWT 把它俩换成 - 和 _,通常还去掉填充符 =。数据没变,只是换了一层对 URL 安全的皮。一个额外的好处是 token 安全地穿过 URL 时不用再做转义。
坑:payload 只是编码,谁拿到 token 都能读。被写进访问日志、或被贴进工单的 token,它的声明就是明文泄露。payload 里只放标识符和标志位,别放任何秘密。
2. Data URI:内联小资源
为了不给一张小图标单独发一次 HTTP 请求,你可以把它直接嵌进 HTML 或 CSS,做成 data URI:
<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==" alt="icon">
浏览器把 Base64 解码成图片,不需要一次网络往返。对 favicon 或 loading 动画来说,这是实打实的收益:少发请求、少一次 DNS 查询,资源也不会 404。
坑:Base64 会让体积膨胀约 33%,而且浏览器没法把内联资源从文档里单独缓存出来。内联 200 字节的图标很香;内联 50 KB 的照片就是失误;内联 2 MB 的视频简直是灾难。什么都内联,HTML 页面本身就成了瓶颈,哪怕只改一个图标也得重新下载整页。经验值:1–2 KB 以下内联,以上用外链。
3. 邮件附件与 MIME
SMTP 协议当初是按 7 位 ASCII 文本设计的。PDF 或 PNG 的原始字节里有很多 SMTP 会破坏或丢弃的字符,所以邮件客户端把附件做 Base64 编码,并打上标记:
Content-Type: application/pdf
Content-Transfer-Encoding: base64
JVBERi0xLjQKMSAwIG9iago8PAovVGl0bGUgKEV4YW1wbGUpCj4+Cg==
接收方再把这段解码回原始字节。同样的机制也出现在 multipart 表单提交,以及某些只接受文本的 message queue 里。
坑:MIME 变体每 76 个字符插一个换行。严格的解析器遇到空白会直接报错。解码邮件或 PEM 内容前,先清掉所有空白,或用本来就认 MIME 换行的库。另外别忘了 33% 的税对所有附件都生效——10 MB 的 PDF 在线上变成约 13 MB,而且每个收件方的存储也要多付这份钱。
4. HTTP Basic Auth
Basic Auth 把凭据放进请求头,格式是 username:password 经 Base64 编码后,前面加 Basic :
const creds = Buffer.from('alice:s3cret-pw').toString('base64');
fetch('https://api.example.com', {
headers: { Authorization: 'Basic ' + creds }
});
服务端解出请求头,按冒号拆开,校验这对凭据。简单、零依赖,这也是它仍出现在内部工具和快速原型里的理由。
坑,而且是个严重的坑:编码不是加密。任何能读到请求的人——代理、日志层、浏览器开发者工具、同一内网后的同事——都能瞬间解出 alice:s3cret-pw。明文 HTTP 下的 Basic Auth 基本等于把密码明摆着发。一定要配合 TLS,能上 token 或 OAuth 就尽量上,因为日志里一行 Base64 凭据就是一次凭据泄露。
5. JSON 与 XML 里的二进制块
JSON 和 XML 都是文本格式。想在里头塞缩略图、签名或 protobuf 负载,就把字节 Base64 成字符串字段:
{
"id": "img-42",
"name": "logo",
"data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="
}
Postgres 通过文本协议返回 bytea、不少 API 传输小二进制,用的都是这招。另一种方案——转义原始字节——出错概率高得多。
坑:33% 的膨胀会吃掉你的负载大小、解析时间和内存。一个 5 MB 文件作为 JSON 字符串字段发出去变成约 6.7 MB,而且为了解码必须整段放进内存。任何大文件都该用二进制传输(gRPC、分块上传,或对象存储的预签名 URL),Base64 只留给小东西。一个粗判:如果你的 JSON 响应基本就是一个巨大的 Base64 字符串,那你多半在滥用这个格式。
6. API 请求签名与 Webhook
很多签名方案会返回一个摘要,再 Base64 一下塞进请求头。HMAC-SHA256 对 webhook 正文签名是经典例子:
const hmac = crypto.createHmac('sha256', secret);
hmac.update(payload);
const sig = hmac.digest('base64');
// 发送头:X-Signature: sig
接收方重算 HMAC 并比对。Base64 让一个定长、面向字节的摘要能作为请求头值传输,又不破坏文本协议。AWS SigV4、GitHub webhook 签名也是同理。
坑:比对必须恒定时间。朴素的 === 会泄漏时序信息,攻击者可以逐字节伪造签名。用 crypto.timingSafeEqual 或等价物,而且要在两边都从 Base64 解码后再比,不要直接比字符串——因为填充和字母表允许多种文本表示同一个摘要,字符串比对还可能因为两种合法编码而失配。
7. Kubernetes 的 Secret 与 ConfigMap
在 Kubernetes 里,Secret 的值在 manifest 中以 Base64 存储:
apiVersion: v1
kind: Secret
metadata:
name: db
type: Opaque
data:
password: czNjcmV0LXB3Cg==
data 字段要求 Base64;更友好的 stringData 字段允许你写明文,由 API server 帮你编码。ConfigMap 用的是明文,这点经常让人混淆这两种对象。
坑:这层编码只是格式要求,不是安全控制。任何能执行 kubectl get secret -o yaml 的人一行就能解出密码。除非你开了 etcd 加密,否则别把 Secret 当成静态加密。还有,因为值就写在 manifest 里,一旦你把 YAML 提交,它就进了 Git——所以密钥要从真正的密钥管理器引入并引用,千万别粘进提交的文件中。
什么时候不该用 Base64
上面七个用途都正当,因为每个场景的任务都是「让字节穿过文本」。出错的地方,是有人拿 Base64 去解决它根本解决不了的问题。
33% 的体积税是实打实的
每编码一个字节就变成 4/3 个字符。小尺度上没人 care;大尺度上积少成多。一个每小时 Base64 编码 2 GB blob 的服务,每小时多发出 660 MB 纯开销,带宽要付钱,编解码的 CPU 也要付钱。如果你的目标是缩小数据,答案是压缩(gzip、zstd、brotli),不是 Base64——后者只会让东西更大。只有通道强制要文本时才用 Base64,绝不用它来省空间。
Base64 不是加密
这点值得反复讲,因为它一直在坑团队。用 Base64 编码密码、API key 或 token,机密性为零。每月的「事故之星」通常是某个开发者用 Base64「混淆」了凭据,提交到公开仓库,还以为受保护了。并没有。传输用 TLS,对称加密用 AES-GCM 或 ChaCha20,存储密码用 bcrypt 或 argon2。如果攻击者和你的秘密之间只有一层 Base64,那你就没有秘密。
日志里的 Base64 是个泄露面
因为 Base64 看起来像乱码,工程师会忘了它是可读的。被记成 Base64 的 token、签名 cookie 或 Basic 凭据,谁读日志谁就能完整还原。更糟的是,攻击者专门扫日志里长串的 Base64,因为它们往往正是那些秘密。在进日志前就脱敏或哈希敏感字段,把任何长 Base64 当可疑。好的日志管道应该按字段屏蔽已知秘密,而不是指望编码来藏住它们。
一张快速决策表
当传输或格式只认文本、而你又必须搬运字节时,用 Base64:token、data URI、邮件、请求头、JSON 里的小块。别用它来隐藏数据、节省空间或保护凭据。把秘密挡在 payload 外、manifest 外、日志外,让真正的密码学去保护。想动手试试,把一段文本粘进 Base64 工具,切换 URL-safe 选项看看字母表怎么变。