第 3 课
填充与长度
末尾 `=` 的含义,以及输入字节与输出长度的关系。
搞懂 六位一组 之后,Base64 的输出长度就很好记。末尾的 =(有时是 ==)不是在“加密”什么,而是在告诉解码器:最后这一组用到的真实输入字节不到三个,需要按规则截断多余的比特。
什么时候会出现填充
看输入长度的 除以 3 的余数即可:
输入字节数 n,n % 3 | 典型填充 |
|---|---|
0 | 无需填充 |
1 | == |
2 | = |
原因:每一段处理都把 最多 3 个字节(24 bit)编成 4 个可打印符号。最后一个“块”如果凑不满 3 字节,仍然会输出 4 个符号,但末位的几位是“占位”,用 = 明示其语义,避免解码器把补零当成真实数据。
在实践中,有人会 删掉 =“因为也能解码”——部分库会自动推断,但这对跨语言/跨实现的互操作是地雷:一边严格校验,一边裁剪,很容易出现偶发兼容问题。
= 不是什么
在传统 Base64里,= 不属于那 64 个数据符号,而是 元字符。它被误删、拼接进 URL、或在复制时被截断,都会导致长度或对齐检查失败——有些实现在宽松模式下仍会“吐出”字节,却已 悄悄错位。
更安全的做法是:校验填充模式是否合理(= 只应出现在末尾、数量是否符合 n mod 3),并拒绝莫名其妙的尾随垃圾。
用脑内比特账快速记忆
- 每 3 字节 ⇒ 4 个编码字符;
- 多出 1 字节 ⇒ 仍然有 4 个输出字符,
==标记短块; - 多出 2 字节 ⇒ 仍然有 4 个输出字符,单个
=。
记住这张模 3 表,比死记硬背位运算更够用。
换行与 MIME 历史
早期邮件编码会 每隔约 76 个字符插入换行,照顾老式终端宽度。MIME 里也常见这种行为。而一些 API 输出的则是 单行 形式。证书 PEM 又把 Base64主体按固定列宽折行——都有各自理由。
如果你不先去掉 \n、\r 再解码,严格解码器会报错;另一些解析器会自动 跳过所有空白。部署时要明确:你的运行时属于哪一类,并在边界上写清楚契约。
概念性示意
例如只编码一个 ASCII 字母 a(0x61),标准编码会给出 长度为 4 的输出并以 == 收尾——用任一语言内置方法验证一次,印象会更牢。
不要手工改填充来“精简 URL”;若需要 =-less 的表达,应在协议里改用 明确的 URL-safe 变体或无填充规范,而不是默默改动标准 Base64字符串。
在安全相关代码里注意长度
比较 Base64 形式的签名、令牌载荷或密文封装时,长度异常往往暴露:
- 传输截断;
- 二次编码(把已是 Base64 的字符串再次编码);
- 查询串对
+//、=的误处理。
把它们当作 必须经过严格校验的字符串类型,而不要当成“反正是文本就随便 concat”。
要点小结
填充的根源是:编码器坚持用 每组 4 个输出符号 对齐六位分组,哪怕真实输入在最后一块不足 24 bit。在生产集成里:该带 = 就别乱删,并在解码前统一 空白/折行规范化策略——以文档与测试为准,而不是各自猜测。