第 3 课

填充与长度

末尾 `=` 的含义,以及输入字节与输出长度的关系。

搞懂 六位一组 之后,Base64 的输出长度就很好记。末尾的 =(有时是 ==)不是在“加密”什么,而是在告诉解码器:最后这一组用到的真实输入字节不到三个,需要按规则截断多余的比特。

什么时候会出现填充

看输入长度的 除以 3 的余数即可:

输入字节数 nn % 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 字母 a0x61),标准编码会给出 长度为 4 的输出并以 == 收尾——用任一语言内置方法验证一次,印象会更牢。

不要手工改填充来“精简 URL”;若需要 =-less 的表达,应在协议里改用 明确的 URL-safe 变体或无填充规范,而不是默默改动标准 Base64字符串。

在安全相关代码里注意长度

比较 Base64 形式的签名、令牌载荷或密文封装时,长度异常往往暴露:

  • 传输截断;
  • 二次编码(把已是 Base64 的字符串再次编码);
  • 查询串对 +//= 的误处理。

把它们当作 必须经过严格校验的字符串类型,而不要当成“反正是文本就随便 concat”。

要点小结

填充的根源是:编码器坚持用 每组 4 个输出符号 对齐六位分组,哪怕真实输入在最后一块不足 24 bit。在生产集成里:该带 = 就别乱删,并在解码前统一 空白/折行规范化策略——以文档与测试为准,而不是各自猜测。

想动手练习时,可使用 DevCove 相关工具——可选,不属于本课正文。

打开相关工具

返回课程概览