开发者工具 · Base64 编码器和解码器
在没有 mojibake 的情况下将 Base64 解码为 UTF-8:atob plus TextDecoder
· 工作原理
base64 编码 统一码
atob() 返回伪装成字符的字节,这就是为什么带重音的文本在解码后看起来很损坏。这篇文章展示了从 Base64 到字节到 UTF-8 文本的正确管道,以及如何识别故障模式。
解码为“Café”的 API 响应 — 一个具体的 mojibake 症状以及两个错误字符后面的两个字节
Base64 字符串 Q2Fmw6k= 解码为字节 (67, 97, 102, 195, 169),即 UTF-8 文本咖啡馆。粘贴到朴素解码器中(只需 atob 和字符串转换),输出通常是 Café,每个重音都被两个错误的字符替换。发生这种 mojibake 是因为 atob 返回字节字符串(代码单元 0–255),而不是 UTF-8 文本。字节 195 和 169 对 UTF-8 中的重音 é 进行编码。
将它们视为单独的拉丁语-1 字符会给出 mojibake 模式。正确的管道是 atob(字节作为字符串),然后是 TextDecoder(将字节解释为 UTF-8),然后返回原始文本。 atob功能没有被破坏;它是为二进制数据设计的。它的名字来源于 ASCII-to binary,它产生的二进制字符串是代码单元 0–255 的序列,每个代码单元代表一个字节。
atob() 实际返回的内容 — 一串代码单元 0–255 代表字节,而不是解码后的文本
如果输入 Q2Fmw6k=(标准 Base64),它会输出字符串,其中每个字符都是一个字节:代码单元 67,然后 97,然后 102,然后 195,然后 169。如果直接显示该字符串或解释为 Latin-1 文本,您会看到乱码输出。缺少的步骤是将代码单元转换为字节数组,然后将数组解码为 UTF-8。
charCodeAt 循环恢复字节值:对于 atob 输出中的每个字符,调用 charCodeAt 获取代码单元(编号 0–255),存储在 Uint8Array 中。一旦字节数组存在,就使用字符集 utf-8 传递给 TextDecoder。 TextDecoder 读取字节序列并解释为 UTF-8 文本,将 (195, 169) 等字节序列组合成 é 等单个字符。字节 (67, 97, 102, 195, 169) 变成四字符字符串 Café。这个循环故意很无聊:用 charCodeAt 读取每个返回的代码单元并将其分配给匹配的 Uint8Array 位置。那里不会发生任何字符集决定。当 TextDecoder 接收该数组并应用 UTF-8 并进行致命错误处理时,唯一的解释就会出现。
将该字符串转换为 Uint8Array — charCodeAt 循环以及为什么它是字节复制而不是转换
这两个步骤的过程(字节恢复,然后 UTF-8 解释)是 Base64 编码器和解码器内部执行的操作。 mojibake 模式是此错误的明显迹象。如果 Café 显示为 Café,您将看到 UTF-8 字节的 Latin-1 解释。 é 的 UTF-8 字节为 0xC3 0xA9(十进制 195、169)。在 Latin-1 中,代码单元 195 是 à,代码单元 169 是 ©。
当读取 UTF-8 字节序列时,就好像每个字节都是单独的拉丁语-1 字符一样,每个多字节 UTF-8 序列都会生成错误的替换字符。如果 Café 显示为 Caf 后跟替换字符,还是 Café?或 Caf 加 U+FFFD,您会看到不同的失败:解码器未将字节序列识别为有效的 UTF-8。具体示例:Base64 SGVsbG8sIOS4lueVjCEg8J-Zgg== 解码如下。
TextDecoder 和字符集决策 - 解码为 UTF-8,以及为什么字符集是您必须了解的单独事实
atob 生成带有字节的二进制字符串 (72, 101, 108, 108, 111, 44, 32, 228, 184, 180、149、140、33、32、240、159、152、130)。前六个字节是ASCII:成为Hello,。字节 228、184、180 是表示 CJK 字符的三字节 UTF-8 序列。字节 149、140 是下一个序列的一部分。完整序列包括最终字符的四字节表情符号序列(240、159、152、130)。
当通过 TextDecoder 正确处理时,所有字节组合起来生成原始的混合脚本文本。 UTF-8 字节序列具有可预测的长度:以 0xxxxxxx 开头的字节是单字节 ASCII;以 110xxxxx 开头的字节预计以 10xxxxxx 开头的后续字节(总共两个字节);以 1110xxxx 开头的字节需要两个后续字节(总共三个字节);以 11110xxx 开头的字节需要三个后续字节(总共四个字节)。对于混合的 CJK 和表情符号样本,字节视图尤其具有诊断性,因为 ASCII 直觉不再有帮助。每个可见符号都有几个字节,删除一个字节会将剩余序列移至无效的 UTF-8 中。严格的解码将这种转变转变为命名的故障,而不是看似合理的损坏。
工作示例:解码包含 CJK 和表情符号的 Base64 字符串 — 字节、代码点以及与原始字符串相比的最终字符串
以 1111110x 开头的序列在 UTF-8 中无效(为将来保留,未使用)。以 10xxxxxx 开头的字节绝不能作为前导字节出现;这是延续。如果字节流违反规则,则 UTF-8 无效。具有字符集 utf-8 的 TextDecoder 按这些规则解释数组,并成功获得有效序列。无效则报错。
Base64 编码器和解码器使用严格模式标志为 true 的 TextDecoder。这意味着无效的 UTF-8 会引发错误,而不是默默地插入替换字符 (U+FFFD)。如果 Base64 字符串解码为无效 UTF-8 的字节,严格模式将抛出异常,而不是继续处理乱码文本。这是设计选择:二进制有效负载(图像、密钥、压缩数据)不是文本,不应解码为文本。
识别模式: à、 – 和 � — 如何区分 Base64 问题和字符集问题
如果尝试将 JPEG 解码为 Base64,字节流将不代表有效的 UTF-8,严格解码将拒绝它。工具为此类有效负载提供十六进制视图:您可以看到原始字节,而无需假装它们是文本。识别 UTF-8 解码错误涉及查看上下文中的字节。它们是多字节序列预期的奇数吗?
潜在序列的第一个字节是否无效(以 10xxxxxx 开头)?是否缺少连续字节?字节输出按钮提供了调查中最干净的分叉。如果出现十六进制但文本解码失败,则 Base64 解析成功,并且有效负载是二进制的、损坏的或使用不同的字符集编码的。在出现正确的字节后,更改 Base64 标点符号无法修复字符集不匹配。
这不包括 - UTF-16 有效负载、二进制输出(例如图像)和无效字节处理
模式是一致的。单字节 0xFF 在 UTF-8 中永远无效;它不能是 ASCII 字节(只有 0–127 是 ASCII),也不能是前导字节(前导字节为 0xC0–0xFD,0xFF 保留)。单独代理(UTF-16 概念)不能出现在 UTF-8 中;如果看到字节序列 0xED 0xA0 0x80(以 UTF-8 样式编码代理项 U+D800),则它是无效的 UTF-8。
一种历史解决方法是 btoa(unescape(encodeURIComponent(text)))。 encodeURIComponent将café转换为%C3%A9(百分比编码UTF-8字节),unescape重新打包为代码单元,btoa对代码单元进行编码。这适用于大多数文本,但在单独的代理项周围很脆弱并且难以阅读。现代管道(TextEncoder 到字节,然后是 base64)更加清晰和标准。 TextEncoder 内置于所有现代浏览器和 Node.js 中,做出了正确的选择。 UTF-16、传统单字节编码和任意文件内容需要为这些字节选择解码器或二进制感知查看器。 ToolAcre 故意不在其中进行猜测。猜测可能会将无效序列变成误导性文本,而十六进制转储会保留每个字节以供以后进行明智的解释。
要点:Base64 为您提供字节,UTF-8 为您提供文本 — Base64 编码器和解码器如何执行这两个步骤,以便解码后的文本与输入完全匹配
当您有 Base64 字符串并想要 UTF-8 文本时,完整的步骤是:将 Base64 解码为字节(使用 atob 或 base64 解码库),从字节创建 Uint8Array,使用字符集 utf-8 将数组传递给 TextDecoder,以字符串形式读取结果。
如果输入是二进制数据而不是文本,则跳过 TextDecoder 并直接检查字节。 Base64 编码器和解码器提供十六进制字节视图,保留严格 UTF-8 解码会拒绝的值。该分叉是诊断性的:成功的字节加上失败的文本意味着 Base64 解析有效,而有效负载是二进制的、损坏的或使用该工具无法猜测的字符集编码的。