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


111

我有一些base64编码的数据,即使其中存在填充错误,我也想将其转换回二进制。如果我用

base64.decodestring(b64_string)

会引发“填充错误”错误。还有另一种方法吗?

更新:感谢您的所有反馈。老实说,提到的所有方法听起来都有些失败,所以我决定尝试使用openssl。以下命令可以使您满意:

openssl enc -d -base64 -in b64string -out binary_data

5
您是否真的尝试过使用base64.b64decode(strg, '-_')?这是先验的,无需您费心提供任何示例数据,这是最可能解决您问题的Python解决方案。提出的“方法”是调试建议,由于提供的信息很少,因此必然“碰碰运气”。
约翰·马钦

2
@John Machin:是的,我尝试过您的方法,但是没有用。该数据是公司机密信息。
FunLovinCoder 2010年

3
试试base64.urlsafe_b64decode(s)
Daniel F

您能提供以下输出sorted(list(set(b64_string)))吗?在不透露任何公司机密信息的情况下,应该透露哪些字符用于编码原始数据,而这些字符又可以提供足够的信息以提供非命中或失败的解决方案。
Brian Carcich '19

是的,我知道它已经解决了,但是,老实说,openssl解决方案对我来说还是成败。
布莱恩·卡西奇

Answers:


79

如其他答复中所述,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


2
注意:ASCII不是Unicode,因此为了安全起见,您可能需要str(data)
MarkHu

4
请注意,这很好。不建议使用base64.decodestring,请使用base64.b64_decode
Ariddell 2015年

2
为了澄清@ ariddell ,Py3中base64.decodestring已弃用注释base64.decodebytes,但为了更好地使用版本兼容性base64.b64decode
Cas

由于base64模块确实会忽略输入中的无效非base64字符,因此您首先必须对 数据进行规范化。删除所有不是字母,数字/或的内容+然后添加填充。
马丁·彼得斯

39

只需添加所需的填充。但是,请注意迈克尔的警告。

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

1
有肯定更简单的东西映射0比0,2比1和1比2
badp

2
为什么要扩展到3而不是4的倍数?
Michael Mrozek

那是关于base64的维基百科文章所暗示的。
Badp 2010年

1
@bp:在base64编码中,每24位(3字节)二进制输入被编码为4字节输出。output_len%3没有任何意义。
约翰·马钦

8
只是追加===总是可行的。=Python似乎可以安全地丢弃所有多余的字符。
Acumenus

32

看来您只需要在解码之前在字节中添加填充即可。关于这个问题,还有许多其他答案,但我想指出(至少在Python 3.x中),base64.b64decode它将截断所有多余的填充,前提是首先要有足够的填充。

所以,这样的:b'abc='工作一样好b'abc=='(一样b'abc=====')。

这意味着您只需添加所需的最大填充字符数(三个(b'===')),base64就会截断所有不必要的填充字符。

这使您可以编写:

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

比以下方法简单:

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

1
好吧,这并不是太“丑陋”,谢谢:)顺便说一句,我认为您不需要两个以上的填充字符。Base64算法一次可处理3个字符的组,仅当最后一组字符的长度仅为1或2个字符时才需要填充。
奥托

@Otto此处的填充用于解码,它适用于4个字符的组。Base64 编码可用于3个字符的组:)
Henry Woody

但是,如果您知道在编码过程中最多会添加2,之后可能会丢失,从而迫使您在解码之前重新添加它们,那么您知道在解码过程中也只需要最多添加2。#ChristmasTimeArgumentForTheFunOfIt
奥托(Otto)

@奥托我相信你是对的。例如,长度为5的base64编码的字符串需要3个填充字符,而长度为5的字符串甚至不是base64编码的字符串的有效长度。您会收到错误:binascii.Error: Invalid base64-encoded string: number of data characters (5) cannot be 1 more than a multiple of 4。感谢您指出了这一点!
亨利·伍迪

24

“不正确的填充”不仅可以表示“缺少填充”,还可以表示(不信不信)“不正确的填充”。

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

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]中没有的人。然后,您需要进行实验,以查看需要在第2个参数中使用它们的顺序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

数据由标准的base64字符集组成。我很确定问题是因为缺少1个或多个字符-因此出现了填充错误。除非Python中有一个健壮的解决方案,否则我将使用调用openssl的解决方案。
FunLovinCoder 2010年

1
默默忽略错误的“解决方案”几乎不值得使用“健壮”一词。正如我前面提到的,各种Python建议都是DEBUGGING的方法,可以找出问题所在,为PRINCIPLED解决方案做准备……您对这样的事情不感兴趣吗?
John Machin 2010年

7
我的要求不是解决为什么base64损坏的问题-它来自我无法控制的来源。我的要求是即使收到损坏的数据也要提供有关接收到的数据的信息。一种方法是从损坏的base64中获取二进制数据,以便从底层ASN.1中收集信息。流。我问原始问题是因为我想要一个问题的答案,而不是另一个问题的答案-例如如何调试损坏的base64。
FunLovinCoder

只需对字符串进行规范化,删除所有非Base64字符。任何地方,而不仅仅是开始或结束。
马丁·彼得斯

24

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'

>>> 

4
他的意思是这样的评论:stackoverflow.com/questions/2941995/…–
jackyalcine

22

如果存在填充错误,则可能意味着您的字符串已损坏;base64编码的字符串应具有四个长度的倍数。您可以尝试=自己添加填充字符(),以使字符串为四的倍数,但除非有错误,否则应该已经有该字符了


基础二进制数据是ASN.1。即使发生损坏,我也想回到二进制文件,因为我仍然可以从ASN.1流中获取一些有用的信息。
FunLovinCoder

不正确,如果您想解码jwt以进行安全检查,则将需要它
DAG

4

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

使用URL安全字母对字符串s进行解码,该字母在标准Base64字母中用-代替+,用_代替/。

例如,各种Google API(例如Google的身份工具包和Gmail负载)就是这种情况。


1
这根本无法回答问题。另外,urlsafe_b64decode还需要填充。
rdb

嗯,在回答这个问题之前我遇到了一个问题,该问题与Google的Identity Toolkit有关。我遇到了错误的填充错误(我相信它在服务器上),即使填充看起来正确也是如此。原来我不得不用base64.urlsafe_b64decode
Daniel

我同意它不能回答问题,rdb,但这正是我也需要听到的。我将答案改成更好的语气,但愿Daniel对您有用。
Henrik Heimbuerger

很好。我没有注意到它听起来有些不友好,我只是认为如果它能够解决问题,那将是最快的解决方法,因此,它应该是首先要尝试的方法。感谢您的更改,这是值得欢迎的。
丹尼尔·F

这个答案解决了我解码来自JWT的Google Access令牌的问题。其他所有尝试均导致“填充错误”。
约翰·汉利

2

很容易地添加填充。这是我借助该线程中的注释以及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)

2

base64.urlsafe_b64decode(data)如果您要解码网络图像,则可以简单地使用。它将自动处理填充。


真的有帮助!
月亮

1

有两种方法可以更正此处描述的输入数据,或更确切地说,与OP保持一致,以使Python模块base64的b64decode方法能够将输入数据处理为某种内容而不会引发未捕获的异常:

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

    一世。通过try / except捕获它,

    ii。(R?)从输入数据中去除=字符(注意,可能没有必要),

    iii。将A ==附加到输入数据(A ==至P ==将起作用),

    iv。使用这些A ==附加的输入数据调用base64.b64decode(...)

上面第1项或第2项的结果将产生所需的结果。

注意事项

这不能保证解码后的结果将是原始编码的结果,但是(有时?)它会给OP提供足够的处理能力:

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

请参阅下面的“我们知道的信息”和“ 假设”

TL; DR

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

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

  2. 看来在base64.b64decode(...)终止解码之后(例如,从= =作为组中的第四个字符),所有附加字符都将被忽略

如上面的几条评论所述,当[解析到该点的字符数为4的值]的值为0或3时,在输入数据的末尾需要填充为零或一或两个。或2。因此,从上述第3项和第4项开始,在输入数据中附加两个或多个=可以纠正这些情况下的任何[Invalid padding]问题。

但是, [解析的字符的模数总数为4]为1时解码无法处理,因为它需要至少两个编码字符来表示三个解码字节组中的第一个解码字节。在损坏的编码输入数据中,永远不会发生这种[N模4] = 1情况,但是由于OP指出字符可能会丢失,因此可能会在这里发生。这就是为什么仅附加= s并不总是有效的原因,以及为什么附加A在附加==时不能 ==的。注意使用[A]几乎是任意的:它仅将已清除的(零)位添加到解码后的位,这可能是正确的,也可能是不正确的,但是此时的对象不是正确的,而是由base64.b64decode(...)完成的,但没有例外。 。

我们从OP中了解到的信息尤其是后续评论是

  • 怀疑在Base64编码的输入数据中缺少数据(字符)
  • Base64编码使用标准的64位值加上填充:AZ;az; 0-9; +; /; =是填充。事实证明或至少建议这样openssl enc ...做。

假设条件

  • 输入数据仅包含7位ASCII数据
  • 唯一的损坏是缺少编码的输入数据
  • 在对应于任何丢失的编码输入数据的那一点之后,OP不在乎解码输出数据

Github

这是实现此解决方案的包装器:

https://github.com/drbitboy/missing_b64


1

造成错误的填充错误是因为有时编码的字符串中也存在元数据。如果您的字符串看起来像:“ 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")

0

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

0

如果此错误来自Web服务器:请尝试对您的帖子值进行url编码。我是通过“ curl”发布的,发现我没有对base64值进行url编码,因此像“ +”这样的字符没有被转义,因此Web服务器的url解码逻辑会自动运行url解码并将+转换为空格。

“ +”是有效的base64字符,也许是唯一被意外的URL解码破坏的字符。


0

就我而言,我在解析电子邮件时遇到了该错误。我将附件作为base64字符串获取,并通过re.search将其提取。最终在末尾有一个奇怪的附加子字符串。

dHJhaWxlcgo8PCAvU2l6ZSAxNSAvUm9vdCAxIDAgUiAvSW5mbyAyIDAgUgovSUQgWyhcMDAyXDMz
MHtPcFwyNTZbezU/VzheXDM0MXFcMzExKShcMDAyXDMzMHtPcFwyNTZbezU/VzheXDM0MXFcMzEx
KV0KPj4Kc3RhcnR4cmVmCjY3MDEKJSVFT0YK

--_=ic0008m4wtZ4TqBFd+sXC8--

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

因此,我的建议是确保您正在解码正确的base64字符串。


0

你应该用

base64.b64decode(b64_string, ' /')

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


1
那在python 3.7中不起作用。断言len(altchars)== 2,repr(altchars)
Dat TT

0

我也遇到了这个问题,没有任何效果。我终于设法找到了适合我的解决方案。我在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中结尾为As,原因为空。但是我仍然不知道为什么编码器会搞砸这个...

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.