Python: ignora l'errore "Padding errato" durante la decodifica base64


111

Ho alcuni dati codificati in base64 che desidero riconvertire in binario anche se è presente un errore di riempimento. Se uso

base64.decodestring(b64_string)

solleva un errore di "riempimento errato". C'è un altro modo?

AGGIORNAMENTO: Grazie per tutto il feedback. Ad essere onesti, tutti i metodi menzionati sembravano un po 'incostante, quindi ho deciso di provare openssl. Il seguente comando ha funzionato a meraviglia:

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

5
Hai effettivamente provato a usare base64.b64decode(strg, '-_')? Questo è a priori, senza che tu ti preoccupi di fornire dati di esempio, la soluzione Python più probabile al tuo problema. I "metodi" proposti erano suggerimenti di DEBUG, NECESSARIAMENTE "incostante" data la scarsità delle informazioni fornite.
John Machin

2
@ John Machin: Sì, ho provato il tuo metodo ma non ha funzionato. I dati sono riservati all'azienda.
FunLovinCoder

3
Provabase64.urlsafe_b64decode(s)
Daniel F

Potresti fornire l'output di questo: per sorted(list(set(b64_string)))favore? Senza rivelare nulla di riservato alla società, ciò dovrebbe rivelare quali caratteri sono stati utilizzati per codificare i dati originali, il che a sua volta potrebbe fornire informazioni sufficienti per fornire una soluzione non a caso.
Brian Carcich

Sì, lo so che è già risolto, ma, ad essere onesti, anche la soluzione openssl mi suona a caso.
Brian Carcich

Risposte:


79

Come detto in altre risposte, ci sono vari modi in cui i dati base64 potrebbero essere danneggiati.

Tuttavia, come dice Wikipedia , la rimozione del riempimento (i caratteri "=" alla fine dei dati codificati in base64) è "senza perdita di dati":

Da un punto di vista teorico, il carattere di riempimento non è necessario, poiché il numero di byte mancanti può essere calcolato dal numero di cifre Base64.

Quindi, se questa è davvero l'unica cosa "sbagliata" con i tuoi dati base64, il riempimento può essere semplicemente aggiunto di nuovo. Ho pensato a questo per poter analizzare gli URL "dati" in WeasyPrint, alcuni dei quali erano base64 senza riempimento:

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)

Test per questa funzione: weasyprint / tests / test_css.py # L68


2
Nota: ASCII non Unicode, quindi per sicurezza, potresti volerlostr(data)
MarkHu

4
Questo va bene con un avvertimento. base64.decodestring è deprecato, usa base64.b64_decode
ariddell

2
Per chiarire su @ariddell il commento base64.decodestringè stato deprecato base64.decodebytesin Py3 ma per compatibilità con la versione è meglio usarlo base64.b64decode.
Cas

Poiché il base64modulo ignora i caratteri non in base64 non validi nell'input, è necessario prima normalizzare i dati. Rimuovi tutto ciò che non è una lettera, una cifra /o +, quindi aggiungi il riempimento.
Martijn Pieters

39

Basta aggiungere imbottitura come richiesto. Tuttavia, ascolta l'avvertimento di Michael.

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

1
C'è sicuramente qualcosa di più semplice che mappa 0 a 0, 2 a 1 e 1 a 2.
badp

2
Perché ti stai espandendo a un multiplo di 3 invece di 4?
Michael Mrozek

Questo è ciò che sembra implicare l'articolo di wikipedia su base64.
badp

1
@bp: nella codifica base64 ogni ingresso binario a 24 bit (3 byte) è codificato come output a 4 byte. output_len% 3 non ha senso.
John Machin

8
Basta aggiungere ===sempre funziona. Eventuali =caratteri extra vengono apparentemente scartati in modo sicuro da Python.
Acumenus

32

Sembra che tu abbia solo bisogno di aggiungere il riempimento ai tuoi byte prima della decodifica. Ci sono molte altre risposte a questa domanda, ma voglio sottolineare che (almeno in Python 3.x) base64.b64decodetroncerà qualsiasi riempimento extra, a condizione che ce ne sia abbastanza in primo luogo.

Quindi, qualcosa come: b'abc='funziona altrettanto bene come b'abc=='(come fa b'abc=====').

Ciò significa che puoi semplicemente aggiungere il numero massimo di caratteri di riempimento di cui avresti mai bisogno, ovvero tre ( b'==='), e base64 troncerà quelli non necessari.

Questo ti consente di scrivere:

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

che è più semplice di:

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

1
Ok, non è troppo "brutto", grazie :) A proposito, penso che non avrai mai bisogno di più di 2 caratteri di riempimento. L'algoritmo Base64 funziona su gruppi di 3 caratteri alla volta e necessita di riempimento solo quando l'ultimo gruppo di caratteri è lungo solo 1 o 2 caratteri.
Otto

@Otto il riempimento qui è per la decodifica, che funziona su gruppi di 4 caratteri. La codifica Base64 funziona su gruppi di 3 caratteri :)
Henry Woody

ma se sai che durante la codifica verranno mai aggiunti al massimo 2, cosa che potrebbe andare "persa" in seguito, costringendoti a riaggiungerli prima della decodifica, allora sai che dovrai aggiungere al massimo 2 anche durante la decodifica. #ChristmasTimeArgumentForTheFunOfIt
Otto

@Otto credo che tu abbia ragione. Mentre una stringa con codifica base64 con lunghezza, ad esempio, 5 richiederebbe 3 caratteri di riempimento, una stringa di lunghezza 5 non è nemmeno una lunghezza valida per una stringa con codifica base64. Si otterrebbe l'errore: binascii.Error: Invalid base64-encoded string: number of data characters (5) cannot be 1 more than a multiple of 4. Grazie per averlo fatto notare!
Henry Woody

24

"Padding errato" può significare non solo "riempimento mancante" ma anche (che ci crediate o no) "riempimento errato".

Se i metodi suggeriti di "aggiunta di riempimento" non funzionano, prova a rimuovere alcuni byte finali:

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

Aggiornamento: qualsiasi manipolazione nell'aggiunta di riempimento o nella rimozione di byte potenzialmente dannosi dalla fine dovrebbe essere eseguita DOPO aver rimosso qualsiasi spazio bianco, altrimenti i calcoli della lunghezza saranno sconvolti.

Sarebbe una buona idea se ci mostrassi un (breve) campione dei dati che devi recuperare. Modifica la tua domanda e copia / incolla il risultato di print repr(sample) .

Aggiornamento 2: è possibile che la codifica sia stata eseguita in modo sicuro per l'URL. Se questo è il caso, sarai in grado di vedere i caratteri meno e di sottolineatura nei tuoi dati e dovresti essere in grado di decodificarli usandobase64.b64decode(strg, '-_')

Se non riesci a vedere i caratteri meno e di sottolineatura nei tuoi dati, ma puoi vedere i caratteri più e barra, allora hai qualche altro problema e potresti aver bisogno dei trucchi add-padding o remove-cruft.

Se non riesci a vedere alcun segno di meno, trattino basso, più e barra nei dati, devi determinare i due caratteri alternativi; saranno quelli che non sono in [A-Za-z0-9]. Quindi dovrai sperimentare per vedere quale ordine devono essere usati nel 2 ° argomento dibase64.b64decode()

Aggiornamento 3 : se i tuoi dati sono "riservati dell'azienda":
(a) dovresti dirlo in anticipo
(b) possiamo esplorare altre strade per comprendere il problema, che è molto probabile che sia correlato a quali caratteri vengono utilizzati al posto di +e/ in l'alfabeto di codifica o altri caratteri di formattazione o estranei.

Una di queste strade sarebbe esaminare quali caratteri non "standard" sono presenti nei dati, ad es

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

I dati sono costituiti dal set di caratteri base64 standard. Sono abbastanza sicuro che il problema sia perché mancano 1 o più caratteri, da qui l'errore di riempimento. A meno che non ci sia una soluzione robusta in Python, seguirò la mia soluzione per chiamare openssl.
FunLovinCoder

1
Una "soluzione" che ignora silenziosamente gli errori difficilmente merita il termine "robusta". Come ho accennato in precedenza, i vari suggerimenti di Python erano metodi di DEBUG per scoprire qual è il problema, propedeutici a una soluzione PRINCIPALE ... non ti interessa una cosa del genere?
John Machin

7
Il mio requisito NON è quello di risolvere il problema del perché il base64 è corrotto: proviene da una fonte su cui non ho alcun controllo. Il mio requisito è fornire informazioni sui dati ricevuti anche se danneggiati. Un modo per farlo è estrarre i dati binari dal database corrotto base64 in modo da poter raccogliere informazioni dall'ASN.1 sottostante. streaming. Ho posto la domanda originale perché volevo una risposta a quella domanda non la risposta a un'altra domanda, ad esempio come eseguire il debug di base64 corrotto.
FunLovinCoder

Basta normalizzare la stringa, rimuovere tutto ciò che non è un carattere Base64. Ovunque, non solo all'inizio o alla fine.
Martijn Pieters

24

Uso

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

Il merito va a un commento da qualche parte qui.

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

>>> 


22

Se c'è un errore di riempimento probabilmente significa che la tua stringa è danneggiata; le stringhe con codifica base64 dovrebbero avere un multiplo di quattro lunghezze. Puoi provare ad aggiungere =tu stesso il carattere di riempimento ( ) per rendere la stringa un multiplo di quattro, ma dovrebbe già averlo a meno che qualcosa non vada


I dati binari sottostanti sono ASN.1. Anche con la corruzione voglio tornare al binario perché posso ancora ottenere alcune informazioni utili dal flusso ASN.1.
FunLovinCoder

non è vero, se vuoi decodificare un jwt per i controlli di sicurezza, ne avrai bisogno
DAG

4

Controlla la documentazione dell'origine dati che stai tentando di decodificare. È possibile che intendevi usare al base64.urlsafe_b64decode(s)posto di base64.b64decode(s)? Questo è uno dei motivi per cui potresti aver visualizzato questo messaggio di errore.

Decodifica la stringa s utilizzando un alfabeto sicuro per URL, che sostituisce - invece di + e _ invece di / nell'alfabeto Base64 standard.

Questo è ad esempio il caso di varie API di Google, come Identity Toolkit di Google e i payload di Gmail.


1
Questo non risponde affatto alla domanda. Inoltre, urlsafe_b64decoderichiede anche imbottitura.
rdb

Bene, c'era un problema che avevo prima di rispondere a questa domanda, che era correlato all'Identity Toolkit di Google. Ho ricevuto l'errore di riempimento errato (credo che fosse sul server) anche se il riempimento sembrava essere corretto. Si è scoperto che ho dovuto usare base64.urlsafe_b64decode.
Daniel F

Sono d'accordo che non risponde alla domanda, rdb, ma era esattamente quello che avevo bisogno di sentire. Ho riformulato la risposta con un tono un po 'più carino, spero che funzioni per te, Daniel.
Henrik Heimbuerger

Perfettamente bene. Non ho notato che sembrava un po 'scortese, ho solo pensato che sarebbe stata la soluzione più rapida se avesse risolto il problema e, per questo motivo, dovrebbe essere la prima cosa da provare. Grazie per il tuo cambiamento, è il benvenuto.
Daniel F,

Questa risposta ha risolto il mio problema di decodifica di un token di accesso Google derivato da un JWT. Tutti gli altri tentativi hanno dato come risultato "riempimento errato".
John Hanley

2

Aggiungere l'imbottitura è piuttosto ... complicato. Ecco la funzione che ho scritto con l'aiuto dei commenti in questo thread e la pagina wiki per base64 (è sorprendentemente utile) 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

Puoi semplicemente usare base64.urlsafe_b64decode(data)se stai cercando di decodificare un'immagine web. Si prenderà automaticamente cura dell'imbottitura.


aiuta davvero!
Luna

1

Esistono due modi per correggere i dati di input descritti qui, o, più specificamente e in linea con l'OP, per rendere il metodo b64decode di base64 del modulo Python in grado di elaborare i dati di input in qualcosa senza sollevare un'eccezione non rilevata:

  1. Aggiungi == alla fine dei dati di input e chiama base64.b64decode (...)
  2. Se questo solleva un'eccezione, allora

    io. Catturalo tramite prova / tranne,

    ii. (R?) Elimina qualsiasi = caratteri dai dati di input (NB potrebbe non essere necessario),

    iii. Aggiungi A == ai dati di input (da A == a P == funzioneranno),

    iv. Chiama base64.b64decode (...) con quelli A == - dati di input aggiunti

Il risultato dell'elemento 1. o dell'articolo 2. sopra darà il risultato desiderato.

Avvertenze

Ciò non garantisce che il risultato decodificato sarà quello originariamente codificato, ma (a volte?) Darà all'OP abbastanza per lavorare con:

Anche con la corruzione voglio tornare al binario perché posso ancora ottenere alcune informazioni utili dallo stream ASN.1 ").

Vedi cosa sappiamo e ipotesi di seguito.

TL; DR

Da alcuni rapidi test di base64.b64decode (...)

  1. sembra che ignori caratteri non [A-Za-z0-9 + /]; che include ignorare = s a meno che non siano gli ultimi caratteri in un gruppo analizzato di quattro, nel qual caso i = s terminano la decodifica (a = b = c = d = dà lo stesso risultato di abc = e a = = b == c == dà lo stesso risultato di ab ==).

  2. Sembra anche che tutti i caratteri aggiunti vengano ignorati dopo il punto in cui base64.b64decode (...) termina la decodifica, ad esempio da un = come quarto in un gruppo.

Come notato in diversi commenti sopra, ci sono zero, o uno, o due, = s di riempimento richiesto alla fine dei dati di input per quando il valore [numero di caratteri analizzati a quel punto modulo 4] è 0 o 3, o 2, rispettivamente. Quindi, dagli elementi 3. e 4. sopra, l'aggiunta di due o più = s ai dati di input correggerà eventuali problemi di [riempimento errato] in quei casi.

TUTTAVIA, la decodifica non può gestire il caso in cui il [numero totale di caratteri analizzati modulo 4] è 1, perché sono necessari almeno due caratteri codificati per rappresentare il primo byte decodificato in un gruppo di tre byte decodificati. In dati di input codificati non corrotti, questo caso [N modulo 4] = 1 non si verifica mai, ma poiché l'OP ha affermato che i caratteri potrebbero mancare, potrebbe accadere qui. Questo è il motivo per cui aggiungere semplicemente = s non funzionerà sempre, e perché aggiungere A == funzionerà quando l'aggiunta di == non funziona. NB Usare [A] è tutto tranne che arbitrario: aggiunge solo bit cancellati (zero) al decodificato, che può essere corretto o meno, ma allora l'oggetto qui non è la correttezza ma il completamento con base64.b64decode (...) senza eccezioni .

Quello che sappiamo dal PO e soprattutto dai commenti successivi lo è

  • Si sospetta che manchino dati (caratteri) nei dati di input con codifica Base64
  • La codifica Base64 utilizza i 64 valori di posizione standard più il riempimento: AZ; az; 0-9; +; /; = è imbottitura. Ciò è confermato, o almeno suggerito, dal fatto che openssl enc ...funziona.

ipotesi

  • I dati di ingresso contengono solo dati ASCII a 7 bit
  • L'unico tipo di danneggiamento è la mancanza di dati di input codificati
  • L'OP non si preoccupa dei dati di uscita decodificati in qualsiasi punto dopo quello corrispondente a qualsiasi dato di ingresso codificato mancante

Github

Ecco un wrapper per implementare questa soluzione:

https://github.com/drbitboy/missing_b64


1

Si verifica un errore di riempimento errato perché a volte sono presenti anche metadati nella stringa codificata Se la stringa ha un aspetto simile a: 'data: image / png; base64, ... base 64 stuff ....', allora devi rimuovere il primo parte prima di decodificarlo.

Supponi di avere una stringa con codifica base64 dell'immagine, quindi prova sotto lo snippet.

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

Aggiungi semplicemente caratteri aggiuntivi come "=" o qualsiasi altro e rendilo un multiplo di 4 prima di provare a decodificare il valore della stringa di destinazione. Qualcosa di simile a;

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

Nel caso in cui questo errore provenga da un server web: prova a codificare l'URL del valore del tuo post. Stavo effettuando il POST tramite "curl" e ho scoperto che non stavo codificando l'URL del mio valore base64, quindi caratteri come "+" non sono stati sottoposti a escape, quindi la logica di decodifica dell'URL del server Web ha eseguito automaticamente la decodifica dell'URL e + convertito in spazi.

"+" è un carattere base64 valido e forse l'unico carattere che viene alterato da una decodifica URL inaspettata.


0

Nel mio caso ho riscontrato quell'errore durante l'analisi di un'e-mail. Ho ricevuto l'allegato come stringa base64 e l'ho estratto tramite re.search. Alla fine c'era una strana sottostringa aggiuntiva alla fine.

dHJhaWxlcgo8PCAvU2l6ZSAxNSAvUm9vdCAxIDAgUiAvSW5mbyAyIDAgUgovSUQgWyhcMDAyXDMz
MHtPcFwyNTZbezU/VzheXDM0MXFcMzExKShcMDAyXDMzMHtPcFwyNTZbezU/VzheXDM0MXFcMzEx
KV0KPj4Kc3RhcnR4cmVmCjY3MDEKJSVFT0YK

--_=ic0008m4wtZ4TqBFd+sXC8--

Quando ho cancellato --_=ic0008m4wtZ4TqBFd+sXC8-- e la stringa, l'analisi è stata risolta.

Quindi il mio consiglio è di assicurarti di decodificare una stringa base64 corretta.


0

Dovresti usare

base64.b64decode(b64_string, ' /')

Per impostazione predefinita, gli altchar sono '+/'.


1
Non funziona in Python 3.7. assert len ​​(altchars) == 2, repr (altchars)
Dat TT

0

Mi sono imbattuto anche in questo problema e niente ha funzionato. Finalmente sono riuscito a trovare la soluzione che funziona per me. Avevo contenuto zippato in base64 e questo è successo a 1 su un milione di record ...

Questa è una versione della soluzione suggerita da Simon Sapin.

Nel caso in cui il padding manchi 3, rimuovo gli ultimi 3 caratteri.

Invece di "0gA1RD5L / 9AUGtH9MzAwAAA =="

Otteniamo "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)   

Secondo questa risposta Trailing As in base64 il motivo è nullo. Ma non ho ancora idea del motivo per cui il codificatore rovini tutto ...

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.