Python: Abaikan kesalahan 'Padding salah' saat decoding base64

111

Saya memiliki beberapa data yang dikodekan base64 yang ingin saya ubah kembali ke biner bahkan jika ada kesalahan padding di dalamnya. Jika saya menggunakan

base64.decodestring(b64_string)

itu memunculkan kesalahan 'Padding salah'. Apakah ada cara lain?

UPDATE: Terima kasih atas semua umpan baliknya. Sejujurnya, semua metode yang disebutkan terdengar sedikit untung-untungan jadi saya memutuskan untuk mencoba openssl. Perintah berikut bekerja dengan baik:

openssl enc -d -base64 -in b64string -out binary_data
FunLovinCoder
sumber
5
Apakah Anda benar-benar MENCOBA menggunakan base64.b64decode(strg, '-_')? Itu apriori, tanpa Anda repot-repot menyediakan data sampel apa pun, solusi Python yang paling mungkin untuk masalah Anda. "Metode" yang diusulkan adalah saran DEBUG, SEHARUSNYA "untung-untungan" mengingat kurangnya informasi yang diberikan.
John Machin
2
@ John Machin: Ya, saya MENCOBA metode Anda tetapi tidak berhasil. Data tersebut bersifat rahasia perusahaan.
FunLovinCoder
3
Cobabase64.urlsafe_b64decode(s)
Daniel F
Bisakah Anda memberikan keluaran dari ini: sorted(list(set(b64_string)))tolong? Tanpa mengungkapkan rahasia perusahaan apa pun, yang harus mengungkapkan karakter mana yang digunakan untuk menyandikan data asli, yang pada gilirannya dapat memberikan informasi yang cukup untuk memberikan solusi non-hit-or-miss.
Brian Carcich
Ya, saya tahu ini sudah terpecahkan, tetapi, sejujurnya, solusi openssl juga terdengar untung-untungan bagi saya.
Brian Carcich

Jawaban:

79

Seperti yang dikatakan dalam tanggapan lain, ada berbagai cara di mana data base64 dapat rusak.

Namun, seperti yang dikatakan Wikipedia , menghapus padding (karakter '=' di akhir data yang dikodekan base64) adalah "lossless":

Dari sudut pandang teoritis, karakter padding tidak diperlukan, karena jumlah byte yang hilang dapat dihitung dari jumlah digit Base64.

Jadi jika ini benar-benar satu-satunya yang "salah" dengan data base64 Anda, padding dapat ditambahkan kembali. Saya datang dengan ini untuk dapat mengurai URL "data" di WeasyPrint, beberapa di antaranya adalah base64 tanpa padding:

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)

Tes untuk fungsi ini: weasyprint / tests / test_css.py # L68

Simon Sapin
sumber
2
Catatan: ASCII bukan Unicode, jadi untuk amannya, Anda mungkin inginstr(data)
MarkHu
4
Ini bagus dengan satu peringatan. base64.decodestring tidak digunakan lagi, gunakan base64.b64_decode
ariddell
2
Untuk mengklarifikasi komentar @ariddell base64.decodestringsudah tidak digunakan lagi base64.decodebytesdi Py3 tetapi untuk kompatibilitas versi lebih baik digunakan base64.b64decode.
Cas
Karena base64modul mengabaikan karakter non-base64 yang tidak valid dalam input, Anda harus menormalkan data terlebih dahulu. Hapus apa pun yang bukan huruf, angka /atau +, lalu tambahkan padding.
Martijn Pieters
39

Cukup tambahkan padding sesuai kebutuhan. Namun, perhatikan peringatan Michael.

b64_string += "=" * ((4 - len(b64_string) % 4) % 4) #ugh
badp
sumber
1
Pasti ada sesuatu yang lebih sederhana yang memetakan 0 ke 0, 2 ke 1 dan 1 ke 2.
badp
2
Mengapa Anda memperluas ke kelipatan 3, bukan 4?
Michael Mrozek
Itulah yang tampaknya tersirat dalam artikel wikipedia di base64.
badp
1
@bp: Dalam pengkodean base64, setiap 24 bit (3 byte) input biner dikodekan sebagai output 4 byte. output_len% 3 tidak masuk akal.
John Machin
8
Menambahkan saja ===selalu berhasil. =Karakter tambahan apa pun tampaknya dibuang dengan aman oleh Python.
Acumenus
32

Sepertinya Anda hanya perlu menambahkan padding ke byte Anda sebelum melakukan decoding. Ada banyak jawaban lain untuk pertanyaan ini, tetapi saya ingin menunjukkan bahwa (setidaknya dengan Python 3.x) base64.b64decodeakan memotong padding tambahan, asalkan ada cukup di tempat pertama.

Jadi, sesuatu seperti: b'abc='bekerja sebaik b'abc=='(seperti halnya b'abc=====').

Artinya, Anda dapat menambahkan jumlah maksimum karakter pengisi yang Anda perlukan — yaitu tiga ( b'===') —dan base64 akan memotong karakter pengisi yang tidak diperlukan.

Ini memungkinkan Anda menulis:

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

yang lebih sederhana dari:

base64.b64decode(s + b'=' * (-len(s) % 4))
Henry Woody
sumber
1
Oke, itu tidak terlalu "jelek" terima kasih :) Menurut saya, Anda tidak perlu lebih dari 2 karakter padding. Algoritme Base64 bekerja pada grup yang terdiri dari 3 karakter sekaligus dan hanya membutuhkan bantalan saat grup karakter terakhir Anda hanya berukuran 1 atau 2 karakter.
Otto
@Otto padding di sini adalah untuk decoding, yang bekerja pada grup yang terdiri dari 4 karakter. Pengkodean Base64 bekerja pada grup yang terdiri dari 3 karakter :)
Henry Woody
tetapi jika Anda tahu bahwa selama encoding maksimal 2 akan pernah ditambahkan, yang mungkin menjadi "hilang" nanti, memaksa Anda untuk menambahkannya kembali sebelum decoding, maka Anda tahu bahwa Anda hanya perlu menambahkan maksimal 2 selama decoding juga. #ChristmasTimeArgumentForTheFunOfIt
Otto
@Otto Saya yakin Anda benar. Sementara string berenkode base64 dengan panjang, misalnya, 5 akan memerlukan 3 karakter pengisi, string dengan panjang 5 bahkan bukan panjang yang valid untuk string berenkode base64. Anda akan mendapatkan error: binascii.Error: Invalid base64-encoded string: number of data characters (5) cannot be 1 more than a multiple of 4. Terima kasih telah menunjukkan hal ini!
Henry Woody
24

"Padding yang salah" tidak hanya berarti "padding yang hilang" tetapi juga (percaya atau tidak) "padding yang salah".

Jika metode "menambahkan padding" yang disarankan tidak berhasil, coba hapus beberapa byte tambahan:

lens = len(strg)
lenx = lens - (lens % 4 if lens % 4 else 4)
try:
    result = base64.decodestring(strg[:lenx])
except etc

Pembaruan: Setiap mengutak-atik menambahkan padding atau menghapus byte yang mungkin buruk dari akhir harus dilakukan SETELAH menghapus spasi, jika tidak perhitungan panjang akan mengecewakan.

Ada baiknya jika Anda menunjukkan contoh (singkat) data yang perlu Anda pulihkan. Edit pertanyaan Anda dan salin / tempel hasil dari print repr(sample) .

Pembaruan 2: Ada kemungkinan bahwa pengkodean telah dilakukan dengan cara yang aman untuk url. Jika demikian, Anda akan dapat melihat karakter minus dan garis bawah dalam data Anda, dan Anda harus dapat memecahkan kode dengan menggunakanbase64.b64decode(strg, '-_')

Jika Anda tidak dapat melihat karakter minus dan garis bawah dalam data Anda, tetapi dapat melihat karakter plus dan slash, maka Anda memiliki masalah lain, dan mungkin memerlukan trik add-padding atau remove-cruft.

Jika Anda tidak dapat melihat minus, garis bawah, plus dan garis miring di data Anda, maka Anda perlu menentukan dua karakter alternatif; mereka akan menjadi orang-orang yang tidak ada di [A-Za-z0-9]. Kemudian Anda harus bereksperimen untuk melihat urutan mana yang harus digunakan dalam argumen keduabase64.b64decode()

Pembaruan 3 : Jika data Anda adalah "rahasia perusahaan":
(a) Anda harus mengatakannya di awal
(b) kami dapat mencari cara lain untuk memahami masalah, yang kemungkinan besar terkait dengan karakter apa yang digunakan sebagai pengganti +dan /dalam alfabet pengkodean, atau dengan format lain atau karakter asing.

Salah satu cara tersebut adalah dengan memeriksa apa karakter non- "standar" dalam data Anda, mis

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
John Machin
sumber
Data terdiri dari kumpulan karakter base64 standar. Saya cukup yakin masalahnya adalah karena 1 atau lebih karakter hilang - karena itu terjadi kesalahan padding. Kecuali, ada solusi yang kuat dengan Python, saya akan menggunakan solusi saya untuk memanggil openssl.
FunLovinCoder
1
Sebuah "solusi" yang diam-diam mengabaikan kesalahan hampir tidak layak disebut "kuat". Seperti yang saya sebutkan sebelumnya, berbagai saran Python adalah metode DEBUGGING untuk mencari tahu apa masalahnya, persiapan untuk solusi PRINSIP ... apakah Anda tidak tertarik dengan hal seperti itu?
John Machin
7
Persyaratan saya BUKAN untuk menyelesaikan masalah mengapa base64 rusak - itu berasal dari sumber yang tidak saya kendalikan. Persyaratan saya adalah memberikan informasi tentang data yang diterima meskipun rusak. Salah satu cara untuk melakukannya adalah dengan mengeluarkan data biner dari base64 yang rusak sehingga saya dapat mengumpulkan informasi dari ASN.1 yang mendasarinya. aliran. Saya menanyakan pertanyaan asli karena saya ingin jawaban untuk pertanyaan itu bukan jawaban untuk pertanyaan lain - seperti bagaimana men-debug base64 yang korup.
FunLovinCoder
Cukup normalkan string, hapus apa pun yang bukan karakter Base64. Dimanapun, bukan hanya awal atau akhir.
Martijn Pieters
24

Menggunakan

string += '=' * (-len(string) % 4)  # restore stripped '='s

Penghargaan diberikan untuk komentar di suatu tempat di sini.

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

>>> 
warvariuc
sumber
4
Dia bermaksud komentar ini: stackoverflow.com/questions/2941995/…
jackyalcine
22

Jika ada kesalahan padding, itu mungkin berarti string Anda rusak; string berenkode base64 harus memiliki kelipatan empat panjang. Anda dapat mencoba menambahkan karakter pengisi ( =) sendiri untuk membuat string menjadi kelipatan empat, tetapi seharusnya sudah memilikinya kecuali ada yang salah

Michael Mrozek
sumber
Data biner yang mendasarinya adalah ASN.1. Bahkan dengan korupsi saya ingin kembali ke biner karena saya masih bisa mendapatkan beberapa info berguna dari aliran ASN.1.
FunLovinCoder
tidak benar, jika Anda ingin memecahkan kode jwt untuk pemeriksaan keamanan, Anda akan membutuhkannya
DAG
4

Periksa dokumentasi sumber data yang Anda coba dekode. Apakah mungkin bahwa Anda bermaksud menggunakannya base64.urlsafe_b64decode(s)bukan base64.b64decode(s)? Itulah salah satu alasan Anda mungkin melihat pesan kesalahan ini.

Dekode string s menggunakan alfabet aman URL, yang menggantikan - alih-alih + dan _ alih-alih / dalam alfabet Base64 standar.

Ini adalah contoh kasus untuk berbagai Google API, seperti Google Identity Toolkit dan payload Gmail.

Daniel F
sumber
1
Ini sama sekali tidak menjawab pertanyaan itu. Plus, urlsafe_b64decodejuga membutuhkan bantalan.
rdb
Nah, ada masalah yang saya alami sebelum menjawab pertanyaan ini, yang terkait dengan Perangkat Identitas Google. Saya mendapatkan kesalahan padding yang salah (saya yakin itu ada di server) bahkan padding itu tampaknya benar. Ternyata saya harus menggunakan base64.urlsafe_b64decode.
Daniel F
Saya setuju bahwa itu tidak menjawab pertanyaannya, rdb, namun itu persis seperti yang perlu saya dengar juga. Saya mengubah jawabannya menjadi nada yang sedikit lebih bagus, saya harap ini berhasil untuk Anda, Daniel.
Henrik Heimbuerger
Sangat baik. Saya tidak memperhatikan bahwa itu terdengar agak tidak baik, saya hanya berpikir bahwa ini akan menjadi perbaikan tercepat jika itu akan memperbaiki masalah, dan, karena alasan itu, harus menjadi hal pertama yang dicoba. Terima kasih atas kembalian Anda, selamat datang.
Daniel F
Jawaban ini memecahkan masalah saya mendekode Token Akses Google yang berasal dari JWT. Semua upaya lainnya menghasilkan "Padding salah".
John Hanley
2

Menambahkan padding agak ... fiddly. Inilah fungsi yang saya tulis dengan bantuan komentar di utas ini serta halaman wiki untuk base64 (sangat membantu) 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)
Bryan Lott
sumber
2

Anda cukup menggunakan base64.urlsafe_b64decode(data)jika Anda mencoba memecahkan kode gambar web. Ini secara otomatis akan mengurus bantalan.

VINEE
sumber
itu sangat membantu!
Bulan
1

Ada dua cara untuk mengoreksi data input yang dijelaskan di sini, atau, lebih spesifik dan sejalan dengan OP, untuk membuat metode b64decode modul Python dapat memproses data input menjadi sesuatu tanpa memunculkan pengecualian yang tidak tertangkap:

  1. Tambahkan == ke akhir data masukan dan panggil base64.b64decode (...)
  2. Jika itu menimbulkan pengecualian, maka

    saya. Tangkap melalui coba / kecuali,

    ii. (R?) Hapus semua karakter = dari data input (NB ini mungkin tidak diperlukan),

    aku aku aku. Tambahkan A == ke data masukan (A == melalui P == akan bekerja),

    iv. Panggil base64.b64decode (...) dengan A == - data masukan yang ditambahkan

Hasil dari Item 1. atau Item 2. di atas akan memberikan hasil yang diinginkan.

Peringatan

Ini tidak menjamin hasil yang didekodekan akan seperti yang semula dikodekan, tetapi (terkadang?) Akan memberikan OP yang cukup untuk bekerja dengan:

Bahkan dengan korupsi saya ingin kembali ke biner karena saya masih bisa mendapatkan beberapa info berguna dari aliran ASN.1 ").

Lihat Apa yang kami ketahui dan Asumsi di bawah ini.

TL; DR

Dari beberapa tes cepat base64.b64decode (...)

  1. tampaknya ia mengabaikan karakter non- [A-Za-z0-9 + /]; itu termasuk mengabaikan = s kecuali mereka adalah karakter terakhir dalam kelompok yang diurai empat, dalam hal ini = s menghentikan decoding (a = b = c = d = memberikan hasil yang sama seperti abc =, dan a = = b == c == memberikan hasil yang sama seperti ab ==).

  2. Tampaknya juga bahwa semua karakter yang ditambahkan diabaikan setelah titik di mana base64.b64decode (...) mengakhiri decoding misalnya dari an = sebagai yang keempat dalam grup.

Seperti dicatat dalam beberapa komentar di atas, ada baik nol, atau satu, atau dua, = s padding yang diperlukan di akhir input data ketika nilai [jumlah karakter yang diurai ke titik modulo 4] adalah 0, atau 3, atau 2, masing-masing. Jadi, dari item 3. dan 4. di atas, menambahkan dua atau lebih = s ke data masukan akan memperbaiki masalah [Padding yang salah] dalam kasus tersebut.

NAMUN, decoding tidak dapat menangani kasus di mana [jumlah total karakter yang diurai modulo 4] adalah 1, karena dibutuhkan setidaknya dua karakter yang disandikan untuk mewakili byte pertama yang didekodekan dalam grup yang terdiri dari tiga byte yang didekode. Dalam data input yang disandikan tidak rusak, kasus [N modulo 4] = 1 ini tidak pernah terjadi, tetapi karena OP menyatakan bahwa karakter mungkin hilang, hal itu dapat terjadi di sini. Itulah mengapa menambahkan = s tidak akan selalu berhasil, dan mengapa menambahkan A == akan berhasil ketika menambahkan == tidak. NB Menggunakan [A] hanyalah sembarang: ia hanya menambahkan bit yang dihapus (nol) ke yang didekodekan, yang mungkin benar atau tidak, tetapi kemudian objek di sini bukanlah kebenaran tetapi diselesaikan oleh base64.b64decode (...) tanpa pengecualian .

Apa yang kita ketahui dari OP dan terutama komentar selanjutnya adalah

  • Diduga ada data (karakter) yang hilang dalam data masukan yang dikodekan Base64
  • Pengkodean Base64 menggunakan 64 nilai tempat standar plus padding: AZ; az; 0-9; +; /; = adalah padding. Ini dikonfirmasi, atau setidaknya disarankan, oleh fakta yang openssl enc ...berhasil.

Asumsi

  • Data masukan hanya berisi data ASCII 7-bit
  • Satu-satunya jenis korupsi yang hilang adalah data masukan yang dikodekan
  • OP tidak peduli tentang data keluaran yang didekodekan pada titik mana pun setelah itu sesuai dengan data masukan yang disandikan yang hilang

Github

Berikut ini pembungkus untuk mengimplementasikan solusi ini:

https://github.com/drbitboy/missing_b64

Brian Carcich
sumber
1

Kesalahan padding yang salah disebabkan karena terkadang, metadata juga ada dalam string yang disandikan. Jika string Anda terlihat seperti: 'data: image / png; base64, ... base 64 stuff ....' maka Anda perlu menghapus yang pertama bagian sebelum mendekodekannya.

Katakanlah jika Anda memiliki gambar string yang dienkode base64, lalu coba cuplikan di bawah ini ..

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")
sam
sumber
0

Cukup tambahkan karakter tambahan seperti "=" atau lainnya dan buat kelipatan 4 sebelum Anda mencoba mendekode nilai string target. Sesuatu seperti;

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)
Syed Mauze Rehan
sumber
0

Jika kesalahan ini berasal dari server web: Coba enkode url nilai posting Anda. Saya sedang POSTING melalui "curl" dan menemukan bahwa saya tidak melakukan pengkodean url nilai base64 saya sehingga karakter seperti "+" tidak lolos sehingga logika dekode url server web secara otomatis menjalankan dekode url dan mengonversi + ke spasi.

"+" adalah karakter base64 yang valid dan mungkin satu-satunya karakter yang rusak oleh dekode url yang tidak terduga.

Curtis Yallop
sumber
0

Dalam kasus saya, saya menghadapi kesalahan itu saat mengurai email. Saya mendapatkan lampiran sebagai string base64 dan mengekstraknya melalui re.search. Akhirnya ada substring tambahan yang aneh di bagian akhir.

dHJhaWxlcgo8PCAvU2l6ZSAxNSAvUm9vdCAxIDAgUiAvSW5mbyAyIDAgUgovSUQgWyhcMDAyXDMz
MHtPcFwyNTZbezU/VzheXDM0MXFcMzExKShcMDAyXDMzMHtPcFwyNTZbezU/VzheXDM0MXFcMzEx
KV0KPj4Kc3RhcnR4cmVmCjY3MDEKJSVFT0YK

--_=ic0008m4wtZ4TqBFd+sXC8--

Ketika saya menghapus --_=ic0008m4wtZ4TqBFd+sXC8--dan menghapus string maka penguraian diperbaiki.

Jadi saran saya adalah memastikan bahwa Anda mendekode string base64 yang benar.

Daniil Mashkin
sumber
0

Kamu harus menggunakan

base64.b64decode(b64_string, ' /')

Secara default, altchar adalah '+/'.

Quoc
sumber
1
Itu tidak berfungsi di python 3.7. assert len ​​(altchars) == 2, repr (altchars)
Dat TT
0

Saya mengalami masalah ini juga dan tidak ada yang berhasil. Saya akhirnya berhasil menemukan solusi yang sesuai untuk saya. Saya memiliki konten zip di base64 dan ini terjadi pada 1 dari satu juta catatan ...

Ini adalah versi solusi yang disarankan oleh Simon Sapin.

Jika padding hilang 3 maka saya menghapus 3 karakter terakhir.

Alih-alih "0gA1RD5L / 9AUGtH9MzAwAAA =="

Kami mendapatkan "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)   

Menurut jawaban ini Trailing As di base64 alasannya adalah nulls. Tapi saya masih tidak tahu mengapa pembuat enkode mengacaukannya ...

Mitzi
sumber