Python:base64 解码时忽略“不正确的填充”错误
- 2025-02-10 08:57:00
- admin 原创
- 63
问题描述:
我有一些 base64 编码的数据,即使其中有填充错误,我也想将其转换回二进制。如果我使用
base64.decodestring(b64_string)
它会引发“填充不正确”错误。还有其他方法吗?
更新:感谢大家的反馈。说实话,上面提到的所有方法听起来都不太好,所以我决定试试 openssl。以下命令很有效:
openssl enc -d -base64 -in b64string -out binary_data
解决方案 1:
看来您只需要在解码之前向字节添加填充即可。关于这个问题还有许多其他答案,但我想指出的是(至少在 Python 3.x 中)base64.b64decode
将截断任何额外的填充,前提是首先有足够的填充。
因此,类似于:的效果与(一样)b'abc='
一样好。b'abc=='
`b'abc====='`
这意味着您只需添加所需的最大填充字符数(即两个(b'=='
)),base64 将截断任何不必要的字符。
这样你就可以写:
base64.b64decode(s + b'==')
这比以下更简单:
base64.b64decode(s + b'=' * (-len(s) % 4))
请注意,如果字符串s
已经有一些填充(例如),则此方法仅在关键字参数设置为(默认值)b"aGVsbG8="
时才有效。如果是,则当总填充长度超过两个字符时,将导致引发。validate
`Falsevalidate
True`binascii.Error
来自文档:
如果validate为
False
(默认值),则在填充检查之前会丢弃既不属于正常 base-64 字母表也不属于替代字母表的字符。如果validate为True
,则输入中的这些非字母表字符将导致出现binascii.Error
。
但是,如果validate
是False
(或默认留空),您可以随意添加两个填充字符而不会出现任何问题。感谢 eel ghEEz 在评论中指出了这一点。
解决方案 2:
正如其他回复所说,base64 数据可能以多种方式被破坏。
但是,正如Wikipedia所说,删除填充(base64 编码数据末尾的 '=' 字符)是“无损的”:
从理论角度来看,填充字符是不需要的,因为可以根据 Base64 数字的数量计算出缺失的字节数。
因此,如果这确实是您的 base64 数据的唯一“错误”,则可以将填充重新添加。我想出了这个方法,以便能够解析 WeasyPrint 中的“数据”URL,其中一些是没有填充的 base64:
import base64
import re
def decode_base64(data, altchars=b'+/'):
"""Decode base64, padding being optional.
:param data: Base64 data as an ASCII byte string
:returns: The decoded byte string.
"""
data = re.sub(rb'[^a-zA-Z0-9%s]+' % altchars, b'', data) # normalize
missing_padding = len(data) % 4
if missing_padding:
data += b'='* (4 - missing_padding)
return base64.b64decode(data, altchars)
该函数的测试:weasyprint/tests/test_css.py#L68
解决方案 3:
只需根据需要添加填充即可。不过,请注意 Michael 的警告。
b64_string += "=" * ((4 - len(b64_string) % 4) % 4) #ugh
解决方案 4:
使用
string += '=' * (-len(string) % 4) # restore stripped '='s
感谢这里某处的评论。
>>> import base64
>>> enc = base64.b64encode('1')
>>> enc
>>> 'MQ=='
>>> base64.b64decode(enc)
>>> '1'
>>> enc = enc.rstrip('=')
>>> enc
>>> 'MQ'
>>> base64.b64decode(enc)
...
TypeError: Incorrect padding
>>> base64.b64decode(enc + '=' * (-len(enc) % 4))
>>> '1'
>>>
解决方案 5:
“不正确的填充”不仅可以表示“缺少填充”,还可以表示(信不信由你)“不正确的填充”。
如果建议的“添加填充”方法不起作用,请尝试删除一些尾随字节:
lens = len(strg)
lenx = lens - (lens % 4 if lens % 4 else 4)
try:
result = base64.decodestring(strg[:lenx])
except etc
更新:任何添加填充或从末尾删除可能的坏字节的操作都应该在删除所有空格之后进行,否则长度计算将会被打乱。
如果您能向我们展示您需要恢复的数据的(简短)样本,那将是一个好主意。编辑您的问题并复制/粘贴结果 print repr(sample)
。
更新 2:编码可能以 URL 安全的方式完成。如果是这种情况,您将能够在数据中看到减号和下划线字符,并且您应该能够使用以下方法对其进行解码base64.b64decode(strg, '-_')
如果您在数据中看不到减号和下划线字符,但可以看到加号和斜线字符,则说明您遇到了其他问题,可能需要添加填充或删除杂乱的技巧。
如果您在数据中看不到减号、下划线、加号和斜线,则需要确定两个替代字符;它们将不在 [A-Za-z0-9] 中。然后您需要进行实验,看看它们需要在第二个参数中使用什么顺序base64.b64decode()
更新 3:如果您的数据是“公司机密”:
(a)您应该提前说出来
(b)我们可以探索其他途径来理解问题,这很可能与在编码字母表中用什么字符代替+
和/
有关,或者与其他格式或无关字符有关。
其中一种方法是检查数据中有哪些非“标准”字符,例如
from collections import defaultdict
d = defaultdict(int)
import string
s = set(string.ascii_letters + string.digits)
for c in your_data:
if c not in s:
d[c] += 1
print d
解决方案 6:
如果出现填充错误,则可能意味着您的字符串已损坏;base64 编码的字符串长度应为 4 的倍数。您可以尝试=
自己添加填充字符 ( ) 以使字符串的长度为 4 的倍数,但除非出现问题,否则字符串应该已经是 4 的倍数
解决方案 7:
导致填充错误的原因在于,有时元数据也存在于编码字符串中。如果您的字符串看起来像:“data:image/png;base64,...base 64 stuff....”,那么您需要在解码之前删除第一部分。
假设您有图像 base64 编码的字符串,那么尝试下面的代码片段..
from PIL import Image
from io import BytesIO
from base64 import b64decode
imagestr = 'data:image/png;base64,...base 64 stuff....'
im = Image.open(BytesIO(b64decode(imagestr.split(',')[1])))
im.save("image.png")
解决方案 8:
如果您尝试解码网络图像,则只需使用它即可base64.urlsafe_b64decode(data)
。它会自动处理填充。
解决方案 9:
检查您尝试解码的数据源的文档。您是否可能想使用base64.urlsafe_b64decode(s)
而不是base64.b64decode(s)
?这可能是您看到此错误消息的原因之一。
使用 URL 安全字母表对字符串 s 进行解码,在标准 Base64 字母表中用 - 代替 + 并用 _ 代替 /。
例如各种 Google API 都是这种情况,例如 Google 的身份工具包和 Gmail 有效负载。
解决方案 10:
在我的例子中,Gmail Web API 将电子邮件内容作为 base64 编码字符串返回,但不是用标准 base64 字符/字母表编码,而是用 base64 的“网络安全”字符/字母表变体编码。+
和/
字符被替换为-
和_
。对于 python 3,请使用base64.urlsafe_b64decode()
。
解决方案 11:
这可以在一行中完成 - 无需添加临时变量:
b64decode(f"{s}{'=' * (4 - len(s) % 4)}")
解决方案 12:
添加填充相当……麻烦。这是我在本帖评论以及 base64 的 wiki 页面的帮助下编写的函数(它非常有用)https://en.wikipedia.org/wiki/Base64#Padding。
import logging
import base64
def base64_decode(s):
"""Add missing padding to string and return the decoded base64 string."""
log = logging.getLogger()
s = str(s).strip()
try:
return base64.b64decode(s)
except TypeError:
padding = len(s) % 4
if padding == 1:
log.error("Invalid base64 string: {}".format(s))
return ''
elif padding == 2:
s += b'=='
elif padding == 3:
s += b'='
return base64.b64decode(s)
解决方案 13:
有两种方法可以纠正这里描述的输入数据,或者更具体地说,与OP一致,使Python模块base64的b64decode方法能够将输入数据处理为某些内容而不会引发未捕获的异常:
将 == 附加到输入数据的末尾并调用 base64.b64decode(...)
如果引发异常,那么
通过 try/except 捕获它,
(R?)从输入数据中删除任何 = 字符(注意:这可能没有必要),
将 A== 附加到输入数据(A== 到 P== 都可以),
使用这些 A== 附加的输入数据调用 base64.b64decode(...)
上述第 1 项或第 2 项的结果将产生所需的结果。
注意事项
这不能保证解码的结果就是最初编码的结果,但是它(有时?)能为 OP 提供足够的信息来进行处理:
即使出现损坏,我仍然希望回到二进制文件,因为我仍然可以从 ASN.1 流中获取一些有用的信息”。
请参阅下文我们的已知内容和假设。
总结
从对 base64.b64decode(...) 的一些快速测试来看
它似乎忽略了非 [A-Za-z0-9+/] 字符;这包括忽略 =s,除非它们是解析的四个组中的最后一个字符,在这种情况下 =s 终止解码(a=b=c=d= 给出的结果与 abc= 相同,a==b==c== 给出的结果与 ab== 相同)。
看起来,在 base64.b64decode(...) 终止解码之后,所有附加的字符都会被忽略,例如从 = 作为组中的第四个字符。
如上文多条评论所述,当 [解析到该点的字符数模 4] 值为 0、3 或 2 时,输入数据末尾需要填充零个、一个或两个 =。因此,根据上文第 3 项和第 4 项,在输入数据后附加两个或更多个 = 可纠正这些情况下的任何 [不正确的填充] 问题。
但是,解码无法处理 [模 4 的解析字符总数] 为 1 的情况,因为需要至少两个编码字符来表示一组三个解码字节中的第一个解码字节。在未损坏的编码输入数据中,这种 [N 模 4]=1 的情况从未发生过,但正如 OP 所述,字符可能缺失,因此它可能在这里发生。这就是为什么简单地附加 =s 并不总是有效,以及为什么附加A == 会有效而附加 == 无效的原因。NB 使用 [A] 几乎是任意的:它只将清除(零)位添加到解码中,这可能是正确的,也可能不是正确的,但这里的目标不是正确性,而是通过 base64.b64decode(...) 完成,无例外。
我们从原帖以及随后的评论中了解到
怀疑Base64编码的输入数据中缺少数据(字符)
Base64 编码使用标准的 64 位值加填充:AZ;az;0-9;+;/;= 为填充。事实证明,或至少表明,这是
openssl enc ...
可行的。
假设
输入数据仅包含7位ASCII数据
唯一的损坏是缺少编码的输入数据
OP 不关心此后任何时间点的解码输出数据是否与任何丢失的编码输入数据相对应
Github
下面是实现该解决方案的包装器:
https://github.com/drbitboy/missing_b64
解决方案 14:
我在没有使用 base64 的情况下遇到了此错误。所以我的解决方案是错误发生在localhost上,它在127.0.0.1上运行正常
解决方案 15:
如果此错误来自 Web 服务器:请尝试对您的帖子值进行 URL 编码。我通过“curl”发送 POST 请求,发现我没有对我的 base64 值进行 URL 编码,因此“+”等字符没有被转义,因此 Web 服务器 URL 解码逻辑自动运行 URL 解码并将 + 转换为空格。
“+” 是有效的 base64 字符,也许是唯一一个因意外的 url 解码而损坏的字符。
解决方案 16:
就我而言,我在解析电子邮件时遇到了该错误。我以 base64 字符串的形式获取附件,并通过 re.search 提取它。最后在末尾出现了一个奇怪的附加子字符串。
dHJhaWxlcgo8PCAvU2l6ZSAxNSAvUm9vdCAxIDAgUiAvSW5mbyAyIDAgUgovSUQgWyhcMDAyXDMz
MHtPcFwyNTZbezU/VzheXDM0MXFcMzExKShcMDAyXDMzMHtPcFwyNTZbezU/VzheXDM0MXFcMzEx
KV0KPj4Kc3RhcnR4cmVmCjY3MDEKJSVFT0YK
--_=ic0008m4wtZ4TqBFd+sXC8--
当我删除--_=ic0008m4wtZ4TqBFd+sXC8--
并剥离字符串时,解析就被修复了。
因此我的建议是确保您正在解码正确的 base64 字符串。
解决方案 17:
你应该使用
base64.b64decode(b64_string, ' /')
默认情况下,替代字符为'+/'
。
解决方案 18:
我也遇到了这个问题,但什么都没用。我终于找到了适合我的解决方案。我已将内容压缩为 base64 格式,但这种情况发生在百万条记录中的 1 条上……
这是 Simon Sapin 建议的解决方案的一个版本。
如果填充缺少 3 个,那么我会删除最后 3 个字符。
而不是“0gA1RD5L/9AUGtH9MzAwAAA=="
我们得到“0gA1RD5L/9AUGtH9MzAwAA”
missing_padding = len(data) % 4
if missing_padding == 3:
data = data[0:-3]
elif missing_padding != 0:
print ("Missing padding : " + str(missing_padding))
data += '=' * (4 - missing_padding)
data_decoded = base64.b64decode(data)
根据此答案,Trailing As in base64 的原因是 null。但我仍然不知道编码器为什么会搞砸这一切……
解决方案 19:
def base64_decode(data: str) -> str:
data = data.encode("ascii")
rem = len(data) % 4
if rem > 0:
data += b"=" * (4 - rem)
return base64.urlsafe_b64decode(data).decode('utf-8')
解决方案 20:
在尝试解码目标字符串值之前,只需添加“=”等其他字符并使其成为 4 的倍数即可。例如;
if len(value) % 4 != 0: #check if multiple of 4
while len(value) % 4 != 0:
value = value + "="
req_str = base64.b64decode(value)
else:
req_str = base64.b64decode(value)
解决方案 21:
我在尝试解码来自 flask 应用的 base64 编码图像时遇到了同样的问题。问题是,当尝试解码通过 POST 方法发送的字符串时,该字符串将被括在 b'' 中。例如 b'_9j_4AAQS' 需要将其删除才能解码。
#encoding code:
@bp.route("/someroute/", methods=('GET', 'POST'))
def someroute():
fileb64 = base64.urlsafe_b64encode(file.read())
return render_template('template.html', fileb64=fileb64)
#HTML form
<form method="post" action="{{url_for('main.myroute')}}" class="inline">
<input type="hidden" name="file" value="{{fileb64}}">
<button type="submit" name="submit_param" value="submit_value" class="link-button">
Download image in the correct format.
</button>
</form>
#decoding:
@bp.route('/myroute/', methods=('GET', 'POST'))
def myroute():
fileb64 = request.form['file']
file = base64.urlsafe_b64decode(fileb64[2:-1])
解决方案 22:
清除浏览器 cookie 并再次检查,它应该可以正常工作。
解决方案 23:
就我而言,我遇到了这个错误,在删除特定项目的 venv 后,它显示每个字段的错误,所以我尝试更改浏览器(Chrome 到 Edge),实际上它有效..