Pourquoi ai-je besoin de 'b' pour encoder une chaîne avec Base64?

259

En suivant cet exemple de python , j'encode une chaîne en Base64 avec:

>>> import base64
>>> encoded = base64.b64encode(b'data to be encoded')
>>> encoded
b'ZGF0YSB0byBiZSBlbmNvZGVk'

Mais, si je laisse de côté le premier b:

>>> encoded = base64.b64encode('data to be encoded')

J'obtiens l'erreur suivante:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Python32\lib\base64.py", line 56, in b64encode
   raise TypeError("expected bytes, not %s" % s.__class__.__name__)
   TypeError: expected bytes, not str

Pourquoi est-ce?

dublintech
la source
38
En fait, toutes les questions qui renvoient "TypeError: octets attendus, pas str" ont la même réponse.
Lennart Regebro

Réponses:

275

base64 l' encodage des données d'octets binaires de 8 bits et code pour qu'il utilise uniquement les caractères A-Z, a-z, 0-9, +, /* afin qu'il puisse être transmis sur les canaux qui ne préservent pas les 8 bits de données, tels que le courrier électronique.

Par conséquent, il veut une chaîne d'octets de 8 bits. Vous créez ceux-ci en Python 3 avec la b''syntaxe.

Si vous supprimez le b, il devient une chaîne. Une chaîne est une séquence de caractères Unicode. base64 ne sait pas quoi faire avec les données Unicode, ce n'est pas du 8 bits. Ce n'est pas vraiment des morceaux, en fait. :-)

Dans votre deuxième exemple:

>>> encoded = base64.b64encode('data to be encoded')

Tous les caractères s'intègrent parfaitement dans le jeu de caractères ASCII, et l'encodage base64 est donc en fait un peu inutile. Vous pouvez le convertir en ascii à la place, avec

>>> encoded = 'data to be encoded'.encode('ascii')

Ou plus simple:

>>> encoded = b'data to be encoded'

Ce serait la même chose dans ce cas.


* La plupart des saveurs base64 peuvent également inclure un =à la fin comme rembourrage. De plus, certaines variantes base64 peuvent utiliser des caractères autres que +et /. Voir le tableau récapitulatif des variantes sur Wikipedia pour un aperçu.

Lennart Regebro
la source
174

Réponse courte

Vous devez pousser un bytes-likeobjet ( bytes, bytearray, etc.) à la base64.b64encode()méthode. Voici deux façons:

>>> data = base64.b64encode(b'data to be encoded')
>>> print(data)
b'ZGF0YSB0byBiZSBlbmNvZGVk'

Ou avec une variable:

>>> string = 'data to be encoded'
>>> data = base64.b64encode(string.encode())
>>> print(data)
b'ZGF0YSB0byBiZSBlbmNvZGVk'

Pourquoi?

Dans Python 3, les strobjets ne sont pas des tableaux de caractères de style C (ils ne sont donc pas des tableaux d'octets), mais plutôt des structures de données qui n'ont pas de codage inhérent. Vous pouvez coder cette chaîne (ou l'interpréter) de différentes manières. L'utf-8 est le plus courant (et par défaut dans Python 3), d'autant plus qu'il est rétrocompatible avec ASCII (bien que, comme le sont les encodages les plus utilisés). C'est ce qui se passe lorsque vous prenez un stringet appelez la .encode()méthode dessus: Python interprète la chaîne dans utf-8 (l'encodage par défaut) et vous fournit le tableau d'octets auquel il correspond.

Encodage Base-64 en Python 3

À l'origine, le titre de la question portait sur l'encodage Base-64. Lisez la suite pour les trucs Base-64.

base64l'encodage prend des morceaux binaires de 6 bits et les encode en utilisant les caractères AZ, az, 0-9, '+', '/' et '=' (certains encodages utilisent des caractères différents à la place de '+' et '/') . Il s'agit d'un codage de caractères basé sur la construction mathématique du système numérique radix-64 ou base-64, mais ils sont très différents. La base-64 en mathématiques est un système de nombres comme binaire ou décimal, et vous effectuez ce changement de radix sur le nombre entier, ou (si le radix que vous convertissez est une puissance de 2 inférieure à 64) en morceaux de droite à la gauche.

En base64encodage, la traduction se fait de gauche à droite; ces 64 premiers caractères sont la raison pour laquelle il est appelé base64 encodage . Le 65ème symbole `` = '' est utilisé pour le remplissage, car l'encodage tire des morceaux de 6 bits mais les données qu'il est généralement destiné à coder sont des octets de 8 bits, donc parfois il n'y a que deux ou 4 bits dans le dernier morceau.

Exemple:

>>> data = b'test'
>>> for byte in data:
...     print(format(byte, '08b'), end=" ")
...
01110100 01100101 01110011 01110100
>>>

Si vous interprétez ces données binaires comme un seul entier, voici comment vous les convertissez en base-10 et base-64 ( tableau pour base-64 ):

base-2:  01 110100 011001 010111 001101 110100 (base-64 grouping shown)
base-10:                            1952805748
base-64:  B      0      Z      X      N      0

base64 cependant, le codage regroupera ces données:

base-2:  011101  000110  010101 110011 011101 00(0000) <- pad w/zeros to make a clean 6-bit chunk
base-10:     29       6      21     51     29      0
base-64:      d       G       V      z      d      A

Donc, 'B0ZXN0' est la version base 64 de notre binaire, mathématiquement parlant. Cependant, l' base64 encodage doit effectuer l'encodage dans la direction opposée (de sorte que les données brutes sont converties en 'dGVzdA') et a également une règle pour indiquer aux autres applications combien d'espace est laissé à la fin. Cela se fait en remplissant la fin avec des symboles «=». Ainsi, le base64codage de ces données est 'dGVzdA ==', avec deux symboles '=' pour signifier deux paires de bits devront être supprimés de la fin lorsque ces données seront décodées pour les faire correspondre aux données d'origine.

Testons cela pour voir si je suis malhonnête:

>>> encoded = base64.b64encode(data)
>>> print(encoded)
b'dGVzdA=='

Pourquoi utiliser l' base64encodage?

Disons que je dois envoyer des données à quelqu'un par e-mail, comme ces données:

>>> data = b'\x04\x6d\x73\x67\x08\x08\x08\x20\x20\x20'
>>> print(data.decode())

>>> print(data)
b'\x04msg\x08\x08\x08   '
>>>

Il y a deux problèmes que j'ai plantés:

  1. Si j'essayais d'envoyer cet e-mail sous Unix, l'e-mail serait envoyé dès que le \x04caractère aurait été lu, car il s'agit de ASCII pour END-OF-TRANSMISSION(Ctrl-D), de sorte que les données restantes seraient exclues de la transmission.
  2. De plus, bien que Python soit suffisamment intelligent pour échapper à tous mes mauvais caractères de contrôle lorsque j'imprime directement les données, lorsque cette chaîne est décodée en ASCII, vous pouvez voir que le «msg» n'est pas là. C'est parce que j'ai utilisé trois BACKSPACEcaractères et trois SPACEcaractères pour effacer le «msg». Ainsi, même si je n'avais pas le EOFcaractère là-bas, l'utilisateur final ne serait pas en mesure de traduire du texte à l'écran vers les vraies données brutes.

Ceci est juste une démo pour vous montrer combien il peut être difficile d'envoyer simplement des données brutes. L'encodage des données au format base64 vous donne exactement les mêmes données mais dans un format qui garantit qu'elles peuvent être envoyées sur des supports électroniques tels que les e-mails en toute sécurité.

Greg Schmit
la source
6
base64.b64encode(s.encode()).decode()n'est pas très pythonique quand tout ce que vous voulez est une conversion de chaîne en chaîne. base64.encode(s)devrait être suffisant au moins en python3. Merci pour une très bonne explication sur les chaînes et les octets en python
MortenB
2
@MortenB Oui, c'est bizarre, mais le côté positif est très clair ce qui se passe tant que l'ingénieur est conscient de la différence entre les tableaux d'octets et les chaînes, car il n'y a pas un seul mappage (encodage) entre eux, comme d'autres langues présumer.
Greg Schmit
3
@MortenB Soit dit en passant, base64.encode(s)ne fonctionnerait pas en Python3; dites-vous que quelque chose comme ça devrait être disponible? Je pense que la raison pour laquelle cela peut être déroutant est que, selon l'encodage et le contenu de la chaîne, il se speut qu'il n'y ait pas 1 représentation unique en tant que tableau d'octets.
Greg Schmit
Schmitt: ce n'était qu'un exemple de la simplicité. les cas d'utilisation les plus courants devraient être comme ça.
MortenB
1
@MortenB mais b64 n'est pas seulement destiné au texte, tout contenu binaire peut être encodé en b64 (audio, images, etc.). Le faire fonctionner comme vous le proposez à mon avis masque encore plus la différence entre le texte et le tableau d'octets, ce qui rend le débogage plus difficile. Cela déplace simplement la difficulté ailleurs.
Michael Ekoka
32

Si les données à encoder contiennent des caractères "exotiques", je pense que vous devez encoder en "UTF-8"

encoded = base64.b64encode (bytes('data to be encoded', "utf-8"))
Alecz
la source
24

Si la chaîne est Unicode, le moyen le plus simple est:

import base64                                                        

a = base64.b64encode(bytes(u'complex string: ñáéíóúÑ', "utf-8"))

# a: b'Y29tcGxleCBzdHJpbmc6IMOxw6HDqcOtw7PDusOR'

b = base64.b64decode(a).decode("utf-8", "ignore")                    

print(b)
# b :complex string: ñáéíóúÑ
Alfredocambera
la source
Vraiment pas le moyen le plus simple, mais l'un des moyens les plus clairs, quand il est important de savoir quel codage est utilisé pour transmettre la chaîne, qui fait partie du "protocole" de la transmission de données via base64.
xuiqzy
12

Il y a tout ce dont vous avez besoin:

expected bytes, not str

Le début brend votre chaîne binaire.

Quelle version de Python utilisez-vous? 2.x ou 3.x?

Edit: Voir http://docs.python.org/release/3.0.1/whatsnew/3.0.html#text-vs-data-instead-of-unicode-vs-8-bit pour les détails sanglants des chaînes en Python 3.x


la source
Merci que j'utilise, 3.x. Pourquoi Python veut-il le convertir explicitement en binaire. La même chose dans Ruby serait ... nécessite> "base64" puis> Base64.encode64 ('données à encoder')
dublintech
2
@dublintech Parce que le texte (unicode) est différent des données brutes. Si vous vouliez coder une chaîne de texte en Base64, vous devez d'abord déterminer le codage des caractères (comme UTF-8), puis vous avez des octets plutôt que des caractères, que vous pouvez coder sous une forme sécurisée de texte ascii.
fortran
2
Cela ne répond pas à la question. Il sait que cela fonctionne avec un objet octets, mais pas avec un objet chaîne. La question est pourquoi .
Lennart Regebro
@fortran Le codage par défaut de la chaîne Python3 est UTF, je ne sais pas pourquoi il doit être explicitement défini.
xmedeko
0

Ce b signifie simplement que vous prenez l'entrée comme un octet ou un tableau d'octets et non comme une chaîne.

Atul6.Singh
la source