Vous avez à traiter le format Base64 ? Alors ce site est parfait pour vous ! Utilisez notre outil en ligne super pratique pour encoder ou décoder vos données.

Encodage Base64 en Python : un guide complet

Voici l'autre face de la pièce. Vous avez des données entre les mains - un fichier, une paire de mots de passe, un blob binaire, un paragraphe de Unicode - et quelque part en aval, elles doivent voyager à travers un canal qui n'accepte que des lettres. C'est tout le travail du Base64 : réécrire trois octets de données en quatre caractères d'un alphabet de 64 caractères, compléter le groupe final avec des = pour que tout sorte par quatre, et remettre les lettres. La page d'accueil de ce site explique le format en entier, alphabet compris, alors on va s'en tenir à une seule haleine et passer le reste du temps sur ce que Python fait vraiment avec.

La comptabilité mérite une phrase honnête avant de commencer, parce que le chiffre revient dans toutes les conversations sur le sujet : le prix de cette sécurité de texte, c'est la taille. Le Base64 gonfle vos données d'environ un tiers, quatre caractères pour trois octets en entrée, alors un mégaoctet de binaire devient un mégaoctet et un tiers de lettres. Pour un token ou une valeur de config, ce n'est pas un problème ; pour un fichier vidéo, c'est la raison pour laquelle vous devriez réfléchir à vos options.

Et la bonne nouvelle : la réponse de Python à tout ça tient en un import et une fonction. base64.b64encode est dans la bibliothèque standard depuis des décennies, n'a besoin d'aucune installation, et tourne à la vitesse du C sous le capot. Le reste de ce guide est la longue traîne qui rend le one-liner utile dans le monde réel : la règle bytes-only qui stoppe la moitié de tous les bugs TypeError, l'alphabet sûr pour les URL, les outils de coupure de lignes MIME, et les protocoles - JWTs, en-têtes HTTP, poignées de main WebSocket, courrier, PEM, URL data - dans lesquels le Base64 fait discrètement son travail.

Faites connaissance avec b64encode

Le contrat tient en quatre phrases. Une : l'entrée est un objet de type bytes - bytes, bytearray, memoryview - et une chaîne simple est refusée. Deux : la sortie est un objet bytes, jamais un str. Trois : la sortie est toujours complétée avec du padding jusqu'à un multiple de quatre caractères, alors même un seul octet en entrée produit Zg==. Quatre : la sortie est une seule ligne, jamais coupée, quelle que soit la taille de l'entrée. Tout le reste de cet article est un commentaire sur ces quatre phrases :

import base64
encoded = base64.b64encode(b"foobar")
print(encoded)
# b'Zm9vYmFy'
print(len(encoded))
# 8

Pour obtenir une vraie chaîne, pour une URL ou un en-tête ou un champ JSON, décodez le résultat en ASCII. L'alphabet garantit que rien d'autre ne peut s'y trouver, ce qui rend cette étape sûre et bon marché :

import base64
text = base64.b64encode(b"foobar").decode("ascii")
print(text)
# Zm9vYmFy

Il y a un argument de plus dans la signature, altchars, et il échange le + et le / de l'alphabet standard contre une autre paire de caractères. C'est exactement le bouton derrière la variante sûre pour les URL, alors gardez cette idée en tête - vous la rencontrerez dans quelques sections quand nous parlerons des tokens et des chaînes de requête.

Le mur de types : str n'est pas des bytes

Le premier mur propre à Python dans cet article, c'est le système de types, et il vaut la peine d'apprendre à le sentir. b64encode refuse les chaînes avec l'un des messages d'erreur les moins indulgents de la langue :

import base64
try:
  base64.b64encode("hello")
except TypeError as caught:
  print(caught)
# a bytes-like object is required, not 'str'

Le correctif est l'habitude la plus importante de tout ce guide : transformez votre texte en bytes d'abord, et choisissez l'encodage délibérément au lieu d'espérer :

import base64
print(base64.b64encode("été".encode("utf-8")))
# b'w6l0w6k='
print(base64.b64encode("été".encode("utf-16")))
# b'//7pAHQA6QA='

Mêmes caractères, deux chaînes d'octets différentes, deux sorties Base64 différentes. Le choix d'encodage est une décision, pas un détail. UTF-8 est la valeur par défaut pour tout ce qui traversera un câble, une base de données ou une API. UTF-16 pointe le bout de son nez quand vous parlez à des API Windows, et il apporte un byte-order mark en tête que vous ne voulez peut-être pas encoder, ce que vous pouvez éviter en utilisant utf-16-le ou en le coupant avec lstrip("\ufeff"). Latin-1 se cache encore dans les vieux fichiers européens, où un caractère fait exactement un octet et où la question ne se pose jamais. Le modèle mental à garder : l'encodeur ne regarde jamais votre texte ; il ne voit que des bits. Le moment où les bytes traversent le mur, la question du charset est close - ce qui explique aussi pourquoi le côté décodage doit demander, plus tard, qui possédait ce charset.

base64url : échangez deux lettres, jetez le padding

L'alphabet standard cache deux caractères que les URL et les systèmes de fichiers détestent. Le signe + est lu silencieusement comme un espace par n'importe quel décodeur de formulaire, et le signe / est un séparateur de chemin, alors un payload de l'alphabet standard dans une chaîne de requête ou un nom de fichier est une bombe à retardement. La section 5 de la RFC 4648 définit le correctif : une variante où + devient - et / devient _, où le padding est jeté chaque fois que la longueur des données est connue depuis le contexte, et que la RFC insiste pour appeler base64url et pas simplement « base64 ». Vous la croiserez dans les JSON Web Tokens, les tokens OAuth et les paramètres de curseur d'API, ce qui veut dire, dans la plus grande partie du web moderne.

Python fournit à la fois une fonction dédiée et le bouton altchars de la première section, et ils produisent une sortie identique :

import base64
data = b"\xfb\xff\xfe"
print(base64.b64encode(data))
# b'+//+'
print(base64.urlsafe_b64encode(data))
# b'-__-'
print(base64.b64encode(data, altchars=b"-_"))
# b'-__-'

Dans les tokens et les chaînes de requête, le padding disparaît d'habitude aussi, parce qu'un = en fin de chaîne nécessiterait un encodage percent et que certaines middleboxes le cabossent de toute façon :

import base64
padded = base64.urlsafe_b64encode(b"fooba")
print(padded)
# b'Zm9vYmE='
print(padded.rstrip(b"="))
# b'Zm9vYmE'

Coupez, envoyez, et le destinataire remet les pads avec l'astuce du modulo, "=" * (-len(s) % 4), qui produit exactement autant de pads que la longueur en demande. La règle générale : si les données vont se poser dans une URL, un nom de fichier ou un JWT, utilisez la variante urlsafe et jetez les pads ; si elles vont se poser dans un corps de courrier ou un fichier texte, l'alphabet standard avec son padding est la norme.

Quand votre lecteur veut des lignes : MIME et la règle des 76 caractères

La ligne infinie unique de b64encode est parfaite pour les champs JSON, les en-têtes et les URL, mais le courrier a des opinions. La RFC 2045, la norme MIME, exige que la sortie Base64 soit coupée en lignes de 76 caractères au maximum, et les outils legacy de Python ont été construits pour produire exactement ça. encodebytes, ajoutée en Python 3.1, fait la coupure pour un objet bytes :

import base64
wrapped = base64.encodebytes(b"x" * 100)
for line in wrapped.splitlines():
  print(len(line), line[:12])
# 76 eHh4eHh4eHh4
# 60 eHh4eHh4eHh4

La mécanique est un peu mignonne. Le module encode par morceaux de 57 octets, la constante MAXBINSIZE, parce que 57 octets deviennent exactement 76 caractères, et dans les CPython modernes chaque ligne coupée se termine par un simple line feed. La RFC 2045 demandait du CRLF, mais la sortie LF de Python est acceptée par tous les décodeurs de l'écosystème, y compris le sien. La fonction legacy fichier-vers-fichier encode fait la même coupure directement d'un descripteur de fichier à l'autre, ce qui en fait un outil impeccable pour les gros fichiers que vous ne voulez pas garder deux fois en mémoire.

Quel outil quand, en bref : b64encode pour tout ce qui va dans un champ JSON, une URL, un en-tête ou une colonne de base de données ; encodebytes pour les corps de courrier et le blindage façon PEM ; le legacy encode quand vous streamez un gros fichier et voulez la coupure gratuitement. Choisir le mauvais est un bug classique, parce qu'un seul retour à la ligne errant dans un champ JSON suffit pour faire jeter une exception à un décodeur strict de l'autre côté.

La famille élargie

Le module base64 est en réalité le module base-N, et il porte toute la famille de la RFC 4648 plus quelques cousins venus d'autres coins du monde de l'informatique. La plupart sont des alternatives d'une ligne pour le même contrat octets-entrée-octets-sortie :

Fonctions Alphabet Quand vous la croisez
b16encode / b16decode 0-9A-F « Base16 », c'est juste de l'hexadécimal ; l'aller-retour le plus rapide du module, parfait pour les hachages et les UUID
b32encode / b32decode A-Z2-7 clés de licence et codes d'activation ; pas de 0, O, 1 ni I, alors il survit à la lecture à voix haute
b32hexencode / b32hexdecode 0-9A-V du Base32 avec un alphabet hexadécimal, ajouté en Python 3.10 ; garde les données encodées triables lexicographiquement
a85encode / a85decode 85 caractères imprimables l'ASCII85 de PostScript et PDF, le descendant de l'utilitaire btoa de Unix ; dans le module depuis Python 3.4
b85encode / b85decode 85 caractères imprimables le format Base85 utilisé par les diffs binaires de git et Mercurial ; lui aussi depuis Python 3.4
z85encode / z85decode 85 caractères imprimables le Z85 de ZeroMQ, ajouté en Python 3.13 ; cadre les données par groupes de quatre octets

Aucun d'eux ne change les règles que vous avez déjà apprises : octets en entrée, octets en sortie, un alphabet à choisir, et une fonction de décodage correspondante qui attend de l'autre côté. En pratique, vous irez chercher b16 chaque fois qu'un humain devrait pouvoir lire la valeur, b32 quand la valeur sera tapée ou dite à la main, et les cousins à 85 caractères seulement quand une spécification vous y invite. Pour tout le reste, la paire Base64 du début de cet article est l'outil correct, et c'est elle sur laquelle se construit chaque autre partie du module.

Des images dans la page : les URL data

Le Base64 le plus visible du web, c'est l'URI data: : des médias intégrés directement dans le HTML ou le CSS pour que le navigateur ne déclenche pas une seconde requête. Le format est data:, le type média, le mot base64, une virgule, et les octets encodés. En construire une depuis un fichier sur disque tient en trois lignes :

import base64
with open("logo.png", "rb") as handle:
  encoded = base64.b64encode(handle.read()).decode("ascii")
uri = "data:image/png;base64," + encoded
print(uri[:40])
# data:image/png;base64,iVBORw0KGgoAAAAN...

Deux précautions, toutes deux bon marché à observer. D'abord, le navigateur affichera joyeusement une URI data, et il tiendra joyeusement des mégaoctets de celles-ci dans le document : pour tout ce qui dépasse quelques kilooctets, une requête image normale avec un en-tête de cache correct gagne sur chaque métrique qui compte. Ensuite, le type média après les deux-points est une promesse. Si les octets sont un JPEG, l'URI dit image/jpeg, parce que certains outils valident la paire et certains renderers refusent tout simplement de deviner. L'étape .decode("ascii") n'est pas de la décoration non plus ; sans elle, vous concaténez un objet bytes à une chaîne et récoltez un TypeError, le mur de types qui fait son tour.

Des tokens que vous pouvez distribuer : les JWT

Un JSON Web Token est trois morceaux base64url joints par des points : un en-tête, un payload, et une signature. Si vous émettez de vrais tokens, ne bricolez pas les morceaux à la main. Installez PyJWT (pip install pyjwt) et laissez-le construire les parties base64url, le padding, et la signature en un seul appel :

import jwt
# Une clé de moins de 32 octets déclenche l'InsecureKeyLengthWarning de PyJWT, une récrimination méritée pour une clé de démo.
token = jwt.encode(
  {"sub": "1234567890", "name": "John Doe"},
  "super-secret-key",
  algorithm="HS256"
)
print(token)
# eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIi...
print(type(token))
# <class 'str'>

Sous le capot, PyJWT fait exactement ce que cet article décrit : sérialiser en JSON, faire passer le tout dans l'encodeur urlsafe, et retirer les pads, selon la définition JWS de la RFC 7515. Si un jour vous devez assembler un morceau à la main, pour un fixture de test ou une session de débogage, la recette est la même arithmétique partout :

import base64
import json
payload = json.dumps({"sub": "1234567890"}).encode("ascii")
part = base64.urlsafe_b64encode(payload).rstrip(b"=")
print(part)
# eyJzdWIiOiAiMTIzNDU2Nzg5MCJ9

Une note sur le sens de la confiance : construire un token est la moitié facile. Le destinataire doit vérifier la signature avant de croire la moindre claim, et PyJWT 2.x ne décodera pas un token sans une liste explicite d'algorithms, ce qui est une fonctionnalité, parce que l'erreur du « n'importe quel algorithme » est l'une des lignes de code d'authentification les plus chères jamais écrites.

HTTP : l'authentification Basic et la poignée de main WebSocket

Deux moments HTTP vivent ou meurent sur le Base64. Le premier est le plus ancien schéma d'authentification du protocole : l'authentification Basic (RFC 7617), où le client envoie user:pass, encodé en base64, derrière le mot Basic :

import base64
credentials = base64.b64encode(b"jane:pa:ss").decode("ascii")
header = "Basic " + credentials
print(header)
# Basic amFuZTpwYTpzcw==

Si requests est déjà dans votre stack, elle construit cet en-tête pour vous avec auth=("jane", "pa:ss"), ce qui vaut le coup d'être utilisé parce que ça garde le détail d'encodage hors de votre code. Et soyez honnête sur ce qui se passe pendant que vous y êtes : la RFC 7617 est catégorique : le schéma « n'est pas une méthode sûre d'authentification d'utilisateur, et ne protège en rien l'entité, qui est transmise en clair ». Les identifiants sont récupérables en une ligne de code par n'importe qui qui voit le trafic, alors c'est une commodité pour des connexions protégées par TLS, pas une frontière de sécurité.

Le second moment est la poignée de main WebSocket (RFC 6455), où le serveur prouve qu'il a lu la clé aléatoire du client en répondant avec le Base64 d'un hachage SHA-1 de la clé collée à un GUID magique :

import base64
import hashlib
key = "dGhlIHNhbXBsZSBub25jZQ=="
magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
accept = base64.b64encode(
  hashlib.sha1((key + magic).encode("ascii")).digest()
).decode("ascii")
print(accept)
# s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

La sortie est la valeur exacte de l'exemple travaillé de la RFC elle-même, ce qui est une belle façon de vérifier une implémentation faite de zéro. En production, la bibliothèque websockets fait cette étape pour vous aux deux bouts ; vous la fabriquez à la main seulement quand vous écrivez le minuscule serveur de test qui prouve votre compréhension.

Le courrier : le client d'origine

Le Base64 a été normalisé en 1993 pour un seul travail, et ce travail était le courrier : faire survivre le binaire au monde texte-uniquement du SMTP, selon le Content-Transfer-Encoding: base64 de la RFC 2045. Le paquet email de Python construit le message, choisit l'encodage, et coupe le corps à la longueur de ligne standard sans que vous écriviez une ligne de Base64 vous-même :

import email.mime.multipart
import email.mime.application
msg = email.mime.multipart.MIMEMultipart()
msg["Subject"] = "binary payload"
part = email.mime.application.MIMEApplication(
  b"\x00\x01\x02", _subtype="octet-stream"
)
msg.attach(part)
text = msg.as_string()
print(text)
# ...
# Content-Transfer-Encoding: base64
#
# AAEC
# --...

C'est la partie MIMEApplication qui est intéressante : elle enveloppe les octets dans les bonnes lignes de 76 caractères et timbre l'en-tête de transfer-encoding, ce qui est exactement le comportement d'encodebytes vu plus tôt, appliqué par le framework. Si vous assemblez un extrait brut plutôt qu'un message complet, email.encoders.encode_base64(obj) fait un encodage-et-coupure unique directement sur un objet message. Et quand le message arrive de l'autre côté, le côté décodage de l'histoire, de get_payload(decode=True) au décodage des en-têtes, est couvert dans l'article lié sur le décodage.

Le blindage PEM pour les clés et les certificats

Les fichiers PEM - les certificats, les clés privées et les CRL qui commencent par -----BEGIN ...----- - ne sont que du blindage autour du Base64 : une ligne d'étiquette, du Base64 coupé, une étiquette de clôture. Le blindage est facile à voir à travers, parce que le corps est juste la sortie coupée que vous avez déjà rencontrée :

import base64
der = b"\x30\x03\x02\x01\x05"   # un minuscule blob DER à titre d'illustration
body = base64.encodebytes(der).decode("ascii")
armor = ("-----BEGIN CERTIFICATE-----\n"
         + body
         + "-----END CERTIFICATE-----")
print(armor)
# -----BEGIN CERTIFICATE-----
# MAMCAQU=
# -----END CERTIFICATE-----

Retirez les deux lignes d'étiquette, rejoignez le reste, et b64decode vous remet les bytes DER. En production, vous ne ferez presque jamais ça à la main : le paquet cryptography (pip install cryptography) génère le blindage avec public_bytes et le parse avec load_pem_x509_certificate et les cousins, faisant l'étape Base64 pour vous sous le capot. Le chemin manuel paie son prix dans le moment précis où les bytes DER bruts sont déjà entre vos mains - une colonne de base de données, un fichier de config, un tampon d'un protocole - et où la spécification devant vous dit « PEM, s'il vous plaît ».

Expédier des fichiers : uploads, downloads et l'habitude des .b64

Le cas d'usage le plus ancien d'internet est un fichier binaire qui doit traverser un canal qui ne transporte que du texte : un FTP qui cabosse les fins de ligne, un formulaire qui refuse les uploads, une fenêtre de chat qui avale le binaire. La recette est lire, encoder, expédier, et laisser l'autre bout décoder, et la moitié intéressante, ce sont les deux étapes du milieu :

import base64
with open("photo.png", "rb") as src:
  data = src.read()
wrapped = base64.encodebytes(data)
with open("photo.b64", "wb") as dst:
  dst.write(wrapped)
print(len(wrapped), "bytes on disk for", len(data), "in the photo")
# environ un tiers plus gros que l'original

Deux notes. L'extension .b64 est une convention de la communauté, pas une norme, alors le côté récepteur doit connaître la convention aussi - c'est pourquoi les API JSON enveloppent d'habitude le payload dans un champ nommé comme "image_base64" et le disent dans leur documentation. Et les lignes coupées de 76 caractères de encodebytes sont le format de choix pour le fichier sur disque, parce qu'elles se copient-colent proprement à travers tous les outils texte que les humains possèdent, des clients de messagerie aux lecteurs PDF. Le sens inverse, relire un tel fichier en bytes, est un seul appel dans l'article lié sur le décodage ; ici, vous n'êtes que l'expéditeur, et le travail de l'expéditeur est d'être constant.

La question du stockage : fichiers de config, variables d'env et bases de données

Les développeurs adorent mettre du Base64 dans des endroits qui n'acceptent que du texte : un fichier .env, un réglage .ini, une colonne TEXT. L'étape d'encodage est triviale, et la forme la plus commune est le JSON-dans-du-Base64 :

import base64
import json
config = {"api_user": "svc-bot", "api_pass": "hunter2-not-really"}
packed = base64.b64encode(
  json.dumps(config).encode("utf-8")
).decode("ascii")
print(packed)
# eyJhcGlfdXNlciI6ICJzdmMtYm90IiwgImFwaV9wYXNzIjogImh1bnRlcjItbm90LXJlYWxseSJ9

Et puis l'avertissement, parce que c'est là que vit le malentendu le plus cher de tout cet article. Le Base64 n'est pas une obfuscation qui tient la route, et ce n'est pas du chiffrement. La section 12 de la RFC 4648 le met aussi plain que possible pour une RFC : l'encodage base « masque visuellement des informations autrement facilement reconnaissables, comme des mots de passe, mais ne fournit aucune confidentialité computationnelle ». Un fichier .env avec des secrets en Base64 vous protège de la personne qui jette un œil dessus, pas de la personne qui le lit, et une commande de base64 -d plus tard, le « secret » est assis dans son terminal en texte brut. Si les données sont vraiment sensibles, chiffrez-les d'abord - le paquet cryptography fournit Fernet exactement pour ça - et seulement après, mettez le texte chiffré en Base64 si votre stockage exige du texte.

Un million d'octets plus tard : gros volumes et coupes en morceaux

b64encode est une fonction à la vitesse du C - sur un portable typique, elle traite un mégaoctet en à peu près une milliseconde - mais ce n'est pas une fonction en flux. Il n'y a pas de paire update-et-finish n'importe où dans la bibliothèque standard, alors encoder des données plus grosses que ce que vous voulez garder en mémoire signifie faire l'arithmétique des frontières vous-même. Trois octets en entrée font quatre caractères en sortie, alors toute frontière de morceau doit atterrir sur une couture de trois octets :

import base64
def encode_chunks(chunks):
  out = []
  leftover = b""
  for chunk in chunks:
    buffer = leftover + chunk
    whole = len(buffer) // 3 * 3
    if whole:
      out.append(base64.b64encode(buffer[:whole]))
    leftover = buffer[whole:]
  if leftover:
    out.append(base64.b64encode(leftover))
  return b"".join(out)
with open("video.mp4", "rb") as handle:
  encoded = encode_chunks(iter(lambda: handle.read(65536), b""))

La sortie est identique octet par octet à l'encodage du fichier entier en un seul appel, parce que la couture de trois octets est le seul endroit où le groupement peut se casser. Le padding apparaît exactement une fois, sur le dernier morceau, ce qu'attendra un décodeur strict de l'autre côté. La ligne iter(lambda: handle.read(65536), b"") est l'idiome standard pour lire un fichier par morceaux de taille fixe, et la variable leftover est tout l'algorithme. Le côté décodage garde une couture de quatre caractères au lieu d'une couture de trois octets, alors les deux articles se partagent l'arithmétique au lieu de la répéter.

Où les encodeurs se trompent

Le côté encodage a moins de pièges que le côté décodage, parce qu'il y a moins de choses qui peuvent mal tourner quand vous êtes celui qui produit les lettres. Pourtant, ceux-ci se pointent chaque semaine, et chacun d'eux a un correctif de deux minutes si vous le reconnaissez tôt :

  • Alimenter l'encodeur avec une chaîne. Le TypeError de la section sur le mur de types. Corrigez-le à la source avec .encode("utf-8"), et pensez à quel charset vous voulez vraiment avant de le taper.
  • Oublier que la sortie est des bytes. b64encode renvoie des bytes ; le str est ce qui va dans une URL ou un champ JSON, alors l'étape .decode("ascii") fait partie de la recette, pas un après-coup.
  • Faire le remplacement sûr pour les URL à la main. str.replace("+", "-").replace("/", "_") marche, mais c'est deux lettres de dette de maintenance là où urlsafe_b64encode est un seul appel. Pire, un remplacement à moitié fait, plus corrigés et slashs oubliés, produit un alphabet qui ne correspond à aucune spécification du tout.
  • Laisser les pads dans une URL. Un = en fin de chaîne dans une chaîne de requête est percent-encodé par un outil et retiré par un autre, et le calcul de padding du destinataire casse de la façon la plus confusante. Retirez-les ; la longueur dit au décodeur tout ce dont il a besoin.
  • Couper là où ce n'est pas voulu. Les retours à la ligne d'encodebytes sont corrects pour le courrier et le PEM, et du poison pour un champ JSON ou une URL. Un seul retour à la ligne errant suffit pour faire jeter une exception à un décodeur strict de l'autre côté, sur vos données, pas sur votre formatage.
  • Encodage double. Les données étaient déjà en Base64 en amont - un champ qui arrive pré-encodé d'une autre API, un fichier qui a eu le traitement .b64 deux fois - et le second passage produit une chaîne qui se décode de nouveau en le premier encodage. Faites un aller-retour, vérifiez les octets magiques, et arrêtez-vous.
  • Faire confiance au Base64 avec des secrets. L'avertissement de la section stockage, répété parce qu'il coûte de l'argent réel : si le modèle de menaces inclut n'importe qui lisant le fichier, il vous faut un chiffrement, pas un alphabet.

Un changelog qu'on peut réellement lire

L'âge du module se voit dans des améliorations discrètes et datées plutôt que dans des révolutions. La version courte, dans l'ordre où les pièces sont arrivées, du point de vue de l'encodeur :

Version Ce qui s'est passé
Python 2.4 (2004) le support complet de la RFC 3548 par Barry Warsaw est livré : les familles b16, b32 et b64, plus les variantes standard_* et urlsafe_* utilisées aujourd'hui
Python 3.1 (2009) encodebytes arrive et encodestring devient non recommandée, un renommage sur lequel les vieux tutos trébuchent encore
Python 3.4 (2014) chaque encodeur accepte n'importe quel objet de type bytes, et a85encode et b85encode rejoignent le module
Python 3.6 (2016) binascii.b2a_base64 apprend un interrupteur newline, ce qui permet à b64encode de rester une seule ligne infinie
Python 3.9 (2020) les noms legacy encodestring et decodestring sont enfin retirés
Python 3.10 (2021) b32hexencode et b32hexdecode, les cousins à alphabet hexadécimal triable
Python 3.13 (2024) z85encode et z85decode, l'alphabet de ZeroMQ, rejoignent la famille
Python 3.14 (2025) des imports plus rapides partout dans la bibliothèque standard, base64 comprise, et un b16decode jusqu'à six fois plus rapide, puisque sa validation tourne maintenant sur bytes.translate au lieu d'une expression régulière

Le fil conducteur, si vous en voulez un : le module a été réécrit en 1995 pour déléguer son travail au module binascii de niveau C, et cette délégation est toujours vraie aujourd'hui. Le premier changement de l'ère bytes, un commit de 2007 pendant le développement de Python 3 qui a fait que tout utilise des bytes partout, est l'origine du mur de types de cet article, et c'est la raison pour laquelle un encodeur moderne prend des bytes et rend des bytes, avec tout le reste un emballage autour de ce contrat unique.

Ce que le module ne vous dit pas

Le travail sérieux est fait, alors voici les petits délices du côté encodage du grand livre :

  • L'exemple de la documentation elle-même a fait la même démonstration depuis plus d'une décennie : b'data to be encoded' entre, b'ZGF0YSB0byBiZSBlbmNvZGVk' sort. Vous avez déjà croisé ce duo, que vous le sachiez ou non.
  • La fonction C sous b64encode ajoute son retour à la ligne final avec un commentaire qui se lit « Ajouter un retour à la ligne de courtoisie ». Toute une culture, dans une ligne de source.
  • Le mot password s'encode en cGFzc3dvcmQ=, c'est pourquoi le Base64 dans un fichier log ressemble à un secret pour un scanner et n'est qu'à une commande d'en devenir un pour un lecteur.
  • b64encode ne coupe jamais. Jamais. Un gigaoctet d'entrée produit une seule ligne de 1,3 gigaoctet, et la fonction ne cligne même pas. Si vous vouliez des lignes, il fallait demander encodebytes.
  • La docstring du module cite encore la RFC 3548, l'édition 2003 de la spécification. La RFC 4648 a pris le relais en 2006 ; la docstring tout simplement n'a jamais remarqué.
  • Python 2 n'avait aucun mur de types du tout : b64encode acceptait joyeusement un str et en renvoyait un. La grande refonte bytes de 2007 a mis fin à ça, et les vieux tutos Python 2 sont là où pointent la plupart des fils de discussion « pourquoi mon encodage plante ».
  • z85encode, le membre le plus récent de la famille (Python 3.13), est le plus pointilleux : ZeroMQ cadre les données par groupes de quatre octets, alors la spécification exige une sortie encodée multiple de cinq caractères - et la documentation met le padding sur vous : l'entrée doit arriver multiple de 4 octets (l'encodeur ne l'ajoutera pas pour vous ; une entrée de 3 octets produit une trame de 4 caractères qu'aucun pair ZeroMQ n'acceptera).

Alors la philosophie de l'encodeur en trois règles. Décidez les octets d'abord et l'encodage ensuite, parce que c'est le mur de types où naissent la plupart des bugs Base64 de Python. Choisissez l'alphabet pour le canal, pas pour les données : standard avec pads pour le courrier et les fichiers, base64url sans pads pour les URL et les tokens, et n'improvisez jamais une troisième variante au clavier. Et gardez la sortie dans la forme que son lecteur attend, une ligne pour le JSON et les en-têtes, des lignes de 76 caractères pour le MIME et le PEM, parce que le décodeur de l'autre côté vous y tiendra.

Quand ces lettres arrivent de l'autre côté, c'est là que ça devient vraiment amusant : pads manquants, rejets silencieux, payloads qui ne sont pas tout à fait du Base64, et un décodeur à deux humeurs à naviguer. Tout ça est couvert en détail dans l'article lié sur le décodage Base64 en bas de cette page, et les deux guides se lisent bien en couple. Bon encodage.

Dernière mise à jour : 2026-09-08

Article associé : Décodage Base64 en Python : un guide complet