Python: Ignorez l'erreur de `` remplissage incorrect '' lors du décodage en base64

111

J'ai des données encodées en base64 que je souhaite reconvertir en binaire même s'il y a une erreur de remplissage. Si j'utilise

base64.decodestring(b64_string)

il déclenche une erreur de «remplissage incorrect». Y a-t-il un autre moyen?

MISE À JOUR: Merci pour tous les commentaires. Pour être honnête, toutes les méthodes mentionnées sonnaient un peu hasardeuses, alors j'ai décidé d'essayer openssl. La commande suivante a fonctionné un régal:

openssl enc -d -base64 -in b64string -out binary_data
FunLovinCoder
la source
5
Avez-vous vraiment essayé d' utiliser base64.b64decode(strg, '-_')? C'est a priori, sans que vous vous souciez de fournir des exemples de données, la solution Python la plus probable à votre problème. Les «méthodes» proposées étaient des suggestions DEBUG, NÉCESSAIREMENT «aléatoires» étant donné la rareté des informations fournies.
John Machin
2
@John Machin: Oui, j'ai essayé votre méthode mais cela n'a pas fonctionné. Les données sont confidentielles pour l'entreprise.
FunLovinCoder
3
Essayezbase64.urlsafe_b64decode(s)
Daniel F
Pourriez-vous fournir le résultat de ceci: sorted(list(set(b64_string)))s'il vous plaît? Sans révéler quoi que ce soit de confidentiel à l'entreprise, cela devrait révéler quels caractères ont été utilisés pour encoder les données d'origine, qui à leur tour peuvent fournir suffisamment d'informations pour fournir une solution non aléatoire.
Brian Carcich le
Oui, je sais que c'est déjà résolu, mais, pour être honnête, la solution openssl me semble également aléatoire.
Brian Carcich le

Réponses:

79

Comme indiqué dans d'autres réponses, les données base64 peuvent être corrompues de différentes manières.

Cependant, comme le dit Wikipedia , la suppression du remplissage (les caractères '=' à la fin des données encodées en base64) est "sans perte":

D'un point de vue théorique, le caractère de remplissage n'est pas nécessaire, car le nombre d'octets manquants peut être calculé à partir du nombre de chiffres en Base64.

Donc, si c'est vraiment la seule chose "qui ne va pas" avec vos données base64, le remplissage peut simplement être rajouté. Je suis venu avec ceci pour être en mesure d'analyser les URL de "données" dans WeasyPrint, dont certaines étaient en base64 sans remplissage:

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)

Tests pour cette fonction: weasyprint / tests / test_css.py # L68

Simon Sapin
la source
2
Remarque: ASCII pas Unicode, donc pour être sûr, vous voudrez peut-êtrestr(data)
MarkHu
4
C'est bien avec une mise en garde. base64.decodestring est obsolète, utilisez base64.b64_decode
ariddell
2
Pour clarifier sur @ariddell, le commentaire base64.decodestringest obsolète pour base64.decodebytesPy3, mais pour la compatibilité des versions, il est préférable de l'utiliser base64.b64decode.
Cas du
Étant donné que le base64module ignore les caractères non base64 non valides dans l'entrée, vous devez d'abord normaliser les données. Supprimez tout ce qui n'est pas une lettre, un chiffre /ou +, puis ajoutez le remplissage.
Martijn Pieters
39

Ajoutez simplement un rembourrage au besoin. Tenez cependant compte de l'avertissement de Michael.

b64_string += "=" * ((4 - len(b64_string) % 4) % 4) #ugh
badp
la source
1
Il y a sûrement quelque chose de plus simple qui mappe 0 à 0, 2 à 1 et 1 à 2.
badp
2
Pourquoi passez-vous à un multiple de 3 au lieu de 4?
Michael Mrozek
C'est ce que semble impliquer l'article de wikipedia sur base64.
badp
1
@bp: Dans le codage base64, chaque entrée binaire de 24 bits (3 octets) est codée comme une sortie de 4 octets. output_len% 3 n'a aucun sens.
John Machin
8
Le simple fait d'ajouter ===fonctionne toujours. Tous les =caractères supplémentaires sont apparemment supprimés en toute sécurité par Python.
Acumenus
32

Il semble que vous ayez juste besoin d'ajouter un remplissage à vos octets avant le décodage. Il y a beaucoup d'autres réponses à cette question, mais je tiens à souligner que (au moins en Python 3.x) base64.b64decodetronquera tout rembourrage supplémentaire, à condition qu'il y en ait assez en premier lieu.

Donc, quelque chose comme: b'abc='fonctionne aussi bien que b'abc=='(comme le fait b'abc=====').

Cela signifie que vous pouvez simplement ajouter le nombre maximum de caractères de remplissage dont vous auriez besoin - qui est de trois ( b'===') - et base64 tronquera tous les caractères inutiles.

Cela vous permet d'écrire:

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

ce qui est plus simple que:

base64.b64decode(s + b'=' * (-len(s) % 4))
Henry Woody
la source
1
Ok, ce n'est pas trop "moche" merci :) Au fait, je pense que vous n'avez jamais besoin de plus de 2 caractères de remplissage. L'algorithme Base64 fonctionne sur des groupes de 3 caractères à la fois et n'a besoin de remplissage que lorsque votre dernier groupe de caractères ne fait que 1 ou 2 caractères.
Otto
@Otto le remplissage ici est pour le décodage, qui fonctionne sur des groupes de 4 caractères. L' encodage Base64 fonctionne sur des groupes de 3 caractères :)
Henry Woody
mais si vous savez que pendant l'encodage, 2 au maximum seront ajoutés, ce qui peut devenir "perdu" plus tard, vous obligeant à les rajouter avant le décodage, alors vous savez que vous n'aurez besoin d'ajouter qu'au maximum 2 pendant le décodage. #ChristmasTimeArgumentForTheFunOfIt
Otto
@Otto, je crois que vous avez raison. Alors qu'une chaîne codée en base64 avec une longueur, par exemple, 5 nécessiterait 3 caractères de remplissage, une chaîne de longueur 5 n'est même pas une longueur valide pour une chaîne codée en base64. Vous obtiendrez l'erreur: binascii.Error: Invalid base64-encoded string: number of data characters (5) cannot be 1 more than a multiple of 4. Merci de l'avoir signalé!
Henry Woody
24

«Remplissage incorrect» peut signifier non seulement «remplissage manquant» mais aussi (croyez-le ou non) «remplissage incorrect».

Si les méthodes "d'ajout de remplissage" suggérées ne fonctionnent pas, essayez de supprimer quelques octets de fin:

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

Mise à jour: Tout tripotage autour de l'ajout de remplissage ou de la suppression des octets éventuellement incorrects de la fin doit être effectué APRÈS avoir supprimé les espaces, sinon les calculs de longueur seront perturbés.

Ce serait une bonne idée si vous nous montriez un (court) échantillon des données que vous devez récupérer. Modifiez votre question et copiez / collez le résultat de print repr(sample) .

Mise à jour 2: il est possible que l'encodage ait été effectué de manière sécurisée pour les URL. Si tel est le cas, vous pourrez voir des caractères moins et des traits de soulignement dans vos données, et vous devriez pouvoir les décoder en utilisantbase64.b64decode(strg, '-_')

Si vous ne pouvez pas voir les caractères moins et les traits de soulignement dans vos données, mais que vous pouvez voir les caractères plus et les barres obliques, vous avez un autre problème et vous aurez peut-être besoin des astuces add-padding ou remove-cruft.

Si vous ne voyez aucun signe moins, trait de soulignement, plus et barre oblique dans vos données, vous devez déterminer les deux caractères alternatifs; ce seront ceux qui ne sont pas dans [A-Za-z0-9]. Ensuite, vous devrez expérimenter pour voir dans quel ordre ils doivent être utilisés dans le 2ème argument debase64.b64decode()

Mise à jour 3 : Si vos données sont "confidentielles de l'entreprise":
(a) vous devriez le dire dès le départ
(b) nous pouvons explorer d'autres voies pour comprendre le problème, qui est très probablement lié aux caractères utilisés à la place +et /dans l'alphabet de codage, ou par un autre formatage ou des caractères superflus.

Une de ces possibilités serait d'examiner quels caractères non "standard" sont dans vos données, par exemple

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
la source
Les données sont constituées du jeu de caractères standard base64. Je suis presque sûr que le problème est dû au fait qu'un ou plusieurs caractères manquent - d'où l'erreur de remplissage. À moins qu'il n'y ait une solution robuste en Python, j'irai avec ma solution d'appeler openssl.
FunLovinCoder
1
Une «solution» qui ignore silencieusement les erreurs ne mérite guère le terme «robuste». Comme je l'ai mentionné plus tôt, les différentes suggestions Python étaient des méthodes de DÉBOGAGE pour découvrir quel est le problème, préparatoire à une solution PRINCIPÉE ... n'est-ce pas intéressé par une telle chose?
John Machin
7
Mon exigence n'est PAS de résoudre le problème de savoir pourquoi la base64 est corrompue - elle provient d'une source sur laquelle je n'ai aucun contrôle. Mon exigence est de fournir des informations sur les données reçues même si elles sont corrompues. Une façon de le faire est d'obtenir les données binaires de la base64 corrompue afin que je puisse glaner des informations à partir de l'ASN.1 sous-jacent. courant. J'ai posé la question d'origine parce que je voulais une réponse à cette question et non la réponse à une autre question - comme comment déboguer base64 corrompu.
FunLovinCoder
Normalisez simplement la chaîne, supprimez tout ce qui n'est pas un caractère Base64. N'importe où, pas seulement le début ou la fin.
Martijn Pieters
24

Utilisation

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

Le crédit va à un commentaire quelque part ici.

>>> 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
la source
4
Il veut dire ce commentaire: stackoverflow.com/questions/2941995/…
jackyalcine
22

S'il y a une erreur de remplissage, cela signifie probablement que votre chaîne est corrompue; Les chaînes codées en base64 doivent avoir un multiple de quatre longueurs. Vous pouvez essayer d'ajouter le caractère de remplissage ( =) vous-même pour rendre la chaîne un multiple de quatre, mais il devrait déjà l'avoir à moins que quelque chose ne va pas

Michael Mrozek
la source
Les données binaires sous-jacentes sont ASN.1. Même avec la corruption, je veux revenir au binaire car je peux toujours obtenir des informations utiles du flux ASN.1.
FunLovinCoder
pas vrai, si vous voulez décoder un jwt pour les contrôles de sécurité, vous en aurez besoin
DAG
4

Consultez la documentation de la source de données que vous essayez de décoder. Est-il possible que vous vouliez utiliser à la base64.urlsafe_b64decode(s)place de base64.b64decode(s)? C'est l'une des raisons pour lesquelles vous avez peut-être vu ce message d'erreur.

Décodez les chaînes en utilisant un alphabet sécurisé pour les URL, qui remplace - au lieu de + et _ au lieu de / dans l'alphabet Base64 standard.

C'est par exemple le cas pour diverses API de Google, comme la boîte à outils d'identité de Google et les charges utiles Gmail.

Daniel F
la source
1
Cela ne répond pas du tout à la question. De plus, urlsafe_b64decodenécessite également un rembourrage.
rdb
Eh bien, il y avait un problème que j'avais avant de répondre à cette question, qui était lié à la boîte à outils d'identité de Google. J'obtenais l'erreur de remplissage incorrecte (je crois que c'était sur le serveur) même si le rembourrage semblait correct. Il s'est avéré que je devais utiliser base64.urlsafe_b64decode.
Daniel F
Je conviens que cela ne répond pas à la question, rdb, pourtant c'était exactement ce que j'avais besoin d'entendre aussi. J'ai reformulé la réponse sur un ton un peu plus gentil, j'espère que cela fonctionne pour vous, Daniel.
Henrik Heimbuerger
Parfaitement bien. Je n'ai pas remarqué que cela semblait quelque peu désagréable, je pensais seulement que ce serait la solution la plus rapide si cela résoudrait le problème et, pour cette raison, devrait être la première chose à essayer. Merci pour votre changement, c'est bienvenu.
Daniel F
Cette réponse a résolu mon problème de décodage d'un jeton d'accès Google dérivé d'un JWT. Toutes les autres tentatives ont abouti à un "remplissage incorrect".
John Hanley
2

L'ajout du rembourrage est plutôt ... délicat. Voici la fonction que j'ai écrite à l'aide des commentaires de ce fil ainsi que de la page wiki pour base64 (c'est étonnamment 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)
Bryan Lott
la source
2

Vous pouvez simplement utiliser base64.urlsafe_b64decode(data)si vous essayez de décoder une image Web. Il s'occupera automatiquement du rembourrage.

VIGNE
la source
ça aide vraiment!
Lune le
1

Il y a deux façons de corriger les données d'entrée décrites ici, ou, plus spécifiquement et en ligne avec l'OP, de rendre la méthode b64decode du module Python base64 capable de traiter les données d'entrée en quelque chose sans lever d'exception non interceptée:

  1. Ajoutez == à la fin des données d'entrée et appelez base64.b64decode (...)
  2. Si cela soulève une exception, alors

    je. Attrapez-le via try / except,

    ii. (R?) Supprimez tous les caractères = des données d'entrée (NB cela peut ne pas être nécessaire),

    iii. Ajoutez A == aux données d'entrée (A == à P == fonctionnera),

    iv. Appelez base64.b64decode (...) avec ces A == - données d'entrée ajoutées

Le résultat de l'élément 1. ou de l'élément 2. ci-dessus donnera le résultat souhaité.

Mises en garde

Cela ne garantit pas que le résultat décodé sera ce qui a été encodé à l'origine, mais cela donnera (parfois?) À l'OP suffisamment pour travailler avec:

Même avec la corruption, je veux revenir au binaire car je peux toujours obtenir des informations utiles du flux ASN.1 ").

Voir ce que nous savons et hypothèses ci-dessous.

TL; DR

De quelques tests rapides de base64.b64decode (...)

  1. il semble qu'il ignore les caractères non- [A-Za-z0-9 + /]; cela inclut ignorer = s à moins qu'il ne s'agisse du (des) dernier (s) caractère (s) d'un groupe analysé de quatre, auquel cas les = s terminent le décodage (a = b = c = d = donne le même résultat que abc =, et a = = b == c == donne le même résultat que ab ==).

  2. Il apparaît également que tous les caractères ajoutés sont ignorés après le point où base64.b64decode (...) termine le décodage, par exemple à partir d'un = comme quatrième d'un groupe.

Comme indiqué dans plusieurs commentaires ci-dessus, il y a soit zéro, soit un, ou deux = s de remplissage requis à la fin des données d'entrée lorsque la valeur [nombre de caractères analysés jusqu'à ce point modulo 4] est 0, ou 3, ou 2, respectivement. Ainsi, à partir des éléments 3. et 4. ci-dessus, l'ajout de deux ou plusieurs = s aux données d'entrée corrigera tout problème de [remplissage incorrect] dans ces cas.

CEPENDANT, le décodage ne peut pas gérer le cas où le [nombre total de caractères analysés modulo 4] est 1, car il faut au moins deux caractères codés pour représenter le premier octet décodé dans un groupe de trois octets décodés. Dans les données d'entrée codées non corrompues, ce cas [N modulo 4] = 1 ne se produit jamais, mais comme l'OP a déclaré que des caractères peuvent être manquants, cela peut se produire ici. C'est pourquoi simplement ajouter = s ne fonctionnera pas toujours, et pourquoi ajouter A == fonctionnera lorsque l'ajout == ne fonctionnera pas. NB Utiliser [A] est tout sauf arbitraire: il n'ajoute que des bits effacés (zéro) au décodé, ce qui peut être correct ou pas, mais alors l'objet ici n'est pas l'exactitude mais la complétion par base64.b64decode (...) sans exceptions .

Ce que nous savons du PO et surtout des commentaires ultérieurs, c'est

  • On soupçonne qu'il manque des données (caractères) dans les données d'entrée encodées en Base64
  • L'encodage Base64 utilise les 64 valeurs de position standard plus le remplissage: AZ; az; 0-9; +; /; = est un remplissage. Ceci est confirmé, ou du moins suggéré, par le fait que cela openssl enc ...fonctionne.

Hypothèses

  • Les données d'entrée contiennent uniquement des données ASCII 7 bits
  • Le seul type de corruption est l'absence de données d'entrée codées
  • L'OP ne se soucie pas des données de sortie décodées à tout moment après celui correspondant aux données d'entrée codées manquantes

Github

Voici un wrapper pour implémenter cette solution:

https://github.com/drbitboy/missing_b64

Brian Carcich
la source
1

Une erreur de remplissage incorrecte est causée car parfois, des métadonnées sont également présentes dans la chaîne codée.Si votre chaîne ressemble à quelque chose comme: 'data: image / png; base64, ... base 64 stuff ....' alors vous devez supprimer le premier partie avant de le décoder.

Dites si vous avez une chaîne encodée en base64 d'image, puis essayez ci-dessous l'extrait de code.

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
la source
0

Ajoutez simplement des caractères supplémentaires comme "=" ou tout autre et faites-en un multiple de 4 avant d'essayer de décoder la valeur de la chaîne cible. Quelque chose comme;

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
la source
0

Dans le cas où cette erreur proviendrait d'un serveur Web: essayez de coder l'URL de votre valeur de publication. J'étais en train de POSTER via "curl" et j'ai découvert que je n'encodais pas ma valeur base64, donc les caractères comme "+" n'étaient pas échappés, de sorte que la logique de décodage d'url du serveur Web exécutait automatiquement le décodage d'url et convertissait + en espaces.

"+" est un caractère base64 valide et peut-être le seul caractère qui est mutilé par un décodage d'URL inattendu.

Curtis Yallop
la source
0

Dans mon cas, j'ai rencontré cette erreur lors de l'analyse d'un e-mail. J'ai obtenu la pièce jointe sous forme de chaîne base64 et je l'ai extraite via re.search. Finalement, il y avait une étrange sous-chaîne supplémentaire à la fin.

dHJhaWxlcgo8PCAvU2l6ZSAxNSAvUm9vdCAxIDAgUiAvSW5mbyAyIDAgUgovSUQgWyhcMDAyXDMz
MHtPcFwyNTZbezU/VzheXDM0MXFcMzExKShcMDAyXDMzMHtPcFwyNTZbezU/VzheXDM0MXFcMzEx
KV0KPj4Kc3RhcnR4cmVmCjY3MDEKJSVFT0YK

--_=ic0008m4wtZ4TqBFd+sXC8--

Quand j'ai supprimé --_=ic0008m4wtZ4TqBFd+sXC8-- et dépouillé la chaîne, l'analyse a été corrigée.

Je vous conseille donc de vous assurer que vous décodez une chaîne base64 correcte.

Daniil Mashkin
la source
0

Tu devrais utiliser

base64.b64decode(b64_string, ' /')

Par défaut, les altchars sont '+/'.

Quoc
la source
1
Cela ne fonctionne pas en python 3.7. assert len ​​(altchars) == 2, repr (altchars)
Dat TT
0

J'ai également rencontré ce problème et rien n'a fonctionné. J'ai finalement réussi à trouver la solution qui me convient. J'avais compressé du contenu en base64 et cela est arrivé à 1 disque sur un million ...

Il s'agit d'une version de la solution proposée par Simon Sapin.

Dans le cas où le remplissage manque 3, je supprime les 3 derniers caractères.

Au lieu de "0gA1RD5L / 9AUGtH9MzAwAAA =="

Nous obtenons "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)   

Selon cette réponse Trailing Comme dans base64, la raison est null. Mais je n'ai toujours aucune idée de la raison pour laquelle l'encodeur gâche tout ça ...

Mitzi
la source