Base64-Kodierung in Python: Ein vollständiger Leitfaden
Hier ist die andere Seite der Münze. Sie haben Daten in der Hand - eine Datei, ein Passwort-Paar, ein Binär-Blob, einen Absatz Unicode - und irgendwo flussabwärts müssen sie durch einen Kanal reisen, der nichts als Buchstaben akzeptiert. Das ist der gesamte Job von Base64: Drei Bytes der Daten als vier Zeichen aus einem 64-Zeichen-Alphabet umschreiben, die letzte Gruppe mit = auffüllen, damit alles in Vieren herauskommt, und die Buchstaben übergeben. Die Startseite dieser Site erklärt das Format vollständig, Alphabet inklusive, also halten wir das auf einen Atemzug und geben den Rest der Zeit dafür aus, was Python tatsächlich damit macht.
Die Ökonomie 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, sodass ein Megabyte Binär zu einem Megabyte und einem Drittel an Buchstaben wird. Für ein Token oder einen Konfigurationswert ist das kein Thema; für eine Videodatei ist es der Grund, warum Sie über Ihre Optionen nachdenken sollten.
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 realen Welt nützlich macht: die nur-Bytes-Regel, die die Hälfte aller TypeError-Bugs stoppt, das URL-sichere Alphabet, die MIME-Zeilenumbruch-Werkzeuge und die Protokolle - JWTs, HTTP-Header, WebSocket-Handshakes, E-Mail, PEM, Data-URLs - in denen Base64 still seinen Job macht.
Triff b64encode
Der Vertrag passt in vier Sätze. Eins: Die Eingabe ist ein bytes-ähnliches Objekt - bytes, bytearray, memoryview - und ein gewöhnlicher String wird abgelehnt. Zwei: Die Ausgabe ist ein bytes-Objekt, niemals ein str. Drei: Die Ausgabe ist immer so gepadded, dass sie ein Vielfaches von vier Zeichen hat, sodass selbst ein einzelnes Eingabe-Byte Zg== produziert. 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 darin 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 aus. Genau das ist der Regler hinter der URL-sicheren Variante, also halten Sie den Gedanken fest - Sie werden ihm in einigen Abschnitten begegnen, wenn wir über Tokens und Query-Strings sprechen.
Die Typmauer: str ist nicht bytes
Die erste Python-spezifische Mauer in diesem Artikel ist das Typsystem, und es lohnt sich, sie zu fühlen. b64encode weist Strings mit einer der ungnädigsten Fehlermeldungen der Sprache zurück:
import base64
try:
base64.b64encode("hello")
except TypeError as caught:
print(caught)
# a bytes-like object is required, not 'str'
Die Lösung ist die eine wichtigste Gewohnheit in diesem ganzen Leitfaden: Wandeln 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='
Dieselben 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 durchqueren wird. UTF-16 taucht auf, wenn Sie mit Windows-APIs sprechen, und es bringt eine Byte-Reihenfolge-Marke vorne mit, die Sie vielleicht nicht kodieren wollen, die Sie loswerden, indem Sie utf-16-le verwenden oder sie 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 aufkommt. Das mentale Modell, das Sie behalten sollten: Der Kodierer schaut sich Ihren Text nie an; er sieht nur je Bits. Im Moment, in dem die Bytes die Mauer überschreiten, ist die Charset-Frage abgeschlossen - was auch der Grund ist, warum die Dekodier-Seite später fragen muss, wem dieses Charset gehört hat.
base64url: Zwei Buchstaben tauschen, das Padding werfen
Das Standardalphabet versteckt zwei Zeichen, die URLs und Dateisysteme hassen. Das + Zeichen wird still von jedem Form-Dekodierer als Leerzeichen gelesen, und das / Zeichen ist ein Pfadtrenner, also ist ein Standardalphabet-Payload in einer Query-String oder einem Dateinamen eine tickende Bombe. Abschnitt 5 von RFC 4648 definiert die Reparatur: eine Variante, in der + zu - und / zu _ wird, in der das Padding weggelassen 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, was so viel heißt wie: in den meisten Teilen des modernen Webs.
Python bringt sowohl eine dedizierte Funktion als auch den altchars-Regler aus dem ersten Abschnitt mit, 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 das Padding in der Regel auch mit, denn ein abschließendes = bräuchte ein Percent-Encoding, und einige Middleboxen verzerren es ohnehin:
import base64
padded = base64.urlsafe_b64encode(b"fooba")
print(padded)
# b'Zm9vYmE='
print(padded.rstrip(b"="))
# b'Zm9vYmE'
Abschneiden, senden, und der Empfänger fügt die Pads mit dem Modulo-Trick, "=" * (-len(s) % 4), zurück, der genau so viele Pads produziert, wie es 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 Pads weg; wenn sie in einem E-Mail-Body oder einer Textdatei sitzen werden, ist das Standardalphabet mit seinem Padding die Norm.
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 umgebrochen wird, und Pythons Legacy-Werkzeuge wurden gebaut, genau das zu produzieren. encodebytes, hinzugefügt in Python 3.1, erledigt das Umbrechen 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 wenig niedlich. Das Modul kodiert in 57-Byte-Chunks, der Konstante MAXBINSIZE, weil 57 Bytes genau 76 Zeichen werden, und in modernem CPython endet jede umgebrochene Zeile mit einem gewöhnlichen Zeilenvorschub. RFC 2045 verlangte CRLF, aber Pythons LF-Ausgabe wird von jedem Dekodierer im Ökosystem akzeptiert, einschließlich Pythons eigenem. Die Legacy-Datei-zu-Datei-Funktion encode macht dasselbe Umbrechen direkt von einem Datei-Handle zum anderen, was sie zu einem aufgeräumten 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 Datenbanksäule geht; encodebytes für E-Mail-Bodies und PEM-artige Panzerung; das Legacy-encode, wenn Sie eine große Datei streamen und das Umbrechen gratis wollen. Das Falsche auszuwählen ist ein klassischer Bug, denn ein einzelner verirrter Zeilenumbruch in einem JSON-Feld reicht aus, um einen strikten Dekodierer 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 Computerwelt. Die meisten davon sind Einzeilen-Drop-ins für denselben Bytes-hin-Bytes-her-Vertrag:
| Funktionen | Alphabet | Wann Sie sie treffen |
|---|---|---|
b16encode / b16decode |
0-9A-F |
"Base16" ist einfach nur Hexadezimal; der schnellste Hin-und-Rück-Weg im Modul, toll für Hashes und UUIDs |
b32encode / b32decode |
A-Z2-7 |
Lizenzschlüssel und Aktivierungs-Codes; kein 0, O, 1 oder I, also übersteht es das Vorlesen |
b32hexencode / b32hexdecode |
0-9A-V |
Base32 mit einem Hex-Alphabet, hinzugefügt in Python 3.10; hält kodierte Daten lexikografisch sortierbar |
a85encode / a85decode |
85 druckbare Zeichen | ASCII85 aus PostScript und PDF, der Nachfahre des Unix-btoa-Werkzeugs; im Modul seit Python 3.4 |
b85encode / b85decode |
85 druckbare Zeichen | das Base85-Format, das von git und Mercurial Binary-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 bereits gelernt haben: Bytes hinein, Bytes heraus, ein Alphabet zum Wählen und eine passende Dekodier-Funktion, die auf der anderen Seite wartet. In der Praxis greifen Sie zu b16, wann immer ein Mensch den Wert lesen können sollte, zu b32, wenn der Wert von Hand getippt oder gesagt wird, und zu den 85-Zeichen-Cousins 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 aufbaut.
Bilder in der Seite: Data-URLs
Das sichtbarste Base64 im Web ist das data:-URI: Medien, die direkt in HTML oder CSS eingebettet sind, damit der Browser keine zweite Anfrage schießt. Das Format lautet data:, der Medientyp, das Wort base64, ein Komma und die kodierten Bytes. Eines aus einer Datei auf der Festplatte 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 Vorsichtsanmerkungen, beide leicht einzuhalten. Erstens: Der Browser rendert ein Data-URI gerne, und er hält Megabytes davon gerne im Dokument: Für alles über ein paar Kilobytes gewinnt eine gewöhnliche Bildanfrage mit einem ordentlichen Cache-Header in jeder Metrik, die zählt. Zweitens: Der Medientyp nach dem Doppelpunkt ist eine Zusage. Wenn die Bytes ein JPEG sind, sagt das URI image/jpeg, denn einige Werkzeuge validieren das Paar, und einige Renderer verweigern es schlicht, zu raten. Der .decode("ascii")-Schritt ist auch kein Schmuck; ohne ihn konkatenieren Sie ein bytes-Objekt an einen String und ernten einen TypeError, die Typmauer macht ihre Runden.
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 von Hand. Installieren Sie PyJWT (pip install pyjwt) und lassen Sie es die base64url-Teile, das Padding und die Signatur in einem Aufruf bauen:
import jwt
# Ein Schlüssel unter 32 Bytes erntet PyJWTs InsecureKeyLengthWarning, ein faires Gemurmel 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 macht PyJWT genau das, was dieser Artikel beschreibt: Serialisieren zu JSON, durch den urlsafe-Kodierer schicken und die Pads abschneiden, gemäß der JWS-Definition in RFC 7515. Wenn Sie jemals ein Stück von Hand zusammenbauen müssen, für ein Test-Fixture oder eine Debugging-Sitzung, ist das Rezept dieselbe Rechenkunde ü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 Richtung des Vertrauens: Ein Token zu bauen ist die leichte Hälfte. Der Empfänger muss die Signatur verifizieren, bevor er einem einzigen Claim vertraut, und PyJWT 2.x dekodiert kein Token ohne eine explizite algorithms-Liste, was eine Funktion ist, denn der "beliebiger Algorithmus"-Fehler ist eine der teuersten Zeilen Authentifizierungs-Code, 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 im Protokoll: Basic-Auth (RFC 7617), bei dem der Client user:pass sendet, 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 bereits in Ihrem Stack ist, baut es diesen Header für Sie mit auth=("jane", "pa:ss"), was sich auszahlt, weil es das Kodierungs-Detail aus Ihrem Code heraushält. Und seien Sie ehrlich darüber, was gerade passiert: RFC 7617 ist unverblümt, dass das Verfahren "keine sichere Methode zur Benutzerauthentifizierung ist und die im Klartext übertragene Entität in keiner Weise schützt". Die Credentials sind von jedem, der den Verkehr sieht, in einer Zeile Code wiederherstellbar, also ist das eine Bequemlichkeit für TLS-geschützte Verbindungen, nicht eine 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 dem Base64 eines SHA-1-Hashes des an eine magische GUID geklebten Schlüssels antwortet:
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 exakt der Wert aus dem Beispielauftrag im RFC selbst, was eine schöne Art ist, eine Implementierung von Grund auf zu prüfen. In der Produktion erledigt die websockets-Bibliothek diesen Schritt für Sie an beiden Enden; Sie basteln ihn nur von Hand, wenn Sie den winzigen 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 durch die nur-Text-Welt von SMTP zu retten, gemäß RFC 2045s Content-Transfer-Encoding: base64. Pythons email-Paket baut die Nachricht, wählt die Kodierung und bricht den Body bei der Standard-Zeilenlänge um, ohne dass Sie eine einzige 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-Zeilen ein und stempelt den Transfer-Encoding-Header auf, was genau das encodebytes-Verhalten von früher ist, nur vom Framework angewendet. Wenn Sie ein nacktes Snippet statt einer vollständigen Nachricht zusammenbauen, macht email.encoders.encode_base64(obj) ein einzelnes Kodieren-und-Umbrechen direkt auf einem Nachrichtenobjekt. Und wenn die Nachricht am anderen Ende ankommt, die Dekodier-Seite der Geschichte, von get_payload(decode=True) bis zum Header-Dekodieren, wird 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, ein schließendes Label. Die Panzerung ist leicht durchzusehen, denn der Body ist nur die umgebrochene Ausgabe, die Sie bereits kennen:
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-----
Die beiden Label-Zeilen abtrennen, den Rest zusammenfügen, und b64decode reicht Ihnen die DER-Bytes zurück. In der Produktion werden Sie das fast nie von Hand machen: 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 Pfad lohnt sich in dem spezifischen Moment, wenn die rohen DER-Bytes bereits in Ihren Händen sind - eine Datenbanksäule, eine Konfigurationsdatei, ein Puffer aus einem Protokoll - und die Spezifikation vor Ihnen sagt "PEM, bitte".
Dateien verschiffen: Uploads, Downloads und die .b64-Gewohnheit
Der älteste Use-Case im Internet ist eine Binärdatei, die durch einen Kanal muss, der nur Text trägt: ein FTP, das Zeilenenden zerhackt, ein Formular, das Uploads verweigert, ein Chat-Fenster, das Binär frisst. Das Rezept lautet lesen, kodieren, verschiffen und dem entfernten Ende 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-Endung ist eine Gemeinschafts-Gewohnheit, kein Standard, also muss die empfangende Seite die Gewohnheit auch kennen - deshalb wickeln JSON-APIs den Payload normalerweise in ein benanntes Feld wie "image_base64" ein und sagen es in ihrer Dokumentation. Und die umgebrochenen 76-Zeichen-Zeilen von encodebytes sind das Format der Wahl für die Datei auf der Festplatte, denn sie lassen sich sauber durch jedes Text-Werkzeug kopieren, das Menschen besitzen, von Mail-Clients bis PDF-Readern. Die umgekehrte Richtung, eine solche Datei zurück in Bytes zu lesen, ist ein Aufruf im verwandten Dekodierungs-Artikel; hier sind Sie nur der Sender, und der Job des Senders ist, konsistent zu sein.
Die Speicher-Frage: Konfigurationsdateien, Umgebungsvariablen und Datenbanken
Entwickler lieben es, Base64 an Orte zu stellen, die nur Text akzeptieren: 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 Obfuskation, die hält, und es ist keine Verschlüsselung. Abschnitt 12 von RFC 4648 formuliert es so offen, wie ein RFC kann: Base-Kodierung "verdeckt optisch Informationen, die sich sonst leicht erkennen ließen, wie Passwörter, bietet aber keine rechnerische Vertraulichkeit". Eine .env-Datei mit Base64-Geheimnissen schützt Sie vor dem, der hineinblickt, nicht vor dem, der liest, und ein base64 -d-Kommando später sitzt das "Geheimnis" in Klartext in ihrem Terminal. Wenn die Daten wirklich sensibel sind, verschlüsseln Sie sie zuerst - das cryptography-Paket bringt Fernet genau dafür mit - und Base64 erst dann den Chiffrat, falls Ihr Speicher Text verlangt.
Millionen Bytes später: Big Data und Chunking
b64encode ist eine C-Geschwindigkeits-Funktion - auf einem typischen Laptop verarbeitet sie ein Megabyte in ungefähr einer Millisekunde - aber sie ist keine Streaming-Funktion. Es gibt nirgends in der Standardbibliothek ein Update-und-Finish-Paar, also bedeutet das Kodieren von Daten, die größer sind als Sie im Speicher halten wollen, die Grenz-Rechenkunde selbst zu machen. Drei Eingabe-Bytes machen 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 Aufruf, denn die Drei-Byte-Naht ist der einzige Ort, an dem die Gruppierung brechen kann. Das Padding erscheint genau einmal, auf dem letzten Chunk, was ein strikter Dekodierer auf der anderen Seite erwarten wird. Die iter(lambda: handle.read(65536), b"")-Zeile ist das Standard-Idiom zum 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 Rechenkunde auf, statt sie zu wiederholen.
Wo Kodierer schiefgehen
Die Kodier-Seite hat weniger Fallen als die Dekodier-Seite, denn es gibt weniger, was schiefgehen kann, wenn Sie derjenige sind, der die Buchstaben produziert. Trotzdem tauchen diese jede Woche auf, und jede einzelne davon hat eine zwei-Minuten-Reparatur, wenn Sie sie früh erkennen:
- Einen String in den Kodierer füttern. Der
TypeErroraus dem Typmauer-Abschnitt. Reparieren Sie ihn 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; dasstrist 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 rollen.
str.replace("+", "-").replace("/", "_")funktioniert, aber es sind zwei Buchstaben Wartungs-Schulden, wourlsafe_b64encodeein Aufruf ist. Schlimmer: ein halbfertiger Tausch, Plusse repariert und Schrägstriche vergessen, produziert ein Alphabet, das mit keiner Spezifikation übereinstimmt. - Die Pads in einer URL lassen. Ein abschließendes
=in einer Query-String wird von einem Werkzeug percent-kodiert und von einem anderen abgestripen, und die Padding-Mathematik des Empfängers bricht auf die verwirrendste Art. Streifen Sie sie ab; die Länge sagt dem Dekodierer alles, was er braucht. - Umbrechen, wo es nicht erwünscht ist.
encodebytes-Zeilenumbrüche sind korrekt für E-Mail und PEM und Gift für ein JSON-Feld oder eine URL. Ein einzelner verirrter Zeilenumbruch reicht aus, um einen strikten Dekodierer auf der anderen Seite eine Ausnahme über Ihre Daten werfen zu lassen, nicht über Ihr Format. - Doppelt kodieren. Die Daten waren bereits stromaufwärts 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 zurück auf die erste Kodierung dekodiert. Einmal hin und zurück, die magischen Bytes prüfen, und aufhören. - Base64 mit Geheimnissen vertrauen. Die Warnung aus dem Speicher-Abschnitt, wiederholt, weil sie echtes Geld kostet: Wenn das Bedrohungsmodell jeden einschließt, der die Datei liest, brauchen Sie eine Chiffre, nicht ein Alphabet.
Ein Changelog, das man tatsächlich lesen kann
Das Alter des Moduls zeigt sich in stillen, datierten Verbesserungen statt in Revolutionen. Die Kurzfassung, in der Reihenfolge, in der die Teile landeten, aus der Sicht des Kodierers:
| Version | Was passierte |
|---|---|
| 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 obsolet erklärt, eine Umbenennung, über die alte Tutorials immer noch stolpern |
| Python 3.4 (2014) | jeder Kodierer akzeptiert jedes bytes-ähnliche Objekt, und a85encode und b85encode treten zum Modul bei |
| Python 3.6 (2016) | binascii.b2a_base64 lernt einen newline-Schalter, der es b64encode erlaubt, eine einzige endlose Zeile zu bleiben |
| Python 3.9 (2020) | die Legacy-Namen encodestring und decodestring werden endlich entfernt |
| Python 3.10 (2021) | b32hexencode und b32hexdecode, die sortierbaren Hex-Alphabet-Cousins |
| Python 3.13 (2024) | z85encode und z85decode, ZeroMQs Alphabet, treten zur Familie bei |
| Python 3.14 (2025) | schnellere Imports in der ganzen 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-Modul binascii zu delegieren, und diese Delegation gilt noch heute. Die erste Änderung der Bytes-Ära, ein Commit von 2007 während der Python-3-Entwicklung, der alles dazu brachte, überall Bytes zu verwenden, ist der Ort, aus dem die Typmauer in diesem Artikel kam, und es ist der Grund, warum ein moderner Kodierer Bytes nimmt und Bytes zurückgibt, wobei alles andere ein Wrapper um diesen einzelnen Vertrag ist.
Dinge, die das Modul Ihnen nicht sagt
Die ernste Arbeit ist erledigt, also hier die kleinen Freuden auf der Kodier-Seite des Kontos:
- Das eigene Beispiel der Dokumentation hat dieselbe Demonstration seit über einem Jahrzehnt laufen:
b'data to be encoded'geht hinein,b'ZGF0YSB0byBiZSBlbmNvZGVk'kommt heraus. Sie haben dieses Paar schon einmal getroffen, ob Sie es wissen oder nicht. - Die C-Funktion unter
b64encodefügt ihren abschließenden Zeilenumbruch mit einem Kommentar hinzu, der "Fügt einen höflichen Zeilenumbruch an" lautet. Eine ganze Kultur, in einer Quellzeile. - Das Wort
passwordkodiert zucGFzc3dvcmQ=, deshalb sieht Base64 in einer Logdatei für einen Scanner aus wie ein Geheimnis und ist für einen Leser ein Kommando davon, es zu sein. b64encodebricht nie um. Je. Ein Gigabyte Eingabe produziert eine einzige 1,3-Gigabyte-Zeile, und die Funktion blinkt nicht. Wenn Sie Zeilen wollten, mussten Sie umencodebytesbitten.- Die Docstring des Moduls nennt immer noch RFC 3548, die Ausgabe von 2003 der Spezifikation. RFC 4648 übernahm 2006; die Docstring hat es einfach nie bemerkt.
- Python 2 hatte überhaupt keine Typmauer:
b64encodeakzeptierte gerne einenstrund gab einen zurück. Die Bytes-Überholung von 2007 beendete das, und die alten Python-2-Tutorials sind der Ort, auf den die meisten "warum crasht mein Kodieren"-Threads immer noch zeigen. z85encode, das neueste Familienmitglied (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 Doku legt das Padding auf Sie: Die Eingabe muss als Vielfaches von 4 Bytes ankommen (der Kodierer wird es nicht für Sie padden; eine 3-Byte-Eingabe erzeugt einen 4-Zeichen-Frame, den kein ZeroMQ-Peer akzeptieren wird).
Und hier ist die Philosophie des Kodierers in drei Regeln. Entscheiden Sie zuerst die Bytes und dann erst die Kodierung, denn die Typmauer 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 Pads für E-Mail und Dateien, base64url ohne Pads für URLs und Tokens, und Improvisieren Sie niemals 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, 76-Zeichen-Zeilen für MIME und PEM, denn der Dekodierer auf der anderen Seite wird es Ihnen nachhalten.
Wenn diese Buchstaben am anderen Ende ankommen, beginnt der Spaß erst richtig: fehlende Pads, stille Verwerfungen, Payloads, die nicht ganz Base64 sind, und ein Dekodierer mit zwei Stimmungen, die man navigieren muss. All das wird im verwandten Base64-Dekodierungs-Artikel unten auf dieser Seite im Detail 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