Python: 在 base64解码时忽略“错误填充”错误

我有一些数据是 base64编码的,我想转换回二进制,即使有一个填充错误在它。如果我用

base64.decodestring(b64_string)

它会出现一个“错误填充”错误。还有其他方法吗?

更新: 感谢所有的反馈。老实说,所有提到的方法听起来有点打击 所以我决定试试 openssl。下面的命令奏效了:

openssl enc -d -base64 -in b64string -out binary_data
369396 次浏览

如果存在填充错误,则可能意味着您的字符串已损坏; base64编码的字符串应该是四个长度的倍数。您可以尝试自己添加填充字符(=) ,使字符串成为4的倍数,但是它应该已经具备了这个特性,除非出现了什么问题

根据需要添加填充物即可,但要注意迈克尔的警告。

b64_string += "=" * ((4 - len(b64_string) % 4) % 4) #ugh

“不正确的填充”不仅意味着“缺少填充”,而且(信不信由你)“不正确的填充”。

如果建议的“添加填充”方法不起作用,尝试删除一些尾随字节:

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, '-_')对其进行解码

如果您在数据中看不到减号和下划线字符,但是可以看到加号和斜杠字符,那么您还有其他问题,可能需要使用 add-padding 或 delete-cruft 技巧。

如果在数据中看不到减号、下划线、加号和斜杠,那么需要确定两个替代字符; 它们将是[ A-Za-z0-9]中没有的字符。然后,您需要进行实验,看看在 base64.b64decode()的第2个参数中需要使用哪些顺序

更新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

正如在其他响应中所说,有多种方式可以破坏 base64数据。

但是,正如 维基百科所说,删除填充(base64编码数据末尾的’=’字符)是“无损的”:

从理论的角度来看,填充字符是不需要的, 因为缺少的字节数可以从该数字中计算出来 基数64位。

因此,如果这真的是 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/test/test _ css. py # L68

在尝试解码目标字符串值之前,只需添加其他字符,如“ =”或其他任何字符,并使其为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)

检查要解码的数据源的文档。有没有可能你是想用 base64.urlsafe_b64decode(s)而不是 base64.b64decode(s)?这是您可能看到此错误消息的原因之一。

使用 URL 安全的字母表对字符串进行解码,而是使用 在标准 Base64字母表中用 + 和 _ 代替/。

这就是各种 Google API 的例子,比如 Google 的 Identity Toolkit 和 Gmail 有效负载。

使用

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'


>>>

添加填充物是相当... 繁琐的。下面是我在这个线程的注释和 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)

如果这个错误来自 Web 服务器: 请尝试使用 url 编码您的文章值。我通过“ curl”发帖,发现我没有对 base64值进行 url 编码,所以像“ +”这样的字符没有转义,因此 web 服务器的 url-decode 逻辑自动运行 url-decode 并将 + 转换为空格。

“ +”是一个有效的 base64字符,也许是唯一一个被意外的 url-decode 破坏的字符。

在我的例子中,我在解析电子邮件时遇到了这个错误。我得到的附件是 base64字符串,并通过 re.research 提取出来。最后,在末尾出现了一个奇怪的附加子串。

dHJhaWxlcgo8PCAvU2l6ZSAxNSAvUm9vdCAxIDAgUiAvSW5mbyAyIDAgUgovSUQgWyhcMDAyXDMz
MHtPcFwyNTZbezU/VzheXDM0MXFcMzExKShcMDAyXDMzMHtPcFwyNTZbezU/VzheXDM0MXFcMzEx
KV0KPj4Kc3RhcnR4cmVmCjY3MDEKJSVFT0YK


--_=ic0008m4wtZ4TqBFd+sXC8--

当我删除 --_=ic0008m4wtZ4TqBFd+sXC8--并去掉字符串时,解析被修复了。

所以我的建议是确保你解码的是正确的 base64字符串。

你应该用

base64.b64decode(b64_string, ' /')

默认情况下,Altchars 是 '+/'

看起来你只需要在解码之前在你的字节上添加填充。关于这个问题还有很多其他的答案,但是我想指出的是(至少在 Python 3.x 中) base64.b64decode会截断任何额外的补丁,只要首先有足够的补丁。

因此,类似于: b'abc='b'abc=='(以及 b'abc=====')工作得一样好。

这意味着您只需添加所需的最大填充字符数ーー即两个(b'==')ーー base64将截断任何不必要的填充字符。

这样你就可以写:

base64.b64decode(s + b'==')

这比:

base64.b64decode(s + b'=' * (-len(s) % 4))

有两种方法可以纠正这里描述的输入数据,或者,更具体地说,根据 OP,使 Python 模块 base64的 b64decode 方法能够处理输入数据到 什么东西,而不会产生未捕获的异常:

  1. 将 = = 追加到输入数据的末尾并调用 base64.b64decode (...)
  2. 如果这引发了异常,那么

    通过尝试/除了,

    (R?)从输入数据中删除任何 = 字符(注意,这可能没有必要) ,

    附加 A = = 到输入数据(A = = 到 P = = 将工作) ,

    使用那些 A = =-附加的输入数据调用 base64.b64decode (...)

上述第1项或第2项的结果将产生所期望的结果。

警告

这并不能保证解码的结果将是最初编码的结果,但是它将(有时?)给业务计划足够的时间与之合作:

即使有损坏,我还是想回到二进制文件,因为我仍然可以从 ASN.1流中获得一些有用的信息”)。

见下面的 我们所知道的假设

DR

来自 base64.b64decode (...)的一些快速测试

  1. 它似乎忽略了非[ A-Za-z0-9 +/]字符; 这包括忽略 = s 除非,它们是解析后的四个字符组中的最后一个字符,在这种情况下,= s 终止解码(a = b = c = d = 给出了与 abc = 相同的结果,a = = b = = c = = 给出了与 ab = = 相同的结果)。

  2. 所有字符在 base64.b64decode (...)终止解码时附加 被忽略了,例如从 an = 作为组中的第四个字符。

正如上面的一些注释所指出的,当[到该点的已解析字符数为模4]值分别为0、3或2时,在输入数据的末尾需要填充0、1或2个 = s。那么,从第三项开始。四。如上所述,在输入数据后面附加两个或更多的 = s 将纠正这些情况下的任何[不正确的填充]问题。

然而, 解码不能处理[已解析字符模块4的总数]为1的情况,因为它至少需要两个已编码字符来表示一组三个已解码字节中的第一个已解码字节。在 损坏的编码输入数据中,这种[ N 模4] = 1的情况从未发生过,但是由于 OP 声明字符可能丢失,所以它可能发生在这里。这就是为什么简单地附加 = s 并不总是有效的原因,也是为什么附加 A = = 会有效而附加 = = 不会有效的原因。注意: 使用[ A ]几乎是任意的: 它只向解码后的文件添加清除(零)位,这可能是正确的,也可能是不正确的,但是这里的对象不是正确的,而是由 base64.b64decode (...)完成的,没有异常。

我们从 OP 特别是后续注释中了解到的

  • 中缺少数据(字符) Base64编码的输入数据
  • Base64编码使用标准的64位值加上填充: A-Z; A-Z; 0-9; + ;/; = 正在进行填充。这已得到证实,或至少已得到证实 由于 openssl enc ...的工作原理。

假设

  • 输入数据只包含7位 ASCII 数据
  • 唯一的损坏类型是缺少编码的输入数据
  • OP 不关心解码后的输出数据对应于任何丢失的编码输入数据

Github

下面是实现这个解决方案的包装器:

Https://github.com/drbitboy/missing_b64

我也遇到了这个问题,但都没有用。 我终于找到了适合我的解决办法。我在 Base64压缩了内容,这种情况发生在百万分之一的唱片上... ..。

这是 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)

根据这个答案 后面跟着 Base64,原因是空。但我还是不明白为什么编码器会搞砸。

你可以简单地使用 base64.urlsafe_b64decode(data),如果你试图解码一个网络图像。它会自动处理填充物。

导致不正确的填充错误是因为有时候,元数据也存在于编码的字符串中 如果字符串类似于: ‘ data: image/png; base64,... base64 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")

我在没有使用 base64的情况下得到了这个错误。所以我得到了一个解决方案,错误是在 本地主机,它在 127.0.0.1上工作得很好

在我的案例中,我遇到了这个错误,在删除了特定项目的 venv 之后,它显示了每个字段的错误,所以我尝试更改 BROWSER (Chrome to Edge) ,实际上它工作了。.

在我的案例中,Gmail Web API 以 base64编码字符串的形式返回电子邮件内容,而不是以标准的 base64字符/字母表编码,而是以 base64的“ Web 安全”字符/字母表变体进行编码。将 +/字符替换为 -_。对于 python 3,使用 base64.urlsafe_b64decode()

清除您的浏览器 cookie 并重新检查,它应该工作。

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')

这可以在一行中完成——不需要添加临时变量:

b64decode(f"{s}{'=' * (4 - len(s) % 4)}")