Python: игнорировать ошибку неправильного заполнения при декодировании base64

111

У меня есть данные в кодировке base64, которые я хочу преобразовать обратно в двоичные, даже если в них есть ошибка заполнения. Если я использую

base64.decodestring(b64_string)

это вызывает ошибку "Неправильное заполнение". Есть другой способ?

ОБНОВЛЕНИЕ: Спасибо за отзывы. Честно говоря, все упомянутые методы казались несколько неудачными, поэтому я решил попробовать openssl. Следующая команда сработала:

openssl enc -d -base64 -in b64string -out binary_data
FunLovinCoder
источник
5
Вы действительно пробовали использовать base64.b64decode(strg, '-_')? Это априори, без предоставления каких-либо примеров данных, наиболее вероятное решение вашей проблемы с помощью Python. Предлагаемые "методы" были предложениями ОТЛАДКИ, ОБЯЗАТЕЛЬНО "удачно", учитывая скудность предоставленной информации.
Джон Мачин
2
@John Machin: Да, я ПОПРОБОВАЛ ваш метод, но он не сработал. Данные являются конфиденциальными.
FunLovinCoder 01
3
Попробуйтеbase64.urlsafe_b64decode(s)
Daniel F
Не могли бы вы предоставить результат этого: sorted(list(set(b64_string)))пожалуйста? Не раскрывая ничего конфиденциального для компании, это должно показать, какие символы использовались для кодирования исходных данных, что, в свою очередь, может предоставить достаточно информации, чтобы обеспечить решение без промахов.
Брайан Карчич
Да, я знаю, что это уже решено, но, честно говоря, решение openssl также звучит для меня неудачно.
Брайан Карчич

Ответы:

79

Как сказано в других ответах, данные base64 могут быть повреждены различными способами.

Однако, как говорит Википедия , удаление отступа (символы '=' в конце данных в кодировке base64) "без потерь":

С теоретической точки зрения символ заполнения не нужен, так как количество пропущенных байтов можно вычислить из количества цифр Base64.

Так что, если это единственное, что "не так" с вашими данными base64, можно просто добавить отступы. Я придумал это, чтобы иметь возможность анализировать URL-адреса "данных" в WeasyPrint, некоторые из которых были 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,
2
Чтобы прояснить @ariddell, комментарий base64.decodestringустарел base64.decodebytesв Py3, но для совместимости версий лучше использовать 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?
Майкл Мрозек
Это то, что, кажется, подразумевает статья в Википедии о base64.
badp
1
@bp: В кодировке base64 каждые 24 бита (3 байта) двоичных входных данных кодируются как 4-байтовые выходные данные. output_len% 3 не имеет смысла.
Джон Мачин
8
Просто добавление ===всегда работает. Любые лишние =символы, по-видимому, безопасно отбрасываются Python.
Acumenus
33

Кажется, вам просто нужно добавить отступ к байтам перед декодированием. На этот вопрос есть много других ответов, но я хочу указать, что (по крайней мере, в 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
Хорошо, это не слишком "уродливо", спасибо :) Кстати, я думаю, вам никогда не понадобится больше 2-х символов заполнения. Алгоритм Base64 работает с группами по 3 символа за раз и требует заполнения только тогда, когда ваша последняя группа символов имеет длину всего 1 или 2 символа.
Отто
@Otto заполнение здесь предназначено для декодирования, которое работает с группами по 4 символа. Кодировка Base64 работает с группами по 3 символа :)
Генри Вуди
но если вы знаете, что во время кодирования когда-либо будет добавлено максимум 2, которые могут быть «потеряны» позже, вынуждая вас повторно добавить их перед декодированием, тогда вы знаете, что вам нужно будет добавить только максимум 2 во время декодирования. #ChristmasTimeArgumentForTheFunOfIt
Отто
@ Отто, я считаю, что ты прав. В то время как для строки в кодировке base64 с длиной, например, 5 потребуется 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, '-_')

Если вы не видите в данных символы минуса и подчеркивания, но видите знаки плюса и косой черты, значит, у вас другая проблема, и вам могут понадобиться уловки add-padding или remove-cruft.

Если вы не видите в данных ни минуса, ни подчеркивания, ни плюса, ни косой черты, вам нужно определить два альтернативных символа; это будут те, кого нет в [A-Za-z0-9]. Затем вам нужно будет поэкспериментировать, чтобы увидеть, в каком порядке они должны использоваться во втором аргументеbase64.b64decode()

Обновление 3 : Если ваши данные «конфиденциальны»:
(а) вы должны сообщить об этом заранее
(б) мы можем изучить другие способы понимания проблемы, которая, скорее всего, будет связана с тем, какие символы используются вместо+ и /в кодирующий алфавит или другие символы форматирования или посторонние символы.

Одним из таких способов было бы изучить, какие нестандартные символы присутствуют в ваших данных, например

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. Я почти уверен, что проблема в том, что отсутствует один или несколько символов - отсюда и ошибка заполнения. Если в Python нет надежного решения, я воспользуюсь своим решением вызова openssl.
FunLovinCoder 02
1
«Решение», которое молча игнорирует ошибки, едва ли заслуживает термина «надежное». Как я упоминал ранее, различные предложения Python были методами ОТЛАДКИ, чтобы выяснить, в чем проблема, подготовкой к ПРИНЦИПИАЛЬНОМУ решению ... разве вас не интересует такая вещь?
Джон Мачин
7
Мое требование НЕ состоит в том, чтобы решить проблему, почему base64 поврежден - он исходит из источника, который я не контролирую. Мое требование - предоставить информацию о полученных данных, даже если они повреждены. Один из способов сделать это - получить двоичные данные из поврежденного base64, чтобы я мог почерпнуть информацию из базового ASN.1. поток. Я задал исходный вопрос, потому что хотел получить ответ на этот вопрос, а не на другой вопрос, например, как отлаживать поврежденный base64.
FunLovinCoder 02
Просто нормализуйте строку, удалите все, что не является символом 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)? Это одна из причин, по которой вы могли видеть это сообщение об ошибке.

Расшифруйте строку s, используя безопасный для URL-адресов алфавит, который заменяет - вместо + и _ вместо / в стандартном алфавите Base64.

Это, например, относится к различным API Google, таким как Google Identity Toolkit и полезные нагрузки Gmail.

Дэниел Ф
источник
1
Это вообще не отвечает на вопрос. Плюс urlsafe_b64decodeтакже требует заполнения.
RDB 02
Прежде чем ответить на этот вопрос, у меня возникла проблема, связанная с Google Identity Toolkit. Я получал неправильную ошибку заполнения (я думаю, что она была на сервере), даже если заполнение оказалось правильным. Оказалось, что пришлось пользоваться base64.urlsafe_b64decode.
Daniel F
Я согласен, что он не отвечает на вопрос, rdb, но это было именно то, что мне нужно было услышать. Я перефразировал ответ на более приятный тон, надеюсь, это сработает для вас, Дэниел.
Henrik Heimbuerger
Прекрасно. Я не заметил, что это прозвучало несколько недобро, я только подумал, что это будет самое быстрое решение, если оно решит проблему, и по этой причине его следует попробовать в первую очередь. Спасибо за изменение, приветствуем.
Daniel F
Этот ответ решил мою проблему с декодированием токена доступа Google, полученного из JWT. Все остальные попытки закончились «неправильным заполнением».
Джон Хэнли
2

Добавление отступов довольно ... неудобно. Вот функция, которую я написал с помощью комментариев в этой ветке, а также вики-страницу для base64 (она на удивление полезна) 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, сделать так, чтобы метод b64decode модуля Python base64 мог обрабатывать входные данные во что-то, не вызывая неперехваченного исключения:

  1. Добавьте == в конец входных данных и вызовите base64.b64decode (...)
  2. Если это вызывает исключение, тогда

    я. Поймать через try / except,

    II. (R?) Удалите любые символы = из входных данных (NB, это может быть необязательно),

    iii. Добавьте A == к входным данным (A == - P == будет работать),

    iv. Вызов base64.b64decode (...) с этими A == - добавленными входными данными

Результат из пункта 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 (...) завершает декодирование, например, с знака = как четвертого в группе.

Как отмечалось в нескольких комментариях выше, в конце входных данных требуется либо ноль, либо один, либо два = s заполнения, когда значение [количество проанализированных символов до этой точки по модулю 4] равно 0 или 3, или 2 соответственно. Итак, из пунктов 3. и 4. выше, добавление двух или более = s к входным данным исправит любые проблемы с [неправильным заполнением] в этих случаях.

ОДНАКО, декодирование не может обработать случай, когда [общее количество проанализированных символов по модулю 4] равно 1, потому что требуется как минимум два закодированных символа для представления первого декодированного байта в группе из трех декодированных байтов. В ООН повреждена кодированные входные данные, это [N по модулю 4] = 1 случай никогда не бывает, но как ОП говорится , что символы могут отсутствовать, это может произойти здесь. Вот почему простое добавление = s не всегда будет работать, и почему добавление A == будет работать, а добавление == - нет. NB. Использование [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

В случае, если эта ошибка возникла с веб-сервера: попробуйте кодировать URL-адрес вашего сообщения. Я отправлял POST через "curl" и обнаружил, что не кодировал URL-адрес моего значения base64, поэтому символы вроде "+" не были экранированы, поэтому логика декодирования URL-адреса веб-сервера автоматически запускала url-decode и преобразовывала + в пробелы.

«+» - допустимый символ base64 и, возможно, единственный символ, который искажается из-за неожиданного декодирования URL.

Кертис Яллоп
источник
0

В моем случае я столкнулся с этой ошибкой при разборе электронного письма. Я получил вложение в виде строки base64 и извлек его через re.search. В конце концов, в конце оказалась странная дополнительная подстрока.

dHJhaWxlcgo8PCAvU2l6ZSAxNSAvUm9vdCAxIDAgUiAvSW5mbyAyIDAgUgovSUQgWyhcMDAyXDMz
MHtPcFwyNTZbezU/VzheXDM0MXFcMzExKShcMDAyXDMzMHtPcFwyNTZbezU/VzheXDM0MXFcMzEx
KV0KPj4Kc3RhcnR4cmVmCjY3MDEKJSVFT0YK

--_=ic0008m4wtZ4TqBFd+sXC8--

Когда я удалил --_=ic0008m4wtZ4TqBFd+sXC8-- и разделил строку, синтаксический анализ был исправлен.

Поэтому я советую убедиться, что вы декодируете правильную строку base64.

Даниил Машкин
источник
0

Вы должны использовать

base64.b64decode(b64_string, ' /')

По умолчанию альт-символы '+/'.

Quoc
источник
1
Это не работает в python 3.7. assert len ​​(altchars) == 2, repr (altchars)
Дата TT
0

Я тоже столкнулся с этой проблемой, и ничего не помогло. Наконец-то мне удалось найти решение, которое мне подходит. Я заархивировал содержимое в base64, и это случилось с 1 из миллиона записей ...

Это вариант решения, предложенного Саймоном Сапином.

В случае, если отступы отсутствуют 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 в base64 причина - нули. Но я до сих пор не понимаю, почему кодировщик все испортил ...

Mitzi
источник