Encoder et décoder une chaîne
Les fonctions Base64 de Python travaillent sur des octets : le texte doit d’abord être encodé en UTF-8, puis décodé à la fin. Oublier l’une de ces étapes est l’erreur la plus courante.
import base64
s = "héllo wörld"
encoded = base64.b64encode(s.encode("utf-8")).decode("ascii")
print(encoded) # aMOpbGxvIHfDtnJsZA==
decoded = base64.b64decode(encoded).decode("utf-8")
print(decoded) # héllo wörld
b64decode accepte str comme bytes, mais b64encode ne prend que des objets de type bytes. Lui passer une chaîne lève TypeError: a bytes-like object is required, not 'str'.
Pour vérifier un résultat à la main, collez-le dans le décodeur Base64 : il fonctionne localement dans le navigateur.
Base64 URL-safe avec et sans remplissage
L’alphabet URL-safe remplace + par - et / par _ (voir Base64 ou Base64URL). Python conserve le remplissage = ; supprimez-le vous-même si le format cible (JWT, de nombreuses API) n’en veut pas.
data = b"<<???>>"
print(base64.b64encode(data)) # b'PDw/Pz8+Pg=='
print(base64.urlsafe_b64encode(data)) # b'PDw_Pz8-Pg=='
print(base64.urlsafe_b64encode(data).rstrip(b"=")) # b'PDw_Pz8-Pg'
Pour décoder une entrée sans remplissage, il faut le remettre, car Python exige une longueur multiple de 4 :
def b64url_decode(s: str) -> bytes:
return base64.urlsafe_b64decode(s + "=" * (-len(s) % 4))
print(b64url_decode("PDw_Pz8-Pg")) # b'<<???>>'
b64encode et b64decode acceptent aussi un argument altchars, une chaîne de deux octets qui remplace +/. altchars=b"-_" équivaut aux fonctions URL-safe.
print(base64.b64decode("PDw_Pz8-Pg==", altchars="-_")) # b'<<???>>'
Encoder et décoder un fichier
Pour les fichiers qui tiennent en mémoire, lisez les octets et encodez-les en un seul appel :
from pathlib import Path
b64 = base64.b64encode(Path("hello.txt").read_bytes()).decode("ascii")
print(b64) # aGVsbG8K
Path("hello-copy.txt").write_bytes(base64.b64decode(b64))
Pour une conversion ponctuelle sans écrire de code, l’outil fichier en Base64 fait la même chose dans le navigateur.
Gros fichiers et flux
Base64 transforme chaque groupe de 3 octets d’entrée en 4 caractères de sortie : vous pouvez donc encoder un flux par morceaux, à condition que chaque morceau sauf le dernier soit un multiple de 3 octets. Aucun remplissage n’apparaît alors au milieu, et la sortie concaténée est identique à celle d’un encodage en un seul appel.
CHUNK = 3 * 1024 * 256 # 768 KiB, a multiple of 3
with open("big.bin", "rb") as src, open("big.b64", "wb") as dst:
while chunk := src.read(CHUNK):
dst.write(base64.b64encode(chunk))
Nous l’avons comparé à base64 -w 0 sur un fichier aléatoire de 100 Ko : identique octet pour octet. Le décodage par morceaux fonctionne de la même façon avec des tailles multiples de 4 caractères, à condition que l’entrée ne contienne pas de sauts de ligne.
Découpage des lignes MIME
L’e-mail et certains systèmes anciens veulent des lignes de 76 caractères au plus (RFC 2045). encodebytes le fait, et base64.encode / base64.decode le font entre objets fichier :
print(base64.encodebytes(b"x" * 60))
# b'eHh4eHh4...eHh4\neHh4\n' (76 chars, newline, rest, newline)
with open("big.bin", "rb") as src, open("big.mime", "wb") as dst:
base64.encode(src, dst)
with open("big.mime", "rb") as src, open("big.out", "wb") as dst:
base64.decode(src, dst)
Notez le saut de ligne final : encodebytes en ajoute toujours un. Pour une version en ligne, voir l’encodeur Base64 MIME.
Valider une entrée
Par défaut, b64decode est tolérant : il jette tout caractère hors de l’alphabet avant de décoder. Cela masque des bugs. Passez validate=True pour le rendre strict.
import binascii
print(base64.b64decode("aGk=!!")) # b'hi' (junk ignored)
try:
base64.b64decode("aGk=!!", validate=True)
except binascii.Error as e:
print(e) # Only base64 data is allowed
Avec validate=True, les sauts de ligne comptent aussi comme invalides : supprimez d’abord les retours à la ligne MIME. Voici les erreurs que vous verrez, toutes de type binascii.Error (une sous-classe de ValueError) :
| Entrée | Message |
|---|---|
aGk | Incorrect padding |
a | Invalid base64-encoded string: number of data characters (1) cannot be 1 more than a multiple of 4 |
aGk== avec validate=True | Excess padding not allowed |
aGk=!! avec validate=True | Only base64 data is allowed |
Le guide des erreurs Base64 explique la cause de chacune, et le validateur Base64 montre où une chaîne casse.
Pièges courants
Une entrée URL-safe passée à b64decode. Sans validate=True, les caractères - et _ sont supprimés en silence, et vous obtenez des octets sans queue ni tête au lieu d’une erreur :
print(base64.b64decode("PDw_Pz8-Pg==")) # b'<<\x0f\xcf\xc3\xe0'
print(base64.urlsafe_b64decode("PDw_Pz8-Pg==")) # b'<<???>>'
urlsafe_b64decode accepte aussi l’entrée standard : c’est le choix par défaut le plus sûr quand vous ne savez pas quel alphabet vous avez reçu.
Décoder du binaire comme du texte. Si le contenu est une image ou un ZIP, .decode("utf-8") échoue avec UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 0: invalid start byte. Gardez-le en octets et écrivez-le dans un fichier.
Le b'...' dans la sortie. Afficher la valeur de retour de b64encode montre un littéral bytes. Si ce b' se retrouve dans du JSON ou un en-tête, vous avez oublié .decode("ascii").
Considérer Base64 comme une protection. C’est un encodage, et n’importe qui peut l’inverser. Voir encodage ou chiffrement.
Notes de version
base64.b64encode,b64decode,urlsafe_*etaltcharsexistent dans toutes les versions de Python 3.b64decodeaccepte une entréestrASCII (et pas seulement des octets) depuis Python 3.3.encodestringetdecodestringont été supprimés en 3.9 ; utilisezencodebytesetdecodebytes.binascii.b2a_base64(data, newline=False)(3.6+) est la fonction de bas niveau derrièreb64encodeet renvoieb'aGk='pourb"hi".- Le même module propose aussi
b32encode,b16encode,b85encodeeta85encodepour d’autres alphabets.
FAQ
Pourquoi b64encode renvoie-t-il b'...' au lieu d'une chaîne ?
En Python, les fonctions Base64 prennent des octets et renvoient des octets. Appelez .decode("ascii") sur le résultat pour obtenir une str, et .encode("utf-8") sur le texte avant de l’encoder.
Comment décoder en Python du Base64 sans remplissage ?
Remettez les = manquants avant de décoder : s + "=" * (-len(s) % 4). Python refuse une entrée sans remplissage avec binascii.Error: Incorrect padding.
Quelle est la différence entre b64decode et urlsafe_b64decode ?
urlsafe_b64decode convertit - et _ en + et / avant de décoder. Le simple b64decode supprime sans rien dire - et _, considérés comme des déchets : vous obtenez donc des octets erronés au lieu d’une erreur, sauf si vous passez validate=True.
base64.encodestring est-il toujours disponible ?
Non. encodestring et decodestring ont été supprimés dans Python 3.9. Utilisez encodebytes et decodebytes pour une sortie de style MIME avec des sauts de ligne.