Python: ignorar o erro de 'preenchimento incorreto' ao decodificar em base64

111

Tenho alguns dados codificados em base64 que desejo converter de volta para binário, mesmo que haja um erro de preenchimento. Se eu usar

base64.decodestring(b64_string)

isso gera um erro de 'Preenchimento incorreto'. Existe outra maneira?

ATUALIZAÇÃO: Obrigado por todos os comentários. Para ser honesto, todos os métodos mencionados pareciam um tanto imprevisíveis, então decidi tentar o openssl. O seguinte comando funcionou muito bem:

openssl enc -d -base64 -in b64string -out binary_data
FunLovinCoder
fonte
5
Você realmente tentou usar base64.b64decode(strg, '-_')? Isso é a priori, sem você se preocupar em fornecer nenhum dado de amostra, a solução Python mais provável para o seu problema. Os “métodos” propostos foram sugestões de DEBUG, NECESSARIAMENTE “acertos e erros” dada a escassez de informações fornecidas.
John Machin
2
@John Machin: Sim, eu experimentei seu método, mas não funcionou. Os dados são confidenciais da empresa.
FunLovinCoder
3
Tentebase64.urlsafe_b64decode(s)
Daniel F
Você poderia fornecer o resultado deste: sorted(list(set(b64_string)))por favor? Sem revelar nada de confidencial da empresa, isso deve revelar quais caracteres foram usados ​​para codificar os dados originais, o que, por sua vez, pode fornecer informações suficientes para fornecer uma solução que não acertar ou errar.
Brian Carcich
Sim, eu sei que já está resolvido, mas, para ser honesto, a solução do OpenSL também parece um sucesso ou um fracasso para mim.
Brian Carcich

Respostas:

79

Como dito em outras respostas, existem várias maneiras em que os dados base64 podem ser corrompidos.

No entanto, como diz a Wikipedia , remover o preenchimento (os caracteres '=' no final dos dados codificados em base64) é "sem perdas":

Do ponto de vista teórico, o caractere de preenchimento não é necessário, já que o número de bytes ausentes pode ser calculado a partir do número de dígitos da Base64.

Portanto, se essa for realmente a única coisa "errada" com seus dados base64, o preenchimento pode ser apenas adicionado de volta. Eu criei isso para ser capaz de analisar URLs de "dados" no WeasyPrint, alguns dos quais eram base64 sem preenchimento:

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)

Testes para esta função: weasyprint / tests / test_css.py # L68

Simon Sapin
fonte
2
Nota: ASCII não Unicode, então por segurança, você pode quererstr(data)
MarkHu
4
Isso é bom com uma ressalva. base64.decodestring está obsoleto, use base64.b64_decode
ariddell
2
Para esclarecer sobre @ariddell, o comentário base64.decodestringfoi descontinuado base64.decodebytesno Py3, mas para compatibilidade de versão é melhor usar base64.b64decode.
Cas
Como o base64módulo ignora caracteres não-base64 inválidos na entrada, primeiro é necessário normalizar os dados. Remova tudo o que não seja uma letra, dígito /ou +e, em seguida, adicione o preenchimento.
Martijn Pieters
39

Basta adicionar preenchimento conforme necessário. Preste atenção ao aviso de Michael, no entanto.

b64_string += "=" * ((4 - len(b64_string) % 4) % 4) #ugh
badp
fonte
1
Certamente há algo mais simples que mapeia 0 a 0, 2 a 1 e 1 a 2.
badp
2
Por que você está expandindo para um múltiplo de 3 em vez de 4?
Michael Mrozek
Isso é o que o artigo da Wikipedia na base64 parece sugerir.
badp
1
@bp: Na codificação base64, cada entrada binária de 24 bits (3 bytes) é codificada como saída de 4 bytes. output_len% 3 não faz sentido.
John Machin
8
Apenas anexar ===sempre funciona. Quaisquer =caracteres extras são aparentemente descartados com segurança pelo Python.
Acumenus
32

Parece que você só precisa adicionar preenchimento aos bytes antes da decodificação. Existem muitas outras respostas para essa pergunta, mas quero salientar que (pelo menos no Python 3.x) base64.b64decodetruncará qualquer preenchimento extra, desde que haja o suficiente em primeiro lugar.

Então, algo como: b'abc='funciona tão bem quanto b'abc=='(como faz b'abc=====').

O que isso significa é que você pode apenas adicionar o número máximo de caracteres de preenchimento que você precisa - que é três ( b'===') - e a base64 truncará quaisquer caracteres desnecessários.

Isso permite que você escreva:

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

que é mais simples do que:

base64.b64decode(s + b'=' * (-len(s) % 4))
Henry Woody
fonte
1
Ok, isso não é muito "feio", obrigado :) A propósito, eu acho que você nunca precisa de mais de 2 caracteres de preenchimento. O algoritmo Base64 funciona em grupos de 3 caracteres por vez e só precisa de preenchimento quando o último grupo de caracteres tem apenas 1 ou 2 caracteres de comprimento.
Otto
@Otto o preenchimento aqui é para decodificação, que funciona em grupos de 4 caracteres. A codificação Base64 funciona em grupos de 3 caracteres :)
Henry Woody
mas se você sabe que durante a codificação, no máximo 2 serão adicionados, o que pode se tornar "perdido" mais tarde, forçando você a adicioná-los novamente antes da decodificação, então você sabe que só precisará adicionar no máximo 2 durante a decodificação também. #ChristmasTimeArgumentForTheFunOfIt
Otto
@Otto Eu acredito que você está certo. Enquanto uma string codificada em base64 com comprimento, por exemplo, 5 exigiria 3 caracteres de preenchimento, uma string de comprimento 5 nem mesmo é um comprimento válido para uma string codificada em base64. Você deseja obter o erro: binascii.Error: Invalid base64-encoded string: number of data characters (5) cannot be 1 more than a multiple of 4. Obrigado por apontar isso!
Henry Woody
24

"Preenchimento incorreto" pode significar não apenas "preenchimento ausente", mas também (acredite ou não) "preenchimento incorreto".

Se os métodos de "adição de preenchimento" sugeridos não funcionarem, tente remover alguns bytes finais:

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

Atualização: Qualquer tentativa de adicionar preenchimento ou remover bytes possivelmente danificados do final deve ser feito APÓS a remoção de qualquer espaço em branco, caso contrário, os cálculos de comprimento serão alterados.

Seria uma boa ideia se você nos mostrasse uma amostra (curta) dos dados que você precisa recuperar. Edite sua pergunta e copie / cole o resultado de print repr(sample) .

Atualização 2: é possível que a codificação tenha sido feita de maneira segura para url. Se for esse o caso, você será capaz de ver os caracteres de subtração e sublinhado em seus dados e deverá ser capaz de decodificá-los usandobase64.b64decode(strg, '-_')

Se você não consegue ver os caracteres de subtração e sublinhado em seus dados, mas consegue ver os caracteres de mais e barra, então você tem outro problema e pode precisar dos truques de adicionar preenchimento ou remover cruft.

Se você não consegue ver nenhum de menos, sublinhado, mais e barra em seus dados, então você precisa determinar os dois caracteres alternativos; eles serão aqueles que não estão em [A-Za-z0-9]. Então você precisará experimentar para ver em qual ordem eles precisam ser usados ​​no segundo argumento debase64.b64decode()

Atualização 3 : Se seus dados são "confidenciais da empresa":
(a) você deve dizer isso antecipadamente
(b) podemos explorar outros caminhos para a compreensão do problema, que é altamente provável que esteja relacionado a quais caracteres são usados ​​em vez de +e /em o alfabeto de codificação, ou por outra formatação ou caracteres estranhos.

Uma dessas vias seria examinar quais caracteres não "padrão" estão em seus dados, por exemplo

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
fonte
Os dados são compostos do conjunto de caracteres padrão base64. Tenho certeza de que o problema é porque um ou mais caracteres estão faltando - daí o erro de preenchimento. A menos que haja uma solução robusta em Python, irei com minha solução de chamar openssl.
FunLovinCoder
1
Uma "solução" que ignora silenciosamente os erros quase não merece o termo "robusta". Como mencionei antes, as várias sugestões do Python eram métodos de depuração para descobrir qual é o problema, preparatório para uma solução PRINCIPAL ... você não está interessado em tal coisa?
John Machin
7
Minha exigência NÃO é resolver o problema de porque a base64 está corrompida - ela vem de uma fonte sobre a qual não tenho controle. Minha exigência é fornecer informações sobre os dados recebidos, mesmo que estejam corrompidos. Uma maneira de fazer isso é obter os dados binários da base64 corrompida para que eu possa obter informações do ASN.1 subjacente. corrente. Fiz a pergunta original porque queria uma resposta a essa pergunta, não a resposta a outra pergunta - como depurar base64 corrompida.
FunLovinCoder
Apenas normalize a string, remova tudo o que não seja um caractere Base64. Em qualquer lugar, não apenas no início ou no fim.
Martijn Pieters
24

Usar

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

O crédito vai para um comentário em algum lugar aqui.

>>> 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
fonte
4
Ele
quis
22

Se houver um erro de preenchimento, provavelmente significa que sua string está corrompida; As strings codificadas em base64 devem ter um comprimento múltiplo de quatro. Você pode tentar adicionar o caractere de preenchimento ( =) para tornar a string um múltiplo de quatro, mas já deve ter isso a menos que algo esteja errado

Michael Mrozek
fonte
Os dados binários subjacentes são ASN.1. Mesmo com a corrupção, quero voltar ao binário porque ainda posso obter algumas informações úteis do fluxo ASN.1.
FunLovinCoder
não é verdade, se você quiser decodificar um jwt para verificações de segurança, você precisará dele
DAG
4

Verifique a documentação da fonte de dados que você está tentando decodificar. É possível que você quisesse usar em base64.urlsafe_b64decode(s)vez de base64.b64decode(s)? Esse é um dos motivos pelos quais você pode ter visto essa mensagem de erro.

Decodifique strings usando um alfabeto seguro para URL, que substitui - em vez de + e _ em vez de / no alfabeto Base64 padrão.

Este é, por exemplo, o caso de várias APIs do Google, como o Identity Toolkit do Google e cargas úteis do Gmail.

Daniel F
fonte
1
Isso não responde à pergunta de forma alguma. Além disso, urlsafe_b64decodetambém requer preenchimento.
rdb
Bem, eu tive um problema antes de responder a esta pergunta, que estava relacionado ao Identity Toolkit do Google. Eu estava recebendo o erro de preenchimento incorreto (creio que era no servidor), mesmo que o preenchimento parecesse correto. Descobri que eu tinha que usar base64.urlsafe_b64decode.
Daniel F
Concordo que não responde à pergunta, rdb, mas era exatamente o que eu precisava ouvir também. Reformulei a resposta para um tom um pouco mais agradável, espero que funcione para você, Daniel.
Henrik Heimbuerger
Perfeitamente bem. Não percebi que parecia meio indelicado, só pensei que seria a solução mais rápida se resolvesse o problema e, por esse motivo, deveria ser a primeira coisa a ser tentada. Obrigado pela sua mudança, é bem-vindo.
Daniel F de
Essa resposta resolveu meu problema de decodificar um token de acesso do Google derivado de um JWT. Todas as outras tentativas resultaram em "Preenchimento incorreto".
John Hanley
2

Adicionar o preenchimento é bastante ... complicado. Aqui está a função que escrevi com a ajuda dos comentários neste tópico, bem como a página wiki para base64 (é surpreendentemente útil) 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
fonte
2

Você pode simplesmente usar base64.urlsafe_b64decode(data)se estiver tentando decodificar uma imagem da web. Ele cuidará automaticamente do enchimento.

VINEE
fonte
realmente ajuda!
Lua
1

Existem duas maneiras de corrigir os dados de entrada descritos aqui ou, mais especificamente e em linha com o OP, para tornar o método b64decode do módulo Python base64 capaz de processar os dados de entrada para algo sem gerar uma exceção não detectada:

  1. Anexe == ao final dos dados de entrada e chame base64.b64decode (...)
  2. Se isso gerar uma exceção, então

    Eu. Pegue-o via try / except,

    ii. (R?) Retire quaisquer = caracteres dos dados de entrada (NB, pode não ser necessário),

    iii. Anexe A == aos dados de entrada (A == a P == funcionará),

    iv. Chame base64.b64decode (...) com aqueles A == - dados de entrada anexados

O resultado do item 1. ou item 2. acima produzirá o resultado desejado.

Ressalvas

Isso não garante que o resultado decodificado será o que foi originalmente codificado, mas (às vezes?) Dará ao OP o suficiente para trabalhar:

Mesmo com a corrupção, quero voltar ao binário porque ainda posso obter algumas informações úteis do fluxo ASN.1 ").

Veja o que sabemos e suposições abaixo.

TL; DR

De alguns testes rápidos de base64.b64decode (...)

  1. parece que ele ignora caracteres não- [A-Za-z0-9 + /]; isso inclui ignorar = s, a menos que sejam os últimos caracteres em um grupo analisado de quatro, caso em que = s encerram a decodificação (a = b = c = d = dá o mesmo resultado que abc =, e a = = b == c == dá o mesmo resultado que ab ==).

  2. Também parece que todos os caracteres acrescentados são ignorados após o ponto onde base64.b64decode (...) termina a decodificação, por exemplo, de an = como o quarto em um grupo.

Conforme observado em vários comentários acima, há zero, ou um, ou dois, = s de preenchimento necessário no final dos dados de entrada para quando o valor de [número de caracteres analisados ​​até aquele ponto módulo 4] for 0 ou 3, ou 2, respectivamente. Portanto, a partir dos itens 3. e 4. acima, anexar dois ou mais = s aos dados de entrada corrigirá quaisquer problemas de [preenchimento incorreto] nesses casos.

NO ENTANTO, a decodificação não pode lidar com o caso onde o [número total de caracteres analisados ​​módulo 4] é 1, porque são necessários pelo menos dois caracteres codificados para representar o primeiro byte decodificado em um grupo de três bytes decodificados. Em dados de entrada codificados não corrompidos, este [N módulo 4] = 1 caso nunca acontece, mas como o OP afirmou que caracteres podem estar faltando, isso pode acontecer aqui. É por isso que simplesmente anexar = s nem sempre funcionará, e por que anexar A == irá funcionar quando anexar == não. NB Usar [A] é quase arbitrário: ele adiciona apenas bits apagados (zero) ao decodificado, o que pode ou não ser correto, mas então o objeto aqui não é a correção, mas a conclusão por base64.b64decode (...) sem exceções .

O que sabemos do OP e especialmente dos comentários subsequentes é

  • Suspeita-se que há dados ausentes (caracteres) nos dados de entrada codificados em Base64
  • A codificação Base64 usa os 64 valores locais padrão mais o preenchimento: AZ; az; 0-9; +; /; = é preenchimento. Isso é confirmado, ou pelo menos sugerido, pelo fato de que openssl enc ...funciona.

Premissas

  • Os dados de entrada contêm apenas dados ASCII de 7 bits
  • O único tipo de corrupção é a falta de dados de entrada codificados
  • O OP não se preocupa com dados de saída decodificados em qualquer ponto após aquele correspondente a quaisquer dados de entrada codificados ausentes

Github

Aqui está um wrapper para implementar esta solução:

https://github.com/drbitboy/missing_b64

Brian Carcich
fonte
1

O erro de preenchimento incorreto é causado porque às vezes os metadados também estão presentes na string codificada. Se sua string for algo como: 'data: image / png; base64, ... base 64 stuff ....' então você precisa remover o primeiro parte antes de decodificá-lo.

Digamos que você tenha uma string codificada em base64 de imagem e tente o snippet abaixo.

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
fonte
0

Basta adicionar caracteres adicionais como "=" ou qualquer outro e torná-lo um múltiplo de 4 antes de tentar decodificar o valor da string de destino. Algo como;

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
fonte
0

Caso este erro venha de um servidor web: tente codificar url seu valor post. Eu estava fazendo um POST via "curl" e descobri que não estava codificando com url meu valor base64, então caracteres como "+" não eram escapados, de modo que a lógica de decodificação de url do servidor web executava decodificação de url automaticamente e convertia + em espaços.

"+" é um caractere base64 válido e talvez o único caractere que é mutilado por uma decodificação de url inesperada.

Curtis Yallop
fonte
0

No meu caso, encontrei esse erro ao analisar um e-mail. Peguei o anexo como string base64 e extraí-o por meio de re.search. Eventualmente, havia uma substring adicional estranha no final.

dHJhaWxlcgo8PCAvU2l6ZSAxNSAvUm9vdCAxIDAgUiAvSW5mbyAyIDAgUgovSUQgWyhcMDAyXDMz
MHtPcFwyNTZbezU/VzheXDM0MXFcMzExKShcMDAyXDMzMHtPcFwyNTZbezU/VzheXDM0MXFcMzEx
KV0KPj4Kc3RhcnR4cmVmCjY3MDEKJSVFT0YK

--_=ic0008m4wtZ4TqBFd+sXC8--

Quando eu apaguei --_=ic0008m4wtZ4TqBFd+sXC8-- e retirei a string, a análise foi corrigida.

Portanto, meu conselho é ter certeza de que você está decodificando uma string base64 correta.

Daniil Mashkin
fonte
0

Você deveria usar

base64.b64decode(b64_string, ' /')

Por padrão, os altchars são '+/'.

Quoc
fonte
1
Isso não funciona no python 3.7. declarar len (altchars) == 2, repr (altchars)
Dat TT
0

Também encontrei este problema e nada funcionou. Finalmente consegui encontrar a solução que funciona para mim. Eu havia compactado o conteúdo em base64 e isso aconteceu com 1 em um milhão de registros ...

Esta é uma versão da solução sugerida por Simon Sapin.

Caso falte 3 caracteres no preenchimento, removo os 3 últimos caracteres.

Em vez de "0gA1RD5L / 9AUGtH9MzAwAAA =="

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

De acordo com esta resposta, Trailing As em base64 o motivo são nulos. Mas ainda não tenho ideia de por que o codificador bagunçou isso ...

Mitzi
fonte