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

3
Спробуйтеbase64.urlsafe_b64decode(s)
Даніель F

Чи можете ви надати висновок цього: sorted(list(set(b64_string)))будь ласка? Не розкриваючи нічого конфіденційного для компанії, це повинно виявити, які символи використовувались для кодування оригінальних даних, що, в свою чергу, може надати достатньо інформації, щоб забезпечити рішення не потрапляння або пропуску.
Брайан Карчіч

Так, я знаю, що це вже вирішено, але, чесно кажучи, рішення opensl також звучить для мене хіт-або-міс.
Брайан Карчіч

Відповіді:


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 / testing / 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.
Кас

Оскільки base64модуль ігнорує недійсні символи non-base64 у вхідних даних, спочатку потрібно нормалізувати дані. Видаліть все, що не є літерою, цифрою /або +, а потім додайте прокладку.
Martijn Pieters

39

Просто додайте прокладки за потребою. Однак прислухайтеся до попередження Майкла.

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

1
Напевно, є щось простіше, що картографує 0 до 0, 2 до 1 і 1 до 2.
badp

2
Чому ви розширюєтесь до кратного 3 замість 4?
Михайло Мрозек

Саме так, мабуть, випливає із статті вікіпедії на базі64.
badp

1
@bp: У base64, що кодує кожні 24 біти (3 байти), двійковий вхід кодується як вихід 4 байтів. output_len% 3 не має сенсу.
Джон Махін

8
Просто додавання ===завжди працює. Будь-які додаткові =символи, схоже, безпечно відкидаються Python.
Акумен

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
Гаразд, це не надто "некрасиво" дякую :) До речі, я думаю, що вам ніколи не потрібно більше 2-х символів. Алгоритм Base64 працює над групами з 3 символів одночасно і потребує замітки лише тоді, коли остання група символів має лише 1 або 2 символи.
Отто

@Отож прокладка тут призначена для розшифровки, яка працює на групи з 4 символів. Base64 кодує роботу по групах з 3 символів :)
Генрі Woody

але якщо ви знаєте, що під час кодування максимально буде додано 2, які згодом можуть "загубитися", змусивши вас знову додати їх до розшифровки, то ви знаєте, що вам потрібно буде додати лише 2 максимально під час декодування. #ChristmasTimeArgumentForTheFunOfIt
Otto

@Ototo я вважаю, ти маєш рацію. Хоча рядок, кодований 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, '-_')

Якщо у ваших даних ви не бачите символів мінусу та підкреслення, але бачите плюсові та нарізні символи, тоді у вас є інша проблема, і, можливо, знадобляться трюки щодо доповнення та видалення.

Якщо ви не бачите у своїх даних жодного мінусу, підкреслення, плюсу та косої риси, то вам потрібно визначити два альтернативних символи; вони будуть такими, яких немає в [A-Za-z0-9]. Тоді вам потрібно буде експериментувати, щоб побачити, у якому порядку вони повинні бути використані у 2-му аргументі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. Я майже впевнений, що проблема полягає в тому, що бракує 1 або більше символів - отже, помилка заміщення. Якщо не існує надійного рішення в Python, я піду із своїм рішенням виклику openssl.
FunLovinCoder

1
"Рішення", яке мовчки ігнорує помилки, мало заслуговує на термін "надійний". Як я вже згадував, різні пропозиції Python були методами ВІДГОВОРЕННЯ, щоб з'ясувати, у чому полягає проблема, підготовка до НАВЧАЛЬНОГО рішення ... Вас не цікавить таке?
Джон Махін

7
Моя вимога НЕ вирішувати проблему, чому base64 пошкоджена - це походить від джерела, над яким я не маю контролю. Моя вимога - надати інформацію про отримані дані, навіть якщо вони є корумпованими. Один із способів зробити це - вивести бінарні дані з корумпованої бази64, щоб я міг отримати інформацію з базового ASN.1. потік. Я задав оригінальне запитання, тому що хотів відповісти на це питання, а не відповіді на інше питання - наприклад, як налагодити корумповану базу64.
FunLovinCoder

Просто нормалізуйте рядок, видаліть усе, що не є символом Base64. Будь-де, а не просто початок чи кінець.
Martijn Pieters

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-кодовані рядки повинні мати кратну чотири довжини. Ви можете спробувати додати символ padding ( =) самостійно, щоб зробити рядок кратним чотирма, але це вже повинно бути, якщо щось не так


Основними двійковими даними є ASN.1. Навіть із корупцією хочу повернутися до бінарного файлу, тому що я все ще можу отримати корисну інформацію з потоку ASN.1.
FunLovinCoder

неправда, якщо ви хочете розшифрувати jwt для перевірок безпеки, вам це знадобиться
DAG

4

Перевірте документацію джерела даних, яке ви намагаєтесь розшифрувати. Чи можливо, що ви мали намір використовувати base64.urlsafe_b64decode(s)замість base64.b64decode(s)? Ось одна причина, можливо, ви бачили це повідомлення про помилку.

Розшифруйте рядок s, використовуючи захищений URL-адресою алфавіт, який замінює + та _ замість / у стандартному алфавіті Base64.

Наприклад, це стосується різних API API, таких як Google Identity Toolkit та корисні навантаження Gmail.


1
Це зовсім не відповідає на питання. Плюс, urlsafe_b64decodeтакож потрібні прокладки.
rdb

Що ж, перед тим, як відповісти на це питання, у мене виникло питання, яке було пов’язане з інструментарієм Google Identity Toolkit. Я отримував помилкову помилку вкладок (я вважаю, що це було на сервері), навіть жорстке прокладка виявилася правильною. Виявилося, що мені доведеться користуватися base64.urlsafe_b64decode.
Даніель Ф

Я погоджуюсь, що це не відповідає на питання, rdb, але все-таки було саме те, що мені потрібно було почути. Я перефразував відповідь трохи приємнішим тоном, сподіваюся, це спрацює для вас, Даніеле.
Генрік Хаймбургер

Ідеально чудово. Я не помітив, що це звучить дещо недобре, я лише подумав, що це буде найшвидший виправлення, якщо він виправить проблему, і, з цієї причини, слід першим, що потрібно спробувати. Дякуємо за вашу зміну.
Даніель Ф

Ця відповідь вирішила мою проблему розшифровки маркера доступу 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

Існує два способи виправити вхідні дані, описані тут, або, більш конкретно, і відповідно до ОП, зробити так, щоб метод модуля b64decode модуля b64decode модуля Python міг обробляти вхідні дані чимось, не створюючи невловимий виняток:

  1. Додайте == до кінця вхідних даних та зателефонуйте base64.b64decode (...)
  2. Якщо це породжує виняток, то

    i. Ловіть його через спробу / крім,

    ii. (R?) Викресліть будь-які = символи з вхідних даних (NB це може бути не потрібно),

    iii. Додайте A == до вхідних даних (A == через P == буде працювати),

    iv. Виклик base64.b64decode (...) з тими A == - доданими вхідними даними

Результат з пункту 1. або пункту 2. вище дасть бажаний результат.

Коваджі

Це не гарантує, що декодований результат буде тим, що було спочатку закодовано, але це (іноді?) Дає ОП достатньо для роботи з:

Навіть із корупцією я хочу повернутися до бінарного файлу, тому що все ще можу отримати корисну інформацію з потоку 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 == буде працювати, коли додавання == не працює. Примітка Використання [A] - це все, але не довільне: воно додає лише очищені (нульові) біти до розшифрованих, що може бути або не коректним, але тоді об'єкт тут не коректність, а доповнення base64.b64decode (...) не містить винятків .

Що ми знаємо з ОП, і особливо наступні коментарі

  • Існує підозра на відсутність даних (символів) у кодованих Base64 вхідних даних
  • Кодування Base64 використовує стандартні 64 значень місця плюс прокладки: AZ; az; 0-9; +; /; = підкладка. Це підтверджується або, принаймні, підказується тим, що openssl enc ...працює.

Припущення

  • Вхідні дані містять лише 7-бітні дані ASCII
  • Єдиний вид корупції - відсутні кодовані вхідні дані
  • ОП не піклується про декодовані вихідні дані в будь-якій точці, що відповідає будь-яким відсутнім кодованим вхідним даним

Гітуб

Ось обгортка для реалізації цього рішення:

https://github.com/drbitboy/missing_b64


1

Неправильна помилка заміщення викликана тим, що іноді метадані також є у кодованому рядку. Якщо ваш рядок виглядає приблизно так: 'data: image / png; base64, ... base 64 stuff ....', тоді потрібно видалити перший частина перед її розшифровкою.

Скажіть, якщо у вас є закодована рядок image 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, що кодує ваше повідомлення. Я здійснював POSTing через "curl" і виявив, що не кодував url моє значення base64, тому символи типу "+" не уникали, тому логіка розшифровки веб-сервера автоматично виконувала 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. assert len ​​(altchars) == 2, repr (altchars)
Dat 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 Як ​​у base64, причина - нулі. Але я досі не маю уявлення, чому кодер змішує це ...

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.