Python: ignore el error de 'relleno incorrecto' al decodificar base64

111

Tengo algunos datos codificados en base64 que quiero convertir de nuevo a binario incluso si hay un error de relleno en ellos. Si uso

base64.decodestring(b64_string)

genera un error de "relleno incorrecto". ¿Hay otra manera?

ACTUALIZACIÓN: Gracias por todos los comentarios. Para ser honesto, todos los métodos mencionados sonaban un poco impredecibles, así que decidí probar openssl. El siguiente comando funcionó de maravilla:

openssl enc -d -base64 -in b64string -out binary_data
FunLovinCoder
fuente
5
¿De verdad INTENTÓ usar base64.b64decode(strg, '-_')? Eso es a priori, sin que se moleste en proporcionar ningún dato de muestra, la solución Python más probable para su problema. Los "métodos" propuestos fueron sugerencias DEBUG, NECESARIAMENTE "acertadas" dada la escasez de información suministrada.
John Machin
2
@John Machin: Sí, PROBÉ tu método pero no funcionó. Los datos son confidenciales de la empresa.
FunLovinCoder
3
Pruebabase64.urlsafe_b64decode(s)
Daniel F
¿Podría proporcionar el resultado de esto: sorted(list(set(b64_string)))por favor? Sin revelar nada confidencial de la empresa, eso debería revelar qué caracteres se utilizaron para codificar los datos originales, que a su vez pueden proporcionar suficiente información para proporcionar una solución que no sea acertada o fallida.
Brian Carcich
Sí, sé que ya está resuelto, pero, para ser honesto, la solución openssl también me suena al azar.
Brian Carcich

Respuestas:

79

Como se dijo en otras respuestas, hay varias formas en las que los datos base64 podrían corromperse.

Sin embargo, como dice Wikipedia , eliminar el relleno (los caracteres '=' al final de los datos codificados en base64) es "sin pérdidas":

Desde un punto de vista teórico, el carácter de relleno no es necesario, ya que el número de bytes que faltan se puede calcular a partir del número de dígitos Base64.

Entonces, si esto es realmente lo único "incorrecto" con sus datos base64, el relleno se puede volver a agregar. Se me ocurrió esto para poder analizar las URL de "datos" en WeasyPrint, algunas de las cuales eran base64 sin relleno:

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)

Pruebas para esta función: weasyprint / tests / test_css.py # L68

Simon Sapin
fuente
2
Nota: ASCII no Unicode, así que para estar seguro, es posible que deseestr(data)
MarkHu
4
Esto es bueno con una advertencia. base64.decodestring está en desuso, use base64.b64_decode
ariddell
2
Para aclarar el comentario de @ariddell base64.decodestringse ha desaprobado base64.decodebytesen Py3, pero es mejor usarlo por compatibilidad de versiones base64.b64decode.
Cas
Debido a que el base64módulo ignora los caracteres no válidos que no son base64 en la entrada, primero debe normalizar los datos. Quite todo lo que no sea una letra, un dígito /o +y luego agregue el relleno.
Martijn Pieters
39

Simplemente agregue relleno según sea necesario. Sin embargo, preste atención a la advertencia de Michael.

b64_string += "=" * ((4 - len(b64_string) % 4) % 4) #ugh
badp
fuente
1
Seguramente hay algo más simple que asigna 0 a 0, 2 a 1 y 1 a 2.
badp
2
¿Por qué se expande a un múltiplo de 3 en lugar de 4?
Michael Mrozek
Eso es lo que parece implicar el artículo de wikipedia sobre base64.
badp
1
@bp: En la codificación base64, cada entrada binaria de 24 bits (3 bytes) se codifica como salida de 4 bytes. output_len% 3 no tiene sentido.
John Machin
8
Solo agregar ===siempre funciona. =Python descarta aparentemente cualquier carácter adicional de forma segura.
Acumenus
32

Parece que solo necesita agregar relleno a sus bytes antes de decodificar. Hay muchas otras respuestas a esta pregunta, pero quiero señalar que (al menos en Python 3.x) base64.b64decodetruncará cualquier relleno adicional, siempre que haya suficiente en primer lugar.

Entonces, algo como: b'abc='funciona tan bien como b'abc=='(como lo hace b'abc=====').

Lo que esto significa es que puede agregar el número máximo de caracteres de relleno que necesitaría, que es tres ( b'==='), y base64 truncará los que no sean necesarios.

Esto te permite escribir:

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

que es más simple que:

base64.b64decode(s + b'=' * (-len(s) % 4))
Henry Woody
fuente
1
De acuerdo, eso no es demasiado "feo", gracias :) Por cierto, creo que nunca necesitas más de 2 caracteres de relleno. El algoritmo Base64 funciona en grupos de 3 caracteres a la vez y solo necesita relleno cuando su último grupo de caracteres tiene solo 1 o 2 caracteres de longitud.
Otto
@Otto, el relleno aquí es para decodificar, que funciona en grupos de 4 caracteres. La codificación Base64 funciona en grupos de 3 caracteres :)
Henry Woody
pero si sabe que durante la codificación se agregarán un máximo de 2, que pueden "perderse" más adelante, lo que le obligará a volver a agregarlos antes de decodificar, entonces sabrá que solo necesitará agregar un máximo de 2 durante la decodificación. #ChristmasTimeArgumentForTheFunOfIt
Otto
@Otto Creo que tienes razón. Mientras que una cadena codificada en base64 con una longitud, por ejemplo, 5 requeriría 3 caracteres de relleno, una cadena de longitud 5 ni siquiera es una longitud válida para una cadena codificada en base64. Te obtener el error: binascii.Error: Invalid base64-encoded string: number of data characters (5) cannot be 1 more than a multiple of 4. ¡Gracias por señalar esto!
Henry Woody
24

"Relleno incorrecto" puede significar no sólo "relleno faltante" sino también (lo crea o no) "relleno incorrecto".

Si los métodos sugeridos de "agregar relleno" no funcionan, intente eliminar algunos bytes finales:

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

Actualización: Cualquier manipulación para agregar relleno o eliminar posibles bytes incorrectos del final debe hacerse DESPUÉS de eliminar cualquier espacio en blanco, de lo contrario, los cálculos de longitud se alterarán.

Sería una buena idea que nos mostrara una muestra (breve) de los datos que necesita recuperar. Edite su pregunta y copie / pegue el resultado de print repr(sample) .

Actualización 2: Es posible que la codificación se haya realizado de manera segura para URL. Si este es el caso, podrá ver los caracteres menos y subrayados en sus datos, y debería poder decodificarlos usandobase64.b64decode(strg, '-_')

Si no puede ver los caracteres de menos y subrayado en sus datos, pero puede ver los caracteres de más y barra, entonces tiene algún otro problema y puede necesitar los trucos de agregar relleno o eliminar cruft.

Si no ve nada de menos, subrayado, más ni barra en sus datos, entonces necesita determinar los dos caracteres alternativos; serán los que no estén en [A-Za-z0-9]. Luego, deberá experimentar para ver qué orden deben usarse en el segundo argumento debase64.b64decode()

Actualización 3 : si sus datos son "confidenciales de la empresa":
(a) debe decirlo desde el principio
(b) podemos explorar otras vías para comprender el problema, que es muy probable que esté relacionado con los caracteres que se utilizan en lugar de +y /en el alfabeto de codificación, o por otro formato o caracteres extraños.

Una de esas vías sería examinar qué caracteres no "estándar" hay en sus datos, p. Ej.

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
fuente
Los datos se componen del juego de caracteres estándar base64. Estoy bastante seguro de que el problema se debe a que faltan 1 o más caracteres, de ahí el error de relleno. A menos que haya una solución sólida en Python, iré con mi solución de llamar a openssl.
FunLovinCoder
1
Una "solución" que ignora silenciosamente los errores apenas merece el término "robusta". Como mencioné anteriormente, las diversas sugerencias de Python fueron métodos de DEPURACIÓN para descubrir cuál es el problema, como preparación para una solución PRINCIPLADA ... ¿no está interesado en tal cosa?
John Machin
7
Mi requisito NO es resolver el problema de por qué la base64 está corrupta; proviene de una fuente sobre la que no tengo control. Mi requisito es proporcionar información sobre los datos recibidos, incluso si están corruptos. Una forma de hacer esto es sacar los datos binarios de la base64 corrupta para poder obtener información del ASN.1 subyacente. corriente. Hice la pregunta original porque quería una respuesta a esa pregunta, no la respuesta a otra pregunta, como cómo depurar base64 corrupta.
FunLovinCoder
Simplemente normalice la cadena, elimine todo lo que no sea un carácter Base64. En cualquier lugar, no solo al principio o al final.
Martijn Pieters
24

Utilizar

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

El crédito va a un comentario en algún lugar aquí.

>>> 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
fuente
4
Quiere decir este comentario: stackoverflow.com/questions/2941995/…
jackyalcine
22

Si hay un error de relleno, probablemente significa que su cadena está dañada; Las cadenas codificadas en base64 deben tener un múltiplo de cuatro de longitud. Puede intentar agregar el carácter de relleno ( =) usted mismo para hacer que la cadena sea un múltiplo de cuatro, pero ya debería tener eso a menos que algo esté mal

Michael Mrozek
fuente
Los datos binarios subyacentes son ASN.1. Incluso con la corrupción, quiero volver al binario porque todavía puedo obtener información útil del flujo ASN.1.
FunLovinCoder
no es cierto, si desea decodificar un jwt para controles de seguridad, lo necesitará
DAG
4

Consulte la documentación de la fuente de datos que está intentando decodificar. ¿Es posible que tuvieras que usar en base64.urlsafe_b64decode(s)lugar de base64.b64decode(s)? Esa es una de las razones por las que puede haber visto este mensaje de error.

Decodifica cadenas usando un alfabeto seguro para URL, que sustituye - en lugar de + y _ en lugar de / en el alfabeto estándar de Base64.

Este es, por ejemplo, el caso de varias API de Google, como el kit de herramientas de identidad de Google y las cargas útiles de Gmail.

Daniel F
fuente
1
Esto no responde a la pregunta en absoluto. Además, urlsafe_b64decodetambién requiere relleno.
rdb
Bueno, hubo un problema que tuve antes de responder a esta pregunta, que estaba relacionado con el kit de herramientas de identidad de Google. Recibía el error de relleno incorrecto (creo que estaba en el servidor) incluso aunque el relleno parecía correcto. Resultó que tuve que usar base64.urlsafe_b64decode.
Daniel F
Estoy de acuerdo en que no responde a la pregunta, rdb, pero era exactamente lo que necesitaba escuchar también. Reformulé la respuesta con un tono un poco más agradable, espero que esto funcione para ti, Daniel.
Henrik Heimbuerger
Perfectamente bien. No noté que sonaba algo desagradable, solo pensé que sería la solución más rápida si solucionaba el problema y, por esa razón, debería ser lo primero que se debe probar. Gracias por tu cambio, es bienvenido.
Daniel F
Esta respuesta resolvió mi problema de decodificación de un token de acceso de Google derivado de un JWT. Todos los demás intentos dieron como resultado un "relleno incorrecto".
John Hanley
2

Agregar el relleno es bastante ... complicado. Aquí está la función que escribí con la ayuda de los comentarios en este hilo, así como la página wiki para base64 (es sorprendentemente ú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
fuente
2

Puede usarlo simplemente base64.urlsafe_b64decode(data)si está intentando decodificar una imagen web. Se encargará automáticamente del acolchado.

VIÑEDO
fuente
realmente ayuda!
Luna
1

Hay dos formas de corregir los datos de entrada descritos aquí, o, más específicamente y en línea con el OP, hacer que el método b64decode del módulo Python base64 sea capaz de procesar los datos de entrada en algo sin generar una excepción no detectada:

  1. Agregue == al final de los datos de entrada y llame a base64.b64decode (...)
  2. Si eso genera una excepción, entonces

    yo. Cógelo a través de try / except,

    ii. (R?) Quite cualquier = carácter de los datos de entrada (NB, esto puede no ser necesario),

    iii. Agregue A == a los datos de entrada (A == a P == funcionará),

    iv. Llame a base64.b64decode (...) con esos A == - datos de entrada adjuntos

El resultado del elemento 1. o del elemento 2. anterior producirá el resultado deseado.

Advertencias

Esto no garantiza que el resultado decodificado sea el que se codificó originalmente, pero (¿a veces?) Le dará al OP lo suficiente para trabajar:

Incluso con la corrupción, quiero volver al binario porque todavía puedo obtener información útil del flujo ASN.1 ").

Vea lo que sabemos y las suposiciones a continuación.

TL; DR

De algunas pruebas rápidas de base64.b64decode (...)

  1. parece que ignora los caracteres que no son [A-Za-z0-9 + /]; que incluye ignorar = s a menos que sean los últimos caracteres en un grupo analizado de cuatro, en cuyo caso = s terminan la decodificación (a = b = c = d = da el mismo resultado que abc =, y a = = b == c == da el mismo resultado que ab ==).

  2. También parece que todos los caracteres añadidos se ignoran después del punto en el que base64.b64decode (...) termina la decodificación, por ejemplo, de an = como el cuarto de un grupo.

Como se señaló en varios comentarios anteriores, hay cero, uno o dos, = s de relleno requeridos al final de los datos de entrada para cuando el valor de [número de caracteres analizados hasta ese punto módulo 4] es 0 o 3, o 2, respectivamente. Por lo tanto, de los elementos 3. y 4. anteriores, agregar dos o más = s a los datos de entrada corregirá cualquier problema de [relleno incorrecto] en esos casos.

SIN EMBARGO, la decodificación no puede manejar el caso donde el [número total de caracteres analizados módulo 4] es 1, porque se necesitan al menos dos caracteres codificados para representar el primer byte decodificado en un grupo de tres bytes decodificados. En datos de entrada codificados no corruptos, este caso [N módulo 4] = 1 nunca ocurre, pero como el OP indicó que pueden faltar caracteres, podría suceder aquí. Es por eso que simplemente agregar = s no siempre funcionará, y por qué agregar A == funcionará cuando agregar == no. NB El uso de [A] es casi arbitrario: agrega solo bits borrados (cero) al decodificado, lo que puede ser correcto o no, pero entonces el objeto aquí no es la corrección sino la finalización por base64.b64decode (...) sin excepciones .

Lo que sabemos del OP y especialmente de los comentarios posteriores es

  • Se sospecha que faltan datos (caracteres) en los datos de entrada codificados en Base64
  • La codificación Base64 utiliza los 64 valores de posición estándar más el relleno: AZ; Arizona; 0-9; +; /; = es relleno. Esto se confirma, o al menos sugiere, por el hecho de que openssl enc ...funciona.

Supuestos

  • Los datos de entrada contienen solo datos ASCII de 7 bits
  • El único tipo de corrupción son los datos de entrada codificados que faltan
  • El OP no se preocupa por los datos de salida decodificados en ningún momento después del correspondiente a los datos de entrada codificados que faltan

Github

Aquí hay un contenedor para implementar esta solución:

https://github.com/drbitboy/missing_b64

Brian Carcich
fuente
1

El error de relleno incorrecto se debe a que, a veces, los metadatos también están presentes en la cadena codificada.Si su cadena se parece a: 'data: image / png; base64, ... base 64 cosas ...', entonces debe eliminar la primera parte antes de decodificarlo.

Digamos que si tiene una cadena codificada en base64 de imagen, intente el siguiente fragmento.

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

Simplemente agregue caracteres adicionales como "=" o cualquier otro y conviértalo en múltiplo de 4 antes de intentar decodificar el valor de la cadena 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
fuente
0

En caso de que este error provenga de un servidor web: intente codificar la URL de su valor de publicación. Estaba publicando a través de "curl" y descubrí que no estaba codificando url mi valor base64 por lo que los caracteres como "+" no se escaparon, por lo que la lógica de decodificación de URL del servidor web ejecutó automáticamente la decodificación de URL y convirtió + en espacios.

"+" es un carácter base64 válido y quizás el único carácter que se estropea por una decodificación de URL inesperada.

Curtis Yallop
fuente
0

En mi caso, me enfrenté a ese error al analizar un correo electrónico. Obtuve el archivo adjunto como cadena base64 y lo extraje a través de re.search. Finalmente, hubo una extraña subcadena adicional al final.

dHJhaWxlcgo8PCAvU2l6ZSAxNSAvUm9vdCAxIDAgUiAvSW5mbyAyIDAgUgovSUQgWyhcMDAyXDMz
MHtPcFwyNTZbezU/VzheXDM0MXFcMzExKShcMDAyXDMzMHtPcFwyNTZbezU/VzheXDM0MXFcMzEx
KV0KPj4Kc3RhcnR4cmVmCjY3MDEKJSVFT0YK

--_=ic0008m4wtZ4TqBFd+sXC8--

Cuando eliminé --_=ic0008m4wtZ4TqBFd+sXC8--y eliminé la cadena, se solucionó el análisis.

Entonces, mi consejo es asegurarse de que está decodificando una cadena base64 correcta.

Daniil Mashkin
fuente
0

Deberías usar

base64.b64decode(b64_string, ' /')

Por defecto, las altchars son '+/'.

Quoc
fuente
1
Eso no funciona en Python 3.7. afirmar len (altchars) == 2, repr (altchars)
Dat TT
0

Me encontré con este problema también y nada funcionó. Finalmente logré encontrar la solución que funciona para mí. Tenía contenido comprimido en base64 y esto le sucedió a 1 de cada millón de registros ...

Esta es una versión de la solución sugerida por Simon Sapin.

En caso de que falten 3 en el relleno, elimino los últimos 3 caracteres.

En lugar de "0gA1RD5L / 9AUGtH9MzAwAAA =="

Obtenemos "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 acuerdo con esta respuesta Trailing As en base64, la razón es nula. Pero todavía no tengo idea de por qué el codificador estropea esto ...

Mitzi
fuente