Base64-Kodierung in Python: Ein vollständiger Leitfaden
Hier ist die andere Seite der Medaille. Sie haben Daten in der Hand - eine Datei, ein Passwort-Paar, einen Binär-Blob, einen Unicode-Absatz - und irgendwo stromab müssen sie durch einen Kanal reisen, der nichts als Buchstaben annimmt. Das ist der gesamte Job von Base64: drei Bytes Daten in vier Zeichen aus einem 64-Zeichen-Alphabet umschreiben, die letzte Gruppe mit = auffüllen, damit alles zu Vieren herauskommt, und die Buchstaben übergeben. Die Startseite dieser Site erklärt das Format in voller Länge, Alphabet inklusive, also halten wir das auf einen Atemzug und widmen die restliche Zeit dem, was Python tatsächlich damit macht.
Die Rechnung verdient einen ehrlichen Satz, bevor wir anfangen, denn die Zahl kommt in jeder Diskussion darüber vor: Der Preis für diese Text-Sicherheit ist die Größe. Base64 vergrößert Ihre Daten um etwa ein Drittel, vier Zeichen für jeweils drei Eingabe-Bytes, also wird ein Megabyte Binärdaten zu einem Megabyte und einem Drittel Buchstaben. Für ein Token oder einen Konfigurationswert ist das kein Thema; für eine Videodatei ist es der Grund, über Ihre Optionen nachzudenken.
Und die gute Nachricht: Pythons Antwort auf all das ist ein Import und eine Funktion. base64.b64encode ist seit Jahrzehnten in der Standardbibliothek, braucht keine Installation und läuft unter der Haube mit C-Geschwindigkeit. Der Rest dieses Leitfadens ist der lange Schwanz, der den Einzeiler in der echten Welt nützlich macht: die nur-Bytes-Regel, die die Hälfte aller TypeError-Bug stoppt, das URL-sichere Alphabet, die MIME-Zeilen-Umbruch-Werkzeuge und die Protokolle - JWTs, HTTP-Header, WebSocket-Handshakes, E-Mail, PEM, Data-URLs - in denen Base64 still seinen Job macht.
Lernen Sie b64encode kennen
Der Vertrag passt in vier Sätze. Eins: Die Eingabe ist ein bytes-ähnliches Objekt - bytes, bytearray, memoryview - und ein normaler String wird abgelehnt. Zwei: Die Ausgabe ist ein bytes-Objekt, nie ein str. Drei: Die Ausgabe ist immer auf ein Vielfaches von vier Zeichen gepadded, also produziert selbst ein einzelnes Eingabe-Byte Zg==. Vier: Die Ausgabe ist eine einzige Zeile, nie umgebrochen, egal wie groß die Eingabe ist. Alles andere in diesem Artikel ist Kommentar zu diesen vier Sätzen:
import base64
encoded = base64.b64encode(b"foobar")
print(encoded)
# b'Zm9vYmFy'
print(len(encoded))
# 8
Um einen echten String zu bekommen, für eine URL oder einen Header oder ein JSON-Feld, dekodieren Sie das Ergebnis als ASCII. Das Alphabet garantiert, dass nichts anderes drin sein kann, was diesen Schritt sicher und billig macht:
import base64
text = base64.b64encode(b"foobar").decode("ascii")
print(text)
# Zm9vYmFy
Es gibt noch ein Argument in der Signatur, altchars, und es tauscht das + und / des Standardalphabets gegen ein anderes Zeichenpaar. Genau das ist der Drehregler hinter der URL-sicheren Variante, also behalten Sie den Gedanken im Kopf - Sie werden ihm in einigen Sektionen begegnen, wenn wir über Tokens und Query-Strings sprechen.
Die Typwand: str ist nicht bytes
Die erste Python-spezifische Mauer in diesem Artikel ist das Typsystem, und es lohnt sich, sie zu fühlen. b64encode lehnt Strings mit einer der ungnädigsten Fehlermeldungen der Sprache ab:
import base64
try:
base64.b64encode("hello")
except TypeError as caught:
print(caught)
# a bytes-like object is required, not 'str'
Die Korrektur ist die eine, wichtigste Gewohnheit in diesem ganzen Leitfaden: Machen Sie Ihren Text zuerst in Bytes um, und wählen Sie die Kodierung bewusst, statt zu hoffen:
import base64
print(base64.b64encode("été".encode("utf-8")))
# b'w6l0w6k='
print(base64.b64encode("été".encode("utf-16")))
# b'//7pAHQA6QA='
Gleiche Zeichen, zwei verschiedene Byte-Strings, zwei verschiedene Base64-Ausgaben. Die Wahl der Kodierung ist eine Entscheidung, kein Detail. UTF-8 ist der Standard für alles, was ein Kabel, eine Datenbank oder eine API überqueren wird. UTF-16 taucht auf, wenn Sie mit Windows-APIs sprechen, und es bringt eine Byte-Order-Mark (BOM) am Anfang mit, die Sie vielleicht nicht kodieren wollen, das Sie wegwerfen können, indem Sie utf-16-le verwenden oder es mit lstrip("\ufeff") abschneiden. Latin-1 versteckt sich immer noch in alten europäischen Dateien, wo ein Zeichen genau ein Byte ist und die ganze Frage nie entsteht. Das mentale Modell, das Sie behalten sollten: Der Encoder schaut sich Ihren Text nie an; er sieht immer nur Bits. In dem Moment, in dem die Bytes die Wand überqueren, ist die Charset-Frage erledigt - was auch der Grund ist, warum die Dekodier-Seite später fragen muss, wem das Charset gehörte.
base64url: Zwei Buchstaben tauschen, das Padding abwerfen
Das Standardalphabet versteckt zwei Zeichen, die URLs und Dateisysteme hassen. Das + Zeichen wird stillschweigend von jedem Formular-Decoder als Leerzeichen gelesen, und das / Zeichen ist ein Pfadtrenner, also ist ein Standard-Alphabet-Payload in einem Query-String oder in einem Dateinamen eine tickende Zeitbombe. Abschnitt 5 von RFC 4648 definiert die Korrektur: eine Variante, in der + zu - und / zu _ wird, in der das Padding fallengelassen wird, wann immer die Datenlänge aus dem Kontext bekannt ist, und die der RFC darauf besteht, base64url zu nennen und nicht einfach "base64". Sie werden sie in JSON Web Tokens, OAuth-Tokens und API-Cursor-Parametern treffen, also in einem Großteil des modernen Webs.
Python liefert sowohl eine dedizierte Funktion als auch den altchars-Drehregler aus der ersten Sektion, und sie produzieren identische Ausgabe:
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'-__-'
In Tokens und Query-Strings geht meistens auch das Padding, denn ein abschließendes = würde Prozent-Kodierung brauchen, und manche Middleboxen vermurksen es ohnehin:
import base64
padded = base64.urlsafe_b64encode(b"fooba")
print(padded)
# b'Zm9vYmE='
print(padded.rstrip(b"="))
# b'Zm9vYmE'
Abschneiden, schicken, und der Empfänger setzt die Paddings mit dem Modulo-Trick zurück, "=" * (-len(s) % 4), der genau so viele Paddings erzeugt, wie die Länge verlangt. Die Faustregel: Wenn die Daten in einer URL, einem Dateinamen oder einem JWT sitzen werden, verwenden Sie die urlsafe-Variante und werfen Sie die Paddings weg; wenn sie in einem E-Mail-Körper oder einer Textdatei sitzen werden, ist das Standardalphabet mit seinem Padding der Normalfall.
Wenn Ihr Leser Zeilen will: MIME und die 76-Zeichen-Regel
Die eine endlose Zeile von b64encode ist perfekt für JSON-Felder, Header und URLs, aber E-Mail hat Meinungen. RFC 2045, der MIME-Standard, verlangt, dass Base64-Ausgabe in Zeilen von höchstens 76 Zeichen gebrochen wird, und Pythons Legacy-Werkzeuge wurden gebaut, genau das zu produzieren. encodebytes, hinzugefügt in Python 3.1, erledigt den Umbruch für ein bytes-Objekt:
import base64
wrapped = base64.encodebytes(b"x" * 100)
for line in wrapped.splitlines():
print(len(line), line[:12])
# 76 eHh4eHh4eHh4
# 60 eHh4eHh4eHh4
Die Mechanik ist ein bisschen süß. Das Modul kodiert in 57-Byte-Chunks, die Konstante MAXBINSIZE, denn 57 Bytes werden genau 76 Zeichen, und in modernem CPython endet jede umgebrochene Zeile mit einem einfachen Zeilenvorschub. RFC 2045 fragte nach CRLF, aber Pythons LF-Ausgabe wird von jedem Decoder im Ökosystem akzeptiert, inklusive Pythons eigenem. Die Legacy-Datei-zu-Datei-Funktion encode macht denselben Umbruch direkt von einem Datei-Handle zum anderen, was sie zu einem ordentlichen Werkzeug für große Dateien macht, die Sie nicht doppelt im Speicher halten wollen.
Wann welches Werkzeug, kurz gesagt: b64encode für alles, was in ein JSON-Feld, eine URL, einen Header oder eine Datenbankspalte geht; encodebytes für E-Mail-Körper und PEM-Stil-Panzerung; die Legacy-encode, wenn Sie eine große Datei streamen und den Umbruch für free wollen. Das Falsche zu wählen ist ein klassischer Bug, denn ein einzelner versehentlicher Zeilenumbruch in einem JSON-Feld reicht, um einen strengen Decoder auf der anderen Seite eine Ausnahme werfen zu lassen.
Die erweiterte Familie
Das base64-Modul ist eigentlich das base-N-Modul, und es trägt die ganze RFC 4648 Familie plus ein paar Verwandte aus anderen Ecken der Computerswelt. Die meisten davon sind Einzeiler, die sich direkt einsetzen lassen, für denselben Bytes-rein-Bytes-raus-Vertrag:
| Funktionen | Alphabet | Wann Sie es treffen |
|---|---|---|
b16encode / b16decode |
0-9A-F |
"Base16" ist schlicht Hexadezimal; die schnellste Hin-und-Rück-Reise im Modul, toll für Hashes und UUIDs |
b32encode / b32decode |
A-Z2-7 |
Lizenzschlüssel und Aktivierungscodes; kein 0, O, 1 oder I, also überlebt es das Vorlesen |
b32hexencode / b32hexdecode |
0-9A-V |
Base32 mit einem Hex-Alphabet, hinzugefügt in Python 3.10; hält kodierte Daten lexikographisch sortierbar |
a85encode / a85decode |
85 druckbare Zeichen | ASCII85 aus PostScript und PDF, der Nachkomme des Unix-btoa-Werkzeugs; im Modul seit Python 3.4 |
b85encode / b85decode |
85 druckbare Zeichen | das Base85-Format, das von git und Mercurial binären Diffs verwendet wird; ebenfalls seit Python 3.4 |
z85encode / z85decode |
85 druckbare Zeichen | ZeroMQs Z85, hinzugefügt in Python 3.13; rahmt Daten in Gruppen von vier Bytes ein |
Keines davon ändert die Regeln, die Sie schon gelernt haben: Bytes rein, Bytes raus, ein Alphabet zum Wählen, und eine passende Decode-Funktion, die auf der anderen Seite wartet. In der Praxis greifen Sie zu b16, wann immer ein Mensch den Wert lesen sollte, zu b32, wenn der Wert von Hand getippt oder vorgesprochen wird, und zu den 85-Zeichen-Verwandten nur, wenn eine Spezifikation es Ihnen sagt. Für alles andere ist das Base64-Paar vom Anfang dieses Artikels das richtige Werkzeug, und es ist das, auf dem jeder andere Teil davon aufbaut.
Bilder in der Seite: Data URLs
Das sichtbarste Base64 im Web ist die data:-URI: Medien, die direkt in HTML oder CSS eingebettet sind, damit der Browser keinen zweiten Request losfeuert. Das Format ist data:, der Medientyp, das Wort base64, ein Komma und die kodierten Bytes. Eine aus einer Datei auf der Platte zu bauen ist ein Dreizeiler:
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...
Zwei Vorsichtsmaßnahmen, beide billig zu befolgen. Erstens: Der Browser rendert eine Data URI gerne, und er hält gerne Megabytes davon im Dokument: Für alles über wenige Kilobytes hinaus gewinnt ein normaler Bild-Request mit einem ordentlichen Cache-Header auf jeder Metrik, die zählt. Zweitens: Der Medientyp nach dem Doppelpunkt ist ein Versprechen. Wenn die Bytes ein JPEG sind, sagt die URI image/jpeg, denn manche Werkzeuge validieren das Paar, und manche Renderer weigern sich einfach zu raten. Der .decode("ascii")-Schritt ist auch keine Dekoration; ohne ihn verketten Sie ein bytes-Objekt an einen String und sammeln eine TypeError ein, die Typwand macht ihre Runde.
Tokens, die Sie austeilen können: JWTs
Ein JSON Web Token ist drei base64url-Stücke, die durch Punkte verbunden sind: ein Header, ein Payload und eine Signatur. Wenn Sie echte Tokens ausstellen, bauen Sie die Stücke nicht selbst. Installieren Sie PyJWT (pip install pyjwt) und lassen Sie es die base64url-Teile, das Padding und die Signatur in einem einzigen Aufruf bauen:
import jwt
# Ein Schlüssel unter 32 Bytes verdient PyJWTs InsecureKeyLengthWarning, ein faires Murren für einen Demo-Schlüssel.
token = jwt.encode(
{"sub": "1234567890", "name": "John Doe"},
"super-secret-key",
algorithm="HS256"
)
print(token)
# eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIi...
print(type(token))
# <class 'str'>
Unter der Haube tut PyJWT genau das, was dieser Artikel beschreibt: in JSON serialisieren, durch den urlsafe-Encoder jagen und die Paddings abschneiden, gemäß der JWS-Definition in RFC 7515. Wenn Sie jemals ein Stück von Hand zusammenbauen müssen, für eine Test-Fixture oder eine Debugging-Sitzung, ist das Rezept dieselbe Arithmetik überall:
import base64
import json
payload = json.dumps({"sub": "1234567890"}).encode("ascii")
part = base64.urlsafe_b64encode(payload).rstrip(b"=")
print(part)
# eyJzdWIiOiAiMTIzNDU2Nzg5MCJ9
Eine Anmerkung zur Vertrauensrichtung: Ein Token zu bauen ist die einfache Hälfte. Der Empfänger muss die Signatur verifizieren, bevor er einem einzigen Claim vertraut, und PyJWT 2.x wird kein Token ohne eine explizite algorithms-Liste dekodieren, was eine Funktionalität ist, denn der "beliebiger Algorithmus"-Fehler ist eine der teuersten Zeilen Authentifizierungscode, die je geschrieben wurden.
HTTP: Basic Auth und der WebSocket-Handshake
Zwei HTTP-Momente leben oder sterben mit Base64. Der erste ist das älteste Authentifizierungsverfahren des Protokolls: Basic Auth (RFC 7617), bei dem der Client user:pass schickt, base64-kodiert, hinter dem Wort Basic:
import base64
credentials = base64.b64encode(b"jane:pa:ss").decode("ascii")
header = "Basic " + credentials
print(header)
# Basic amFuZTpwYTpzcw==
Wenn requests schon in Ihrem Stack ist, baut es diesen Header für Sie mit auth=("jane", "pa:ss"), was sich lohnt, weil es das Kodierungs-Detail aus Ihrem Code heraushält. Und seien Sie ehrlich dabei, was gerade passiert: RFC 7617 ist direkt, dass das Verfahren "keine sichere Methode der Benutzer-Authentifizierung ist und die Entität in keiner Weise schützt, die im Klartext übertragen wird". Die Anmeldedaten sind von jedem, der den Traffic sieht, in einer Zeile Code wiederherstellbar, also ist dies eine Bequemlichkeit für TLS-geschützte Verbindungen, keine Sicherheitsgrenze.
Der zweite Moment ist der WebSocket-Handshake (RFC 6455), bei dem der Server beweist, dass er den zufälligen Schlüssel des Clients gelesen hat, indem er mit Base64 eines SHA-1-Hashes antwortet, der aus dem Schlüssel und einer magischen GUID zusammengeklebt wurde:
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=
Die Ausgabe ist der genaue Wert aus dem Arbeitsbeispiel im RFC selbst, was eine schöne Art ist, eine von-Scratch-Implementierung zu prüfen. In der Produktion erledigt die websockets-Bibliothek diesen Schritt für Sie an beiden Enden; Sie bauen ihn nur von Hand, wenn Sie den kleinen Test-Server schreiben, der Ihr Verständnis beweist.
E-Mail: Der ursprüngliche Kunde
Base64 wurde 1993 für einen Job standardisiert, und dieser Job war E-Mail: Binärdaten in der nur-Text-Welt von SMTP überleben zu lassen, gemäß Content-Transfer-Encoding: base64 von RFC 2045. Pythons email-Paket baut die Nachricht, wählt die Kodierung und bricht den Körper bei der Standard-Zeilenlänge um, ohne dass Sie eine Zeile Base64 selbst schreiben:
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
# --...
Der MIMEApplication-Teil ist der interessante: Er wickelt die Bytes in die korrekten 76 Zeichen langen Zeilen und stempelt den Transfer-Encoding-Header auf, was genau das encodebytes-Verhalten von früher ist, angewendet vom Framework. Wenn Sie einen nackten Ausschnitt statt einer vollen Nachricht zusammenbauen, erledigt email.encoders.encode_base64(obj) eine einzelne Kodierung mit Umbruch direkt auf einem Nachrichtenobjekt. Und wenn die Nachricht auf der anderen Seite ankommt, ist die Dekodier-Seite der Geschichte, von get_payload(decode=True) bis zur Header-Dekodierung, im verwandten Dekodierungs-Artikel abgedeckt.
PEM-Panzerung für Schlüssel und Zertifikate
PEM-Dateien - die Zertifikate, privaten Schlüssel und CRLs, die mit -----BEGIN ...----- beginnen - sind nichts als Panzerung um Base64: eine Label-Zeile, umgebrochenes Base64, eine schließende Label-Zeile. Die Panzerung ist leicht durchzusehen, denn der Körper ist nur die umgebrochene Ausgabe, die Sie schon getroffen haben:
import base64
der = b"\x30\x03\x02\x01\x05" # ein winziger DER-Blob zur Veranschaulichung
body = base64.encodebytes(der).decode("ascii")
armor = ("-----BEGIN CERTIFICATE-----\n"
+ body
+ "-----END CERTIFICATE-----")
print(armor)
# -----BEGIN CERTIFICATE-----
# MAMCAQU=
# -----END CERTIFICATE-----
Streichen Sie die zwei Label-Zeilen, verbinden Sie den Rest, und b64decode gibt Ihnen die DER-Bytes zurück. In der Produktion tun Sie das fast nie von Hand: das cryptography-Paket (pip install cryptography) erzeugt die Panzerung mit public_bytes und parst sie mit load_pem_x509_certificate und Freunden und erledigt den Base64-Schritt für Sie unter der Haube. Der manuelle Weg verdient sich seinen Lohn in dem spezifischen Moment, in dem die rohen DER-Bytes schon in Ihren Händen sind - eine Datenbankspalte, eine Konfigurationsdatei, ein Puffer aus einem Protokoll - und die Spezifikation vor Ihnen sagt "PEM, bitte".
Dateien verschicken: Uploads, Downloads und die .b64-Gewohnheit
Der älteste Anwendungsfall im Internet ist eine Binärdatei, die einen Kanal überqueren muss, der nur Text trägt: ein FTP, das Zeilenumbrüche vermurkst, ein Formular, das Uploads ablehnt, ein Chat-Fenster, das Binär frisst. Das Rezept ist lesen, kodieren, schicken und der anderen Seite das Dekodieren überlassen, und die interessante Hälfte davon sind die mittleren zwei Schritte:
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")
# etwa ein Drittel größer als das Original
Zwei Anmerkungen. Die .b64-Erweiterung ist eine Community-Konvention, kein Standard, also muss die Empfangsseite die Konvention auch kennen - deshalb wickeln JSON-APIs den Payload normalerweise in ein benanntes Feld wie "image_base64" und sagen das in ihrer Dokumentation. Und die umgebrochenen Zeilen mit 76 Zeichen von encodebytes sind das Format der Wahl für die Datei auf der Platte, denn sie kopieren sich sauber durch jedes Text-Werkzeug, das Menschen haben, von Mail-Clients bis PDF-Readern. Die umgekehrte Richtung, so eine Datei wieder in Bytes zu lesen, ist ein Aufruf im verwandten Dekodierungs-Artikel; hier sind Sie nur der Absender, und der Job des Absenders ist, konsistent zu sein.
Die Speicher-Frage: Konfigurationsdateien, Umgebungsvariablen und Datenbanken
Entwickler lieben es, Base64 in Orte zu stecken, die nur Text annehmen: eine .env-Datei, eine .ini-Einstellung, eine TEXT-Spalte. Der Kodierungsschritt ist trivial, und die häufigste Form ist JSON-in-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
Und dann die Warnung, denn hier lebt das teuerste Missverständnis in diesem ganzen Artikel. Base64 ist keine Verschleierung, die hält, und es ist keine Verschlüsselung. Abschnitt 12 von RFC 4648 sagt es so schlicht, wie ein RFC kann: Base-Kodierung "versteckt visuell sonst leicht erkennbare Informationen, wie Passwörter, bietet aber keine rechnerische Vertraulichkeit". Eine .env-Datei mit Base64-Geheimnissen schützt Sie vor der Person, die hinschaut, nicht vor der Person, die liest, und ein Befehl base64 -d später sitzt das "Geheimnis" im Klartext in ihrem Terminal. Wenn die Daten wirklich sensibel sind, verschlüsseln Sie sie zuerst - das cryptography-Paket liefert Fernet genau dafür - und erst dann base64-kodieren Sie den Geheimtext, wenn Ihr Speicher Text verlangt.
Eine Million Bytes später: Big Data und Chunking
b64encode ist eine Funktion mit C-Geschwindigkeit - auf einem typischen Laptop verarbeitet sie ein Megabyte in ungefähr einer Millisekunde - aber es ist keine Streaming-Funktion. In der Standardbibliothek gibt es nirgends ein update-und-finish-Paar, also bedeutet das Kodieren von Daten, die größer sind als Sie im Speicher halten wollen, die Grenz-Mathematik selbst zu erledigen. Drei Eingabe-Bytes ergeben vier Ausgabe-Zeichen, also muss jede Chunk-Grenze auf einer Drei-Byte-Naht landen:
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""))
Die Ausgabe ist Byte-für-Byte identisch mit dem Kodieren der ganzen Datei in einem einzigen Aufruf, denn die Drei-Byte-Naht ist der einzige Ort, an dem die Gruppierung brechen kann. Das Padding erscheint genau einmal, im letzten Chunk, was ein strenger Decoder auf der anderen Seite erwarten wird. Die iter(lambda: handle.read(65536), b"")-Zeile ist das Standard-Idiom für das Lesen einer Datei in festen Stücken, und die leftover-Variable ist der gesamte Algorithmus. Die Dekodier-Seite hält eine Vier-Zeichen-Naht statt einer Drei-Byte-Naht, also teilen sich die beiden Artikel die Arithmetik, statt sie zu wiederholen.
Wo Encoder falsch gehen
Die Kodierungs-Seite hat weniger Fallen als die Dekodierungs-Seite, denn es gibt weniger, was schiefgehen kann, wenn Sie derjenige sind, der die Buchstaben produziert. Trotzdem tauchen diese jede Woche auf, und jedes von ihnen hat eine Zwei-Minuten-Korrektur, wenn man es früh erkennt:
- Ein String in den Encoder gefüttert. Die
TypeErroraus der Typwand-Sektion. Korrigieren Sie an der Quelle mit.encode("utf-8"), und denken Sie darüber nach, welches Charset Sie wirklich meinen, bevor Sie es tippen. - Vergessen, dass die Ausgabe Bytes sind.
b64encodegibt Bytes zurück; derstrist das, was in eine URL oder ein JSON-Feld geht, also ist der.decode("ascii")-Schritt Teil des Rezepts, kein nachträglicher Gedanke. - Den URL-sicheren Tausch von Hand bauen.
str.replace("+", "-").replace("/", "_")funktioniert, aber es sind zwei Buchstaben Wartungs-Schulden, wourlsafe_b64encodeein Aufruf ist. Schlimmer noch: ein halbfertiger Tausch, Plusse repariert und Slashes vergessen, produziert ein Alphabet, das mit keiner Spezifikation übereinstimmt. - Die Paddings in einer URL lassen. Ein abschließendes
=in einem Query-String wird von einem Werkzeug Prozent-kodiert und von einem anderen gestrippt, und die Padding-Mathematik des Empfängers bricht auf die verwirrendste Art. Streichen Sie sie; die Länge sagt dem Decoder alles, was er braucht. - Umbruch dort, wo er nicht gewollt ist.
encodebytes-Zeilenumbrüche sind korrekt für E-Mail und PEM, und Gift für ein JSON-Feld oder eine URL. Ein einzelner versehentlicher Zeilenumbruch reicht, um einen strengen Decoder auf der anderen Seite eine Ausnahme über Ihre Daten werfen zu lassen, nicht über Ihre Formatierung. - Doppelte Kodierung. Die Daten waren upstream schon Base64 - ein Feld, das vor-kodiert von einer anderen API ankommt, eine Datei, die zweimal die
.b64-Behandlung bekommen hat - und der zweite Durchgang produziert einen String, der sich zurück in die erste Kodierung dekodiert. Einmal hin-und-rück, die Magie-Bytes prüfen, und aufhören. - Base64 mit Geheimnissen vertrauen. Die Warnung der Speicher-Sektion, wiederholt, weil sie echtes Geld kostet: Wenn das Bedrohungsmodell jemanden einschließt, der die Datei liest, brauchen Sie einen Cipher, kein Alphabet.
Ein Changelog, das man wirklich lesen kann
Das Alter des Moduls zeigt sich in leisen, datierten Verbesserungen statt in Revolutionen. Die Kurzversion, in der Reihenfolge, in der die Stücke landeten, aus der Sicht des Encoders:
| Version | Was geschah |
|---|---|
| Python 2.4 (2004) | Barry Warsaws vollständige RFC 3548 Unterstützung wird ausgeliefert: die b16, b32 und b64-Familien, plus die heute verwendeten standard_* und urlsafe_*-Varianten |
| Python 3.1 (2009) | encodebytes kommt und encodestring wird als veraltet markiert, eine Umbenennung, über die alte Tutorials immer noch stolpern |
| Python 3.4 (2014) | jeder Encoder akzeptiert jedes bytes-ähnliche Objekt, und a85encode und b85encode treten zum Modul bei |
| Python 3.6 (2016) | binascii.b2a_base64 lernt einen newline-Schalter, und das ist es, was b64encode eine einzige endlose Zeile bleiben lässt |
| Python 3.9 (2020) | die Legacy-Namen encodestring und decodestring werden endlich entfernt |
| Python 3.10 (2021) | b32hexencode und b32hexdecode, die sortierbaren Hex-Alphabet-Verwandten |
| Python 3.13 (2024) | z85encode und z85decode, ZeroMQs Alphabet, treten zur Familie bei |
| Python 3.14 (2025) | schnellere Imports quer durch die Standardbibliothek, base64 inklusive, und ein b16decode, das bis zu sechsmal schneller ist, denn seine Validierung läuft jetzt auf bytes.translate statt auf einem regulären Ausdruck |
Der rote Faden, wenn Sie einen wollen: Das Modul wurde 1995 umgeschrieben, um seine Arbeit an das C-Level-Modul binascii zu delegieren, und diese Delegation ist noch heute wahr. Die erste Bytes-Ära-Änderung, ein 2007er Commit während der Python 3 Entwicklung, der alles überall Bytes benutzen ließ, ist der Ort, aus dem die Typwand in diesem Artikel kam, und sie ist der Grund, warum ein moderner Encoder Bytes nimmt und Bytes zurückgibt, mit allem anderen eine Hülle um diesen einen Vertrag.
Dinge, die das Modul Ihnen nicht sagt
Die ernste Arbeit ist erledigt, also hier die kleinen Freuden auf der Kodierungs-Seite:
- Das eigene Beispiel der Dokumentation läuft seit über einem Jahrzehnt dieselbe Demonstration:
b'data to be encoded'geht rein,b'ZGF0YSB0byBiZSBlbmNvZGVk'kommt raus. Sie haben dieses Paar schon mal getroffen, ob Sie es wissen oder nicht. - Die C-Funktion unter
b64encodefügt ihr abschließendes Zeilenende mit einem Kommentar hinzu, der "Ein Zeilenende aus Höflichkeit anhängen" liest. Eine ganze Kultur, in einer Zeile Quellcode. - Das Wort
passwordwird zucGFzc3dvcmQ=kodiert, deshalb sieht Base64 in einer Log-Datei für einen Scanner aus wie ein Geheimnis und ist für einen Leser ein Befehl davon. b64encodebricht nie um. Niemals. Ein Gigabyte Eingabe produziert eine einzige 1,3-Gigabyte-Zeile, und die Funktion bleibt dabei unbeeindruckt. Wenn Sie Zeilen wollten, hätten Sieencodebytesanfragen müssen.- Die Docstring des Moduls nennt immer noch RFC 3548, die 2003er Ausgabe der Spezifikation. RFC 4648 übernahm 2006; die Docstring hat es einfach nie bemerkt.
- Python 2 hatte überhaupt keine Typwand:
b64encodeakzeptierte froh einstrund gab eines zurück. Die Bytes-Grundüberholung von 2007 beendete das, und die alten Python 2 Tutorials sind der Ort, auf den die meisten "warum crasht mein Encoding"-Threads noch immer zeigen. z85encode, das jüngste Mitglied der Familie (Python 3.13), ist das wählerischste: ZeroMQ rahmt Daten in Gruppen von vier Bytes ein, also verlangt die Spezifikation, dass die kodierte Ausgabe ein Vielfaches von fünf Zeichen ist - und die Docs legen das Padding Ihnen auf: Eingabe muss als Vielfaches von 4 Bytes ankommen (der Encoder wird es nicht für Sie padden; eine 3-Byte-Eingabe ergibt einen 4-Zeichen-Frame, den kein ZeroMQ-Peer akzeptieren wird).
So die Encoder-Philosophie in drei Regeln. Entscheiden Sie zuerst die Bytes und zweitens die Kodierung, denn die Typwand ist der Ort, an dem die meisten Python-Base64-Bugs geboren werden. Wählen Sie das Alphabet für den Kanal, nicht für die Daten: Standard mit Paddings für E-Mail und Dateien, base64url ohne Paddings für URLs und Tokens, und improvisieren Sie nie eine dritte Variante an der Tastatur. Und halten Sie die Ausgabe in der Form, die ihr Leser erwartet, eine Zeile für JSON und Header, Zeilen mit 76 Zeichen für MIME und PEM, denn der Decoder auf der anderen Seite wird Sie daran messen.
Wenn diese Buchstaben auf der anderen Seite ankommen, beginnt der Spaß erst: fehlende Paddings, stille Verwerfungen, Payloads, die nicht ganz Base64 sind, und ein Decoder mit zwei Launen, die man navigieren muss. All das ist ausführlich im verwandten Base64-Dekodierungs-Artikel unten auf dieser Seite abgedeckt, und die beiden Leitfäden lesen sich gut als Paar. Frohes Kodieren.
Zuletzt aktualisiert: 2026-09-08
Verwandter Artikel: Base64-Dekodierung in Python: Ein vollständiger Leitfaden