报错信息速查
| 报错信息 | 出现在哪里 | 最可能的原因 |
|---|---|---|
binascii.Error: Incorrect padding | Python base64.b64decode | 末尾缺少 =,通常因为是 Base64URL 或字符串被截断 |
binascii.Error: Invalid base64-encoded string: number of data characters (5) cannot be 1 more than a multiple of 4 | Python | 输入被截断,或 -/_ 被悄悄丢弃 |
binascii.Error: Only base64 data is allowed | Python,使用 validate=True 时 | 输入中有换行符、空格、- 或 _ |
InvalidCharacterError: Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded. | Chrome、Edge | Base64URL 字符、杂字符、长度错误 |
InvalidCharacterError: String contains an invalid character | Firefox | 同上 |
InvalidCharacterError: Invalid character | Node.js atob | 同上 |
IllegalArgumentException(“Illegal base64 character …”) | Java java.util.Base64 | 解码器与字母表不匹配,或者有换行符 |
FormatException | .NET Convert.FromBase64String | Base64URL 字符或缺少填充 |
base64: invalid input | GNU coreutils base64 -d | 字母表之外的字符 |
解决解码器报错
Python 报 Incorrect padding
Base64 输出的长度总是 4 的倍数,末尾用 = 填充。Python 的解码器要求必须有填充:
>>> import base64
>>> base64.b64decode("SGVsbG8")
Traceback (most recent call last):
...
binascii.Error: Incorrect padding
>>> base64.b64decode("SGVsbG8=")
b'Hello'
缺少填充通常意味着字符串是 Base64URL(JWT、URL 令牌和许多 API 会去掉 =),或者被什么东西截掉了。把填充补回去:
s = "SGVsbG8"
s += "=" * (-len(s) % 4)
如果长度比 4 的倍数多 1,补填充也无济于事。这时 Python 会报 number of data characters (5) cannot be 1 more than a multiple of 4。多出的单个字符只承载 6 位,所以有一部分数据丢失了。即使字符串没有被截断,也可能遇到这条信息:不加 validate=True 时,Python 会丢弃字母表之外的字符,包括 - 和 _。base64.b64decode("SGk_Pg==") 丢掉 _ 之后,只剩 5 个数据字符。对于 Base64URL 输入,请使用 base64.urlsafe_b64decode。
Invalid character 与 atob 失败
各浏览器实现 atob 的算法相同,但报错措辞不同。Chrome 说 “The string to be decoded is not correctly encoded”,Firefox 说 “String contains an invalid character”,Node.js 说 “Invalid character”。遇到以下输入时,三者都会抛出 InvalidCharacterError:
- Base64URL 字符:
atob("SGk_Pg")因为_而失败。请先把-换成+、_换成/。 - 去掉空白后长度为 4n+1,例如
atob("A")。 - 不该出现的字符:从 JSON 中一起复制过来的引号、
data:前缀、URL 编码留下的%2F。
atob 也会宽容一些情况。它会跳过空格和换行符,并且在长度合适时接受缺失的填充,所以 atob("SGVsbG8") 返回 "Hello"。与之对应的另一个错误是 btoa 报 “characters outside of the Latin1 range”,那是 Unicode 文本的问题,见 Uint8Array.toBase64。
一个兼容两种字母表、空白和缺失填充的宽松解码器:
function decodeLoose(s) {
s = s.replace(/\s+/g, "").replace(/-/g, "+").replace(/_/g, "/");
s += "=".repeat((4 - (s.length % 4)) % 4);
return Uint8Array.from(atob(s), (c) => c.charCodeAt(0));
}
new TextDecoder().decode(decodeLoose("SGk_Pg")); // "Hi?>"
等价的 Python 版本:
import base64
def b64decode_any(s: str) -> bytes:
s = "".join(s.split())
s = s.replace("-", "+").replace("_", "/")
s += "=" * (-len(s) % 4)
return base64.b64decode(s, validate=True)
修复 Base64 工具会进行同样的清理,并告诉你它改了什么。
Java 的 Illegal base64 character 与 .NET 的 FormatException
Java 的 java.util.Base64 提供三个解码器,每个都对自己的字母表很严格。错误信息会以十六进制给出出问题的字符:5f 是 _,2d 是 -,2f 是 /,20 是空格,a 是换行符。
| 输入包含 | 应使用 |
|---|---|
+ 和 / | Base64.getDecoder() |
- 和 _ | Base64.getUrlDecoder() |
| 换行符(MIME、PEM 正文) | Base64.getMimeDecoder() |
基本解码器拒绝换行符和空格,但接受缺失的填充,所以 SGVsbG8 可以顺利解码。
.NET 的 Convert.FromBase64String 遇到 Base64URL 字符和缺失的填充时,会抛出 FormatException。它会跳过空格和换行符。调用之前,请按上面的方法修正字母表和填充。完整示例见 Java 中的 Base64 和 C# 中的 Base64。
解码后出现乱码
解码成功了,但文本看起来不对。Base64 存储的是字节,而这些字节是用某种字符编码从文本生成的。用另一种编码读回来,就会得到乱码。
| 你看到的 | 原文 | 发生了什么 |
|---|---|---|
Café | Café | UTF-8 字节被当作 Latin-1 或 Windows-1252 读取 |
Caf� | Café | Windows-1252 字节被当作 UTF-8 读取 |
H e l l o 或 H\0e\0l\0l\0o\0 | Hello | UTF-16LE 字节被当作 UTF-8 读取 |
第一行是经典的 atob 陷阱。atob("Q2Fmw6k=") 返回 "Café",因为 atob 把每个字节映射成一个字符。正确做法是按 UTF-8 解码字节:
const bytes = Uint8Array.from(atob("Q2Fmw6k="), (c) => c.charCodeAt(0));
new TextDecoder().decode(bytes); // "Café"
UTF-16LE 在 Windows 工具中很常见:PowerShell 的 -EncodedCommand 和许多 .NET API 都会生成它。Base64 转文本 可以切换字符集,直到输出读起来正确为止;Base64 转 UTF-16 专门处理宽字符的情况。
输出看起来是二进制数据
如果解码结果满是 �、控制字符,或者夹杂着 PNG、PK、%PDF 之类的片段,说明载荷是一个文件。看看 Base64 开头的几个字符:iVBORw0KGgo 是 PNG,JVBERi0 是 PDF,H4sI 是 gzip。完整列表见 常见的 Base64 前缀。用 Base64 转文件 打开数据,它会识别格式并提供下载。对于 H4sI,请使用 解码 gzip Base64。
解码出的字节与预期不符
没有报错,但字节不对。常见原因有这些:
- 编码时多了一个结尾换行符:
echo "hello" | base64得到aGVsbG8K(含\n),而echo -n "hello" | base64得到aGVsbG8=。请使用printf '%s'或echo -n。 +变成了空格:在 URL 查询字符串和表单正文中,+表示空格。URLSearchParams("t=PGI+Ym9sZDwvYj4=").get("t")返回PGI Ym9sZDwvYj4=。有些解码器会因此报错,有些则会丢掉空格,得到乱码。把 Base64 放进 URL 之前,请用encodeURIComponent进行百分号编码,或者改用 Base64URL。- 残留的百分号编码:
SGk%2FPg%3D%3D必须先经过 URL 解码变成SGk/Pg==,再进行 Base64 解码。 - 从 JSON 中复制来的转义序列:从原始 JSON 中复制字符串,可能会带进一个字面的
\n(两个字符)。Python 的宽松解码器会丢掉反斜杠、保留n,在不报错的情况下返回错误的字节:b64decode("SGVs\\nbG8=")得到b'Hel\x9d\xb1\xbc'。请解析 JSON,而不是从中复制。JSON Base64 解码器 会对每个字段都这样处理。 - 对空白的严格与宽松处理:不加
validate=True的 Python、atob、.NET 和 Java 的 MIME 解码器会跳过换行符。加了validate=True的 Python 和 Java 的基本解码器则会拒绝它们。MIME 和 PEM 数据会在 76 或 64 个字符处换行,所以请选一个能接受换行的解码器。
被编码了两次的数据
解码 U0dWc2JHOGdkMjl5YkdRaA== 得到 SGVsbG8gd29ybGQh,它又是有效的 Base64,解码后是 Hello world!。如果一次解码的结果只由 Base64 字符组成,且长度是 4 的倍数,就要怀疑是重复编码。
这种情况在 Kubernetes Secret 中很常见。data: 下的值是 Base64,而 kubectl create secret --from-literal 会替你编码。如果你自己先编码了一次,Pod 收到的就是 Base64 文本,而不是密码。纯文本值请用 stringData:,或者用 Kubernetes Secret 解码器 查看实际存储的内容。拿不准时,Base64 校验工具 会显示字符串是否有效以及包含多少字节。
常见问题
如何解决 binascii.Error: Incorrect padding?
在末尾追加 =,直到长度是 4 的倍数:s += "=" * (-len(s) % 4)。如果字符串是 Base64URL,还要把 - 换成 +、_ 换成 /,或者直接用 base64.urlsafe_b64decode。
为什么 atob 解码 JWT 会失败?
JWT 使用 Base64URL,其中有 - 和 _。atob 只认识 + 和 /。请先转换这些字符,或者把令牌粘贴到 JWT 解码器 中。
为什么解码出的文本把 é 显示成 é 这样的乱码?
这些字节是 UTF-8,却被当作 Latin-1 或 Windows-1252 读取了。请按 UTF-8 解码字节,例如在 JavaScript 中用 TextDecoder,在 Python 中用 .decode("utf-8")。
长度为 4n+1 的 Base64 字符串还能修复吗?
不能。多出的一个字符只承载 6 位,还不到一个字节,所以至少缺了一个字符。请找到原始来源,或者检查字符串是在哪里被截断的。
怎么判断数据是不是被 Base64 编码了两次?
先解码一次。如果结果仍然是只由 A-Z a-z 0-9 + / = 组成、长度为 4 的倍数的字符串,就再解码一次。