Base64 的六个坑:中文乱码、加号变空格与 data URI

Base64 本身只是查表替换,坑全在边界上:中文要先过 UTF-8、URL 里加号会变空格、atob 动不动报错、图片转 data URI 会膨胀三分之一——六个高频场景逐一讲清修法。

约 7 分钟读完 · 更新于 2026-10-21 · 文中每条都能在下面的配套工具里动手验证

先记住:Base64 是编码,不是加密

它做的事只有一件:把任意 3 字节重排成 4 个可打印字符(A-Z a-z 0-9 + /),让二进制能安全穿过「只认文本」的通道(JSON 字段、邮件、URL)。任何人拿到 Base64 都能瞬间还原——需要保密请用加密,Base64 只负责「安全运输」。

坑 1:中文必须先过 UTF-8,否则必乱码

btoa 的输入按 Latin-1(每字符 1 字节)理解,中文字符超出该范围直接抛错;有人用 charCodeAt 硬凑,产出的是错误字节序列,解码回来就是乱码。正确做法:先 UTF-8 编码成字节,再对字节做 Base64。

✗ 直接编码:要么报错要么乱码
btoa("中文")
// InvalidCharacterError(或产出无法还原的字节)
✓ 先 UTF-8 再编码
const bytes = new TextEncoder().encode("中文");
const b64  = btoa(String.fromCharCode(...bytes));

// 解码反向:
const bin = atob(b64);
const txt = new TextDecoder().decode(Uint8Array.from(bin, c => c.charCodeAt(0)));
// → "中文"

坑 2:URL 里的加号会变空格

标准 Base64 字母表含 + 和 /,而 + 在 URL 查询串的历史语义是空格:服务端做一次 URL 解码,token 里的 + 全变成空格,校验直接失败。这正是 JWT 用 base64url 的原因。修复二选一:传输前整体 encodeURIComponent;或直接换 URL 安全变体。

标准 ↔ base64url 互转
const urlSafe = b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
const back    = urlSafe.replace(/-/g, "+").replace(/_/g, "/");
变体第 62 / 63 位字符填充适用
标准 Base64+ 和 /= 有JSON 字段、邮件、MIME
Base64url- 和 _= 可留可去URL / Cookie / JWT / 文件名

坑 3:atob 报错的三大来源

  1. 长度不是 4 的倍数——末尾的 = 填充被谁吃掉了(URL 传输、日志截断、复制少了)。
  2. 混入了字母表外的字符:空格、换行(邮件正文常按 76 字符折行)、全角字符。
  3. 对 base64url 字符串用 atob:- 和 _ 不在标准字母表里,先换回 + / 再解。

坑 4:data URI 让图片体积涨 33%

3 字节变 4 字符,膨胀率恒定 4/3 ≈ 133%,再算上 data:image/png;base64, 前缀。小图标内嵌没问题(省一次请求),但把几百 KB 的图内嵌进 HTML/CSS/JS,等于让每个访客多下三分之一的流量,还会错失缓存。经验值:2 KB 以下考虑内嵌,更大的图老老实实用文件 + 缓存头。体积与编码互转可以直接用 <a class="faq-link" href="/tools/image-to-base64/">图片转 Base64 工具</a>核对。

坑 5:padding 与长度:= 是补齐不是内容

原始字节数除以 3 的余数决定补几个 =:余 1 补两个,余 2 补一个,整除不补。因此去掉 = 也能无损还原(长度 mod 4 可推算),JWT 就去掉了 padding;但混用场景要小心——有的校验器严格要求 padding,有的拒绝 padding,传输两端先对齐约定。判断一段 Base64 长度是否合法:去掉 = 后,长度 mod 4 的余数必须与 padding 数量自洽。

坑 6:Blob / File 与 Base64 互转是异步的

浏览器里正确的互转姿势
// Blob/File → base64(异步)
const b64 = await new Promise((res, rej) => {
  const r = new FileReader();
  r.onload = () => res(r.result.split(",")[1]);  // 去掉 data:...;base64, 前缀
  r.onerror = rej;
  r.readAsDataURL(blob);
});

// base64 → Blob
const bin = atob(b64);
const bytes = Uint8Array.from(bin, c => c.charCodeAt(0));
const blob2 = new Blob([bytes], { type: "image/png" });

常见问题

为什么 JWT 用的是「看起来不太一样」的 Base64?

JWT 用 base64url:+ 换成 -、/ 换成 _、去掉 = 填充。因为 JWT 出现在 URL、Cookie 和 HTTP 头里,标准字母表的 + / = 都有特殊含义,直接放会被改写。互转规则就一行 replace,可以用 Base64 编解码工具实际验证。

Base64 能用来「加密」密码吗?

绝对不能。Base64 没有密钥、没有混淆,任何人拿到的就是原文的另一种写法,一行代码即可还原。密码要存就先加盐哈希(bcrypt / argon2),传输要保密就走 TLS。

图片转 Base64 后 CSS 里显示不出来,查什么?

三处常见断点:① data URI 前缀不完整或 MIME 类型写错(image/jpg 应为 image/jpeg);② Base64 串里混入了换行或空格(从邮件 / PDF 复制时常见);③ CSS url() 里的引号把串截断了。先把编码结果粘进工具里「解码回预览」看图能不能出来,能出来就是引用端的问题。

Base64 编码后为什么比原文件大三分之一?能避免吗?

膨胀率是数学决定的:3 字节重排成 4 字符(4/3 ≈ 133%),再算上 data URI 前缀。无法避免,只有替代方案:大文件走二进制通道(multipart、原始字节流),URL 场景用 base64url 并配合 gzip(Base64 字符集熵低,压缩能收回一部分)。2 KB 以下内嵌、更大走文件 + 缓存,是性价比分界线。

📂

松开以自动格式化

支持 .json / .sql / .csv / .html / .css / .xml / .yaml 文件