Haben Sie mit dem Base64-Format zu tun? Dann ist diese Website genau das Richtige für Sie! Nutzen Sie unser superpraktisches Online-Tool, um Ihre Daten zu kodieren oder zu dekodieren.

Base64-Dekodierung in Python: Ein vollständiger Leitfaden

Irgendwo in Ihrem Code ist gerade eine Zeichenkette gelandet, die gar nicht nach Text aussieht: eine lange Reihe von A bis Z, ein paar Ziffern, ab und zu ein + oder /, vielleicht ein - oder _, und dazu noch ein oder zwei = Zeichen, die am Ende parken. Hinter diesem String könnte der Payload eines JWT stecken, den Ihr Gateway abgelehnt hat, ein Bild, das in einer HTML-Seite versteckt ist, eine Datei, die Ihnen jemand als .b64-Anhang geschickt hat, oder ein Zertifikatsblock in einem Ticket, das bereits durch drei Helpdesks gewandert ist. Ihre Aufgabe ist es, die originalen Bytes zurückzugeben, genau so, wie sie waren. Und Python ist für diesen Job in bester Stimmung, denn das gesamte Werkzeug gehört seit Jahrzehnten zur Standardbibliothek: Eine Zeile import base64, und Sie sind auf jeder Plattform startklar - nichts zu installieren, nichts zu konfigurieren.

Ein kurzer Refresher, während Sie sich einrichten, denn jeder braucht es einmal im Jahr: Base64 schreibt jeweils drei Bytes der Daten als vier Zeichen aus einem Alphabet von 64 Zeichen um, und wenn die letzte Gruppe von drei Bytes unvollständig ist, füllt =-Padding die Gruppe auf, damit die Ausgabe immer in Vieren kommt. Das ist der gesamte Trick. Es ist keine Kompression und keine Geheimhaltung, nur ein Weg, Binärdaten durch Kanäle zu bringen, die nichts als Text akzeptieren. Die Startseite dieser Site geht das Format in voller Tiefe durch, inklusive Alphabet und Padding-Mathematik, also geben wir unsere Energie dort aus, wo es wirklich wehtut: auf der Python-Seite des Dekodierens und darauf, das Ergebnis ehrlich zu halten.

Drei Fakten formen alles, was folgt, und sie lohnen es, eingeprägt zu werden, bevor Sie noch eine Zeile weiterlesen. Erstens hat der Dekodierer zwei Stimmungen: einen höflichen, nachsichtigen Standard, der alles, was er nicht erkennt, still verwirft, und einen strikten Modus, der solche Eingabe glatt und ohne Wenn und Aber ablehnt. Zweitens ist das Ergebnis eines Dekodierens immer ein bytes-Objekt, niemals ein String, und der Moment, in dem Sie daraus echten Text machen wollen, ist eine Entscheidung, die Sie bewusst treffen müssen. Drittens gibt es zwei Alphabete, die sich fast identisch sehen, das Standardalphabet und das URL-sichere, und sie zu verwechseln ist eine beliebte Methode, Daten zu verlieren, ohne auch nur einen einzigen Fehler zu bekommen. Dieser Leitfaden führt Sie an allen drei vorbei, damit Sie beim nächsten Mal, wenn eine Mauer aus Unsinn in Ihrem Terminal landet, lächeln können, statt die Augen zusammenzukneifen.

Das komplette Dekodier-Menü

Öffnen Sie das base64-Modul, und Sie finden zwei Generationen von Schnittstellen nebeneinander. Die moderne, zentriert um b64decode, wandelt bytes-ähnliche Objekte (und ganz gewöhnliche ASCII-Strings) wieder in Bytes um, und sie spricht beide Base64-Dialekte, die in RFC 4648 definiert sind. Die Legacy-Variante ist älter und dateiorientiert: Sie arbeitet mit Dateiobjekten, kennt nur das Standardalphabet, und sie wurde um die auf 76 Zeichen umgebrochenen Zeilen herumgebaut, die RFC 2045, der MIME-Mail-Standard von 1996, von kodierter Ausgabe verlangte. Die Legacy-Namen werden Sie in reichlich Code antreffen, der schon eine Weile herumläuft, also hier die komplette Dekodier-Seite des Menüs:

Funktion Was sie tut Anmerkungen
base64.b64decode(s, altchars=None, validate=False) das Arbeitstier: ein Base64-Blob zurück in rohe Bytes akzeptiert Bytes oder einen ASCII-String, gibt immer Bytes zurück
base64.standard_b64decode(s) derselbe Job, festgelegt auf das Standardalphabet praktisch, wenn Sie den Dialekt sicher kennen
base64.urlsafe_b64decode(s) liest das URL-sichere Alphabet mit - und _ die Funktion, die JWTs liest
base64.decodebytes(s) dekodiert eine oder mehrere umgebrochene Base64-Zeilen ab Python 3.1, der MIME-freundliche Weg, nachsichtig
base64.decode(input, output) streamt eine Base64-Datei in eine Rohdatei Legacy, liest Zeile für Zeile, nachsichtig
base64.b32decode(s, casefold=False) dekodiert den kleineren Base32-Cousin casefold akzeptiert Kleinschreibung als Eingabe
base64.b16decode(s, casefold=False) dekodiert Base16, das schlichtes Hexadezimal ist bis zu sechsmal schneller in Python 3.14
binascii.a2b_base64(s, strict_mode=False) die C-Funktion, die die eigentliche Arbeit macht direkter Griff auf die Strenge, mit strict_mode seit Python 3.11

Alles, was unten folgt, baut auf die erste Zeile auf. Ein Fakt lohnt es, vor dem Vertiefen gewusst zu werden: In der offiziellen Dokumentation ist das Modul unter "Internet Data Handling" zu finden, direkt neben binascii, und diese Platzierung ist kein Zufall. b64decode ist ein dünner Wrapper, der das Alphabet übersetzt (wenn Sie altchars übergeben) und dann der C-Funktion binascii.a2b_base64 die schwere Arbeit überlässt. Genau deshalb ist die Funktion schnell, und genau deshalb haben ihre Fehlermeldungen den knappen, gefühllosen Geschmack von C.

Das Arbeitstier: b64decode

Hier ist der gesamte Vertrag, kurz genug, um ihn im Kopf zu behalten. Die Funktion nimmt ein bytes-ähnliches Objekt oder einen ASCII-String, einen optionalen Zwei-Zeichen-Alphabettausch und eine Validierungs-Flag. Sie gibt ein bytes-Objekt zurück. Bei einem Fehler löst sie binascii.Error aus, was eine Unterklasse von ValueError ist, falls Sie jemals eine ganze Ausnahme-Familie auf einmal fangen müssen:

import base64
data = base64.b64decode("Zm9vYmFy")
print(data)
# b'foobar'
print(type(data))
# <class 'bytes'>

Diese letzte Zeile ist die wichtigste Zeile in diesem ganzen Artikel. Das Ergebnis sind Bytes, kein String, und Python nimmt Sie genau so lange an der Hand, wie es angebracht ist: Der Ausdruck des Objekts zeigt Ihnen die b'...'-Darstellung, und der Versuch, es an einen String zu kleben, löst einen TypeError aus. Im Moment, in dem Sie echten Text wollen, ist die Entscheidung bei Ihnen, und der Charset-Abschnitt unten deckt ab, wann diese Entscheidung leicht ist und wann sie eine Falle.

Das optionale altchars-Argument tauscht das + und / des Standardalphabets gegen ein anderes Zeichenpaar aus. Genau das ist der Regler, der den URL-sicheren Dialekt erzeugt, und genau so wird urlsafe_b64decode auf b64decode aufgebaut. Sie werden selbst nur selten nach altchars greifen, aber es ist gut zu wissen, dass der Mechanismus da ist. Für alles andere erledigt die Funktion einfach ihren Job, schnell, in C.

Nachsichtig von Haus aus, streng auf Wunsch

Standardmäßig ist b64decode ein höflicher Vergesser. Jedes Zeichen, das nicht im 64-Zeichen-Alphabet (und nicht in Ihren altchars) steht, wird still weggeschmissen, bevor das Dekodieren beginnt, und was übrig bleibt, wird dekodiert. Keine Warnung, keine Meldung, kein Rückgabewert, den Sie prüfen könnten, einfach ein Ergebnis. Diese Nachsicht hat einen edlen Ahnen: Abschnitt 6.8 von RFC 2045 sagt Dekodierern, dass "alle Zeilenumbrüche oder anderen Zeichen, die in Tabelle 1 nicht zu finden sind, ignoriert werden müssen", weil SMTP historisch lange Zeilen umgebrochen und dabei Streuzeichen verteilt hat. Ein Payload, der einen Mail-Client, eine Chat-App oder eine PDF-Kopie durchquert hat, dekodiert oft ganz ohne Vorbereitung, und das ist eine echte Superkraft.

Dieselbe Freundlichkeit ist auch der Grund, warum der Standard-Dekodierer als Validator nutzlos ist. Abschnitt 12 von RFC 4648 beschreibt das Risiko im Detail: Nicht-Alphabet-Zeichen zu ignorieren, statt die ganze Kodierung abzulehnen, öffnet einen verdeckten Kanal, mit dem Informationen geleakt werden können, und es kann String-Gleichheitsprüfungen brechen, weil zwei verschiedene Eingaben auf dieselben Bytes dekodiert werden können. Für alles, was Sie nicht selbst kodiert haben, übergeben Sie validate=True und behandeln Sie die Ausnahme als die Antwort. Hier ist der Schadensbericht, jede Zeile auf jedem modernen Python reproduzierbar:

Was reinkommt Nachsichtig (Standard) validate=True
Zm9vYmFy (ein sauberer Payload) b'foobar' b'foobar'
Zm9v\r\nYmFy (Zeilenumbruch in der Mitte) b'foobar' binascii.Error
Zm9v YmFy (zusätzliche Leerzeichen) b'foobar' binascii.Error
Zm9v!YmFy (ein verirrtes Ausrufezeichen) b'foobar' binascii.Error
junkZm9vYmFy (ein Wort vor dem Payload) b'\x8e\xe9\xe4foobar' b'\x8e\xe9\xe4foobar'
Zm9v=YmFy (ein Pad in der Mitte) b'foobar' binascii.Error
=Zm9v (Padding ganz vorne) b'foo' binascii.Error
==== (vier Pads, keine Daten) b'' binascii.Error
(leere Eingabe) b'' b''

Sehen Sie der nachsichtigen Spalte bei ihrer stillen Arbeit zu. Die Zeile, die die Leute zuerst überrascht, ist die mit dem Wort vorne drin: Alle vier Buchstaben von junk sitzen zufällig im Base64-Alphabet, also dekodiert der "Mist" zu drei echten Bytes und wird mit geradem Blick an Ihren Payload geklebt. Der strikte Modus rettet hier nicht, denn die Eingabe ist wirklich gültiges Base64; es sind die anderen Zeilen, die flach abgewiesen werden, und die Ablehnungen haben genau eine Form: ein binascii.Error mit einer von einigen einprägsamen Meldungen:

  • Incorrect padding - die Länge ist nach dem Verwerfen kein Vielfaches von vier, oder eine letzte Gruppe ist zu kurz. Ein String wie Zm9vYmE ohne jegliche Pads landet hier.
  • Invalid base64-encoded string: number of data characters (N) cannot be 1 more than a multiple of 4 - die Eingabe ist um genau ein Zeichen zu kurz für die nächste Gruppe. Das ist der klassische Fingerabdruck eines abgeschnittenen oder per Copy-Paste übergebenen Payloads.
  • Only base64 data is allowed - ein Nicht-Alphabet-Zeichen hat es in den strikten Modus geschafft, und ein einzelner Zeilenumbruch zählt als eines.
  • Excess padding not allowed - Pads in der Mitte des Strings, oder mehr Pads, als die letzte Gruppe erlaubt.
  • Leading padding not allowed - der String beginnt mit =.
  • Und einer aus einer anderen Familie: ValueError: string argument should contain only ASCII characters, den Sie bekommen, wenn Sie einen String mit Nicht-ASCII-Buchstaben übergeben. Strings werden akzeptiert, aber nur ASCII-Strings.

Hinter den Kulissen ist validate=True überhaupt kein eigener Code-Pfad. Das Modul reicht die Flag als strict_mode-Parameter an binascii.a2b_base64 weiter, den Strenge-Check, der in Python 3.11 zu binascii hinzugekommen ist. Damit haben Sie einen direkten Griff, wenn Sie Strenge wollen, ohne die Base64-Ebene zu durchlaufen:

import binascii
line = b"Zm9vYmFy"
print(binascii.a2b_base64(line, strict_mode=True))
# b'foobar'

Eine Eigenheit, die Sie festnageln sollten, bevor Sie dem strikten Modus blind vertrauen: Er weist sogar einen einzelnen abschließenden Zeilenumbruch zurück, also ist ein MIME-umwickelter Block ein Job für den nachsichtigen Pfad oder für decodebytes, nicht für validate=True. Halten Sie den strikten Pfad für Daten, die Sie makellos sauber erwarten, wie ein frisches Token direkt aus Ihrem eigenen Code.

base64url, das Alphabet, das in URLs passt

Das Standardalphabet hat zwei Zeichen, die URLs hasen. Das + Zeichen wird von jedem Form-Dekodierer als Leerzeichen gelesen, und das / Zeichen ist für Pfadtrenner reserviert. Abschnitt 5 von RFC 4648 definiert den Cousin-Dialekt, in dem + zu - und / zu _ wird, und in dem das Padding weggelassen wird, wann immer die Datenlänge aus dem Kontext bekannt ist. Der RFC gibt der Variante sogar einen ordentlichen Namen, base64url, und besteht darauf, dass sie nicht einfach "base64" genannt werden soll. Am häufigsten treffen Sie sie in JSON Web Tokens, wo jeder Teil des Tokens base64url ohne Padding ist, und sie taucht auch in OAuth-Tokens und API-Cursor-Parametern auf.

Python bringt eine dedizierte Funktion dafür mit, urlsafe_b64decode. Sie übersetzt die Bindestriche und Unterstriche zurück in Plusse und Schrägstriche und dekodiert dann, aber sie wird das Padding nicht für Sie wieder anfügen. Eingabe ohne Padding ist der Normalfall für JWTs, also kommt die Rechenzeile zuerst, und sie ist dieselbe, die Bibliotheken wie PyJWT unter der Haube verwenden:

import base64
segment = "Zm9vYmE"
padded = segment + "=" * (-len(segment) % 4)
print(base64.urlsafe_b64decode(padded))
# b'fooba'

Der Ausdruck "=" * (-len(segment) % 4) sieht nach einem Trick aus, aber er ist der gesamte Job: Er produziert null, eins oder zwei Pads, aber nie drei, sodass ein bereits gepaddeter String unangetastet durchgeht. Der negative Modulo ist es, der ihn für Strings jeder Länge funktionieren lässt, und er ist die eine Zeile Base64-Rechenkunde, die jeder Python-Entwickler irgendwann mindestens einmal tippt.

Und nun die gefährliche Verwechslung, denn die beiden Alphabete sehen sich ähnlich genug, um zu verwirren. Schicken Sie einen base64url-String durch den Standard-Dekodierer, und die Bindestriche und Unterstriche sind einfach nicht im Standardalphabet, also schluckt der nachsichtige Dekodierer sie und dekodiert, was übrig bleibt. Bei manchen Payloads ist das ein verhexter Byte-Strom; bei anderen ist es überhaupt nichts:

import base64
tricky = base64.urlsafe_b64encode(b"\xfb\xff\xfe")
print(tricky)
# b'-__-'
print(base64.standard_b64decode(tricky))
# b'' - jedes Zeichen wurde still verworfen
print(base64.urlsafe_b64decode(tricky))
# b'\xfb\xff\xfe'

Die umgekehrte Richtung ist nachsichtig, und genau das lässt die Verwechslung unbemerkt: urlsafe_b64decode übersetzt zuerst sein Alphabet und dekodiert dann nachsichtig, also akzeptiert es gerne auch einen Standardalphabet-String mit + und / darin. Die Lektion ist nicht, sich etwas zusammenzudenken. Sie besteht darin, eine Funktion pro Dialekt zu wählen und dabei zu bleiben, so wie bei einer Fremdwährung: Geben Sie Yen dort aus, wo Yen gültig sind, nicht an der falschen Wechselstube.

Die Ausgabe sind Bytes: Das Charset-Gespräch

Hier ist der Satz, der die Hälfte der Charset-Fragen beantwortet, die die Leute mit zu Base64 bringen: b64decode dekodiert Bytes; es dekodiert keinen Text. Es gibt kein Charset-Argument, es gibt keine Konvertierung, und nichts an der Eingabe sagt Python, was die Bytes bedeuten sollen. Die Bedeutung ist etwas, das Sie aus dem Kontext beisteuern müssen, und dieser Kontext ist fast immer eines von drei Dingen: ein Header, der es sagt, ein API-Vertrag, der es sagt, oder eine magische Zahl, die sich in den Bytes selbst versteckt.

import base64
raw = base64.b64decode("w6l0w6k=")
print(raw)
# b'\xc3\xa9t\xc3\xa9'
print(raw.decode("utf-8"))
# été

Dieselbe Idee mit dem falschen Etikett ist ein lautes Scheitern, und das ist Gnade. Bytes, die kein gültiges UTF-8 sind, verweigern es, ein String zu werden, und die Ausnahme sagt Ihnen genau, welches Byte sich geärgert hat:

import base64
raw = base64.b64decode("/w==")
try:
  raw.decode("utf-8")
except UnicodeDecodeError as caught:
  print(caught)
# 'utf-8' codec can't decode byte 0xff in position 0: invalid start byte

Drei Faustregeln halten diesen Abschnitt davon ab, eine Horrorshow zu werden. Eins: Wenn die dekodierten Daten JSON sind, müssen Sie überhaupt nicht manuell dekodieren, denn json.loads akzeptiert seit Python 3.6 Bytes direkt und erkennt UTF-8, UTF-16 und UTF-32 von allein. Zwei: Binär ist kein Text, also ist ein "erkanntes" Charset für eine PNG ein glücklicher Schuss statt einer Tatsache; prüfen Sie die Bytes statt des Etiketts. Drei: Wenn der Sender Ihnen das Charset gesagt hat, glauben Sie dem Sender, denn ein Content-Type-Header oder ein API-Dokument schlägt jedes Erkennungs-Tool, jedes einzelne Mal.

Wo dekodiertes Base64 in Python-Code auftaucht

Nach einer Weile beginnen Sie, die Formen zu erkennen. Hier ist der Feldführer zu den Orten, an denen dekodiertes Base64 in einer Python-Anwendung auftaucht, und das Einzeilen-Rezept für jeden. Die folgenden Abschnitte behandeln die häufigsten in voller Länge:

Sie finden es in Was es ist Wie Sie es lesen
Ein JWT Header-, Payload- und Signaturteile (RFC 7519) am Punkt trennen, urlsafe_b64decode mit dem Padding-Fix
Ein Authorization-Header HTTP-Basic-Credentials, user:pass (RFC 7617) das Basic-Präfix abtrennen, dekodieren, am ersten Doppelpunkt trennen
Ein data:-URI Inline-Medien in HTML oder CSS (RFC 2397) am ersten Komma abschneiden, den Rest dekodieren
Ein E-Mail-Anhang ein Content-Transfer-Encoding: base64-Body (RFC 2045) get_payload(decode=True) auf dem Nachrichtenteil
Ein E-Mail-Headerwert ein =?charset?b?...?= Encoded-Word (RFC 2047) dem email-Paket das Dekodieren überlassen
Eine PEM-Datei ein gepanzerter Schlüssel oder ein Zertifikat (RFC 7468) die Panzerzeilen wegwerfen, den Body nach DER dekodieren
Ein JSON-API-Feld Binär, das sich als String durchschleicht dekodieren, dann das Ergebnis als Bytes behandeln, nicht als Text
Ein TEXT-Feld oder eine Umgebungsvariable Binär oder JSON, gespeichert an einem nur-Text-Platz dekodieren, dann parsen oder schreiben, mit dem vereinbarten Charset

Einen JSON Web Token lesen

Ein JWT ist drei base64url-Stücke, die durch Punkte verbunden sind: ein Header, ein Payload und eine Signatur. Die ersten beiden sind schlichtes JSON, also ist ein Blick hinein jeweils eine Zeile, mit dem Padding-Fix aus dem Abschnitt oben:

import base64
import json
def read_part(segment):
  padded = segment + "=" * (-len(segment) % 4)
  return base64.urlsafe_b64decode(padded)
token = ("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9."
         "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0."
         "8Rmup2hf8jZvoBgoCRqRWlBFNtvUYmA0eR7YKellPMs")
head, body, _signature = token.split(".")
print(json.loads(read_part(head)))
# {'alg': 'HS256', 'typ': 'JWT'}
print(json.loads(read_part(body)))
# {'sub': '1234567890', 'name': 'John Doe'}

Ein Hinweis zum Umfang, denn er ist wichtig: Ein Token auf diese Weise zu inspizieren ist ein Debugging-Werkzeug, kein Authentifizierungsmechanismus. Dass der Payload lesbar ist, bedeutet nicht, dass er echt ist; ein Angreifer kann die ersten beiden Segmente fälschen, ohne je Ihr Geheimnis zu kennen. Für echte Verifizierung übergeben Sie den Token an PyJWT (pip install pyjwt), der die Signatur prüft und ohne eine explizite Algorithmus-Liste nicht dekodiert:

import jwt
# Ein Schlüssel unter 32 Bytes erntet PyJWTs InsecureKeyLengthWarning (PyJWT 2.11+), ein faires Gemurmel für einen Demo-Schlüssel.
decoded = jwt.decode(token, "super-secret-key", algorithms=["HS256"])
print(decoded)
# {'sub': '1234567890', 'name': 'John Doe'}

Mit einem falschen Schlüssel bekommen Sie eine Ausnahme statt eines Wörterbuchs, was genau das Verhalten ist, das Sie in Produktionscode wollen. Und wenn der Token mit einem abgelaufenen Zeitstempel ankam, wirft PyJWT auch dafür aus, so dass Sie sich die Claim-Namen nie selbst merken müssen.

Ein Data URI öffnen

Data-URIs betten Medien direkt in HTML oder CSS ein, damit der Browser keine zweite Anfrage schießt: data:, der Medientyp, das Wort base64, ein Komma und die kodierten Bytes. Der Schnitt ist am ersten Komma, Punkt, und alles danach ist ein gewöhnlicher Standardalphabet-Payload:

import base64
uri = ("data:image/png;base64,"
       "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJ"
       "AAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==")
mime, payload = uri.split(",", 1)
data = base64.b64decode(payload)
print(mime)
# data:image/png;base64
print(data[:8])
# b'\x89PNG\r\n\x1a\n'

Die PNG-Signatur aus acht Bytes am Anfang des Ergebnisses ist eine günstige und fröhliche Kontrolle, dass Sie das Richtige dekodiert haben. Zwei Fallen verdienen eine Erwähnung. Wenn das URI von einer gescrapten Seite oder einer Chat-Nachricht kam, entfernen Sie zuerst HTML-Entitäten und verirrten Leerraum, denn der nachsichtige Dekodierer verzeiht viel Mist und reicht Ihnen ein beschädigtes Bild statt eines Fehlers. Und wenn Sie unzuverlässige Eingabe in Masse dekodieren, übergeben Sie validate=True: Ein Data-URI, das die strenge Validierung nicht besteht, ist ein Data-URI, das nie wohlgeformt war, und Sie wollen es nicht aus einem Bauchgefühl heraus auf die Festplatte schreiben.

Den Authorization-Header knacken

Die Basic-Authentifizierung (RFC 7617) ist das älteste Verfahren in HTTP, und es verankert immer noch eine überraschende Anzahl von API-Integrationen, Webhooks und CI-Pipelines. Der Client sendet seine Credentials als user:pass, base64-kodiert, hinter dem Wort Basic:

import base64
header = "Basic amFuZTpwYTpzcw=="
decoded = base64.b64decode(header[len("Basic "):]).decode("utf-8")
user, _, password = decoded.partition(":")
print(user, password)
# jane pa:ss

Beachten Sie das partition, denn es ist das Detail, das Sie später rettet: Das Passwort darf Doppelpunkte enthalten, die Benutzer-ID darf sie nicht, und nur der erste Doppelpunkt ist der Trenner. Ein ehrlicher Hinweis, denn der RFC selbst ist offenherzig dazu: Base64 ist keine Verschlüsselung. RFC 4648 sagt, dass Base-Kodierung "optisch Informationen verbirgt, die sich sonst leicht erkennen ließen, wie Passwörter, aber keine rechnerische Vertraulichkeit bietet". Ein Basic-Header kann von jedem dekodiert werden, der den Verkehr sieht, also behandeln Sie ihn als Bequemlichkeit für TLS-geschützte Verbindungen, nicht als Sicherheitsgrenze. Wenn Sie derjenige sind, der den Header sendet, baut requests ihn für Sie mit auth=("jane", "pa:ss"), was sich auszahlt, wann immer die Bibliothek schon in Ihrem Stack ist.

E-Mail, der ursprüngliche Kunde

Base64 wurde 1993 für genau einen Job standardisiert: Binärdaten durch E-Mail zu retten. RFC 2045, der MIME-Standard, definierte die Content-Transfer-Encoding: base64 Body-Kodierung, und sie ist immer noch der Standardweg, auf dem Anhänge über das Internet reisen. Pythons email-Paket erledigt den gesamten Job für Sie: Es parst die Header, dekodiert die =?utf-8?b?...?= Encoded-Words, die RFC 2047 in Headerfeldern versteckt, und dekodiert Bodies base64-mäßig, wenn Sie es verlangen:

import email
from email import policy
raw = (b"Subject: =?utf-8?b?w6l0w6k=?=\r\n"
       b"From: sender@example.com\r\n"
       b"To: reader@example.com\r\n"
       b"Content-Transfer-Encoding: base64\r\n"
       b"\r\n"
       b"w6l0w6kgbWFpbA==\r\n")
msg = email.message_from_bytes(raw, policy=policy.default)
print(msg["Subject"])
# été
print(msg.get_payload(decode=True))
# b'\xc3\xa9t\xc3\xa9 mail'

Der get_payload(decode=True)-Aufruf liest den Content-Transfer-Encoding-Header und dekodiert den Body für Sie base64-mäßig und wickelt dabei die 76-Zeichen-Zeilen auf. Das policy=policy.default-Argument wählt die moderne Schnittstelle von Python 3.6 an, als die neue policy-basierte E-Mail-API ihren Provisorium-Status verlor, was Ihnen dekodierte Headerwerte gleich aus der Hand gibt; der Legacy-Parser funktioniert immer noch, aber Sie dekodieren Encoded-Words am Ende von Hand. Sie gehen nur bis zu decodebytes hinunter, wenn Sie ein nacktes Snippet parsen, das keine vollständige Nachricht ist, wie ein Block, den jemand in ein Ticket gepastet hat. Für Multipart-Nachrichten iterieren Sie mit iter_attachments() und geben Sie jedem Teil dieselbe Einzeilen-Behandlung.

PEM-Panzerung und das cryptography-Paket

Eine PEM-Datei ist eine Header-Zeile, etwas umgebrochenes Base64 und eine Footer-Zeile, und nichts weiter. Die Panzerung ist Dekor; das Base64 ist die ganze Geschichte, denn es dekodiert zu der rohen DER-Struktur darunter. Das cryptography-Paket (pip install cryptography) kann das Ergebnis direkt laden, und genau deshalb ist es das Standardwerkzeug für alles, was Zertifikate und Schlüssel betrifft:

import base64
from cryptography import x509
pem = b"""-----BEGIN CERTIFICATE-----
MIIBGzCBwaADAgECAgEBMAoGCCqGSM49BAMCMBcxFTATBgNVBAMMDGV4YW1wbGUu
dGVzdDAeFw0yNjA4MjkxNzIxMzZaFw0yNjA4MzAxNzIxMzZaMBcxFTATBgNVBAMM
DGV4YW1wbGUudGVzdDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABPvNHjdF4b1n
SkBDT6UWtG2k8ICe45eL3kSkVfuhriev1uO9PBLMP50HWnrLbCXtl3lhWaVibctl
QbWRG4xqGLcwCgYIKoZIzj0EAwIDSQAwRgIhAKdFm5GLecg2fF7qUhSmKGtgNFaL
qVyKtDXK07N6GZd/AiEAtRXemnYqDMz77o9+VpM/NsNEwDi0yaVB+tKGLbdKJb0=
-----END CERTIFICATE-----
"""
body = b"".join(pem.splitlines()[1:-1])
der = base64.b64decode(body)
cert = x509.load_der_x509_certificate(der)
print(cert.subject.rfc4514_string())
# CN=example.test

In den meisten Produktionscode machen Sie Panzerung und Dekodieren nie von Hand: load_pem_x509_certificate akzeptiert die gepanzerten Bytes und erledigt den Base64-Schritt für Sie unter der Haube. Der manuelle Pfad lohnt sich, wenn die DER-Bytes bereits in Ihren Händen sind (eine Datenbanksäule, eine Konfigurationsdatei, ein Byte-Puffer aus einem Protokoll), oder wenn der Block in einem String verpackt ankam und Sie sehen wollen, was drin ist, bevor Sie ihm vertrauen. Schlüssel funktionieren auf dieselbe Weise, mit load_der_private_key, das auf der anderen Seite desselben Dekodierens wartet.

Dateien, magische Zahlen und die .b64-Gewohnheit

Dekodieren ist nur die Hälfte des Jobs; die Bytes wollen in der Regel eine Datei. Das Muster lautet lesen, dekodieren, prüfen, schreiben, und die Prüfung zählt, denn ein kaputter Payload würde sonst eine still und leise falsche Datei produzieren, die Sie Wochen später entdecken:

import base64
import binascii
with open("payload.b64", "rb") as handle:
  encoded = handle.read()
try:
  data = base64.b64decode(encoded, validate=True)
except binascii.Error:
  data = base64.b64decode(encoded)
with open("payload.bin", "wb") as out:
  out.write(data)

Für schnelle Einmal-Konvertierungen erledigt die Legacy-Datei-zu-Datei-Funktion die ganze Fahrt in einem einzigen Aufruf, umgebrochene Zeilen und alles:

import base64
with open("photo.b64", "rb") as src, open("photo.png", "wb") as dst:
  base64.decode(src, dst)

Was haben Sie gerade überhaupt dekodiert? Die ersten Bytes fast jedes gängigen Formats sind eine feste Signatur, und weil Base64 deterministisch ist, ist die kodierte Signatur auch fest. Einen dieser Präfixe zu sehen, ist wie ein Kennzeichen auf Distanz zu erkennen:

Das Base64 beginnt mit Es ist wahrscheinlich
iVBORw0KGgo ein PNG-Bild
/9j/ ein JPEG-Bild
R0lGODlh ein GIF-Bild
JVBERi0 ein PDF-Dokument
UEsDBA== ein ZIP-Archiv
UklGRg== ein RIFF-Container (WAV, WEBP, AVI)
LS0tLS1CRUdJTg== ein ASCII-gepanzerter Block ("-----BEGIN ...")

Und machen Sie die Größen-Mathematik, während die Datei schreibt, denn es ist die Zahl, die die Leute überrascht, wenn die Festplatte voll wird: Kodieren bläht Daten um etwa ein Drittel auf, also reist eine 300-KB-Datei als rund 400 KB Base64-Text, und die Datei, die Sie zurückdekodieren, hat die kleinere, ursprüngliche Größe. Ihre Festplatte, und Ihr Speicher, wenn Sie die ganze Datei auf einmal lesen, sollten für den Unterschied budgetieren.

Datenbanken, Konfigurationsdateien und Umgebungsvariablen

Base64 ist ein Favorit, um Binärdaten (oder JSON) durch Speicher zu schmuggeln, der nur Text akzeptiert: eine TEXT-Spalte, ein Wert in einer .ini-Datei, eine Umgebungsvariable in einer Deploy-Pipeline. Das Dekodier-Rezept ist dasselbe wie bei Dateien, nur ohne die Festplatte:

import base64
import json
stored = "eyJyb2xlIjogImFkbWluIiwicHJvamVjdCI6Im15c2l0ZSJ9"
payload = json.loads(base64.b64decode(stored))
print(payload)
# {'role': 'admin', 'project': 'mysite'}

Zwei Anmerkungen für diese Ecke des Hauses. Wenn der gespeicherte Wert JSON ist, überspringen Sie den Zwischenschritt .decode("utf-8") und lassen Sie json.loads die Bytes direkt nehmen, denn es tut das seit Python 3.6. Und eine ehrliche Warnung, denn hier lebt das teuerste Missverständnis im ganzen Artikel: Base64 in einer Umgebungsvariable oder einer Konfigurationsdatei ist ein Schild gegen den Menschen, der in die Datei schielt, nicht gegen den, der sie liest. Wenn der Wert wirklich sensibel ist, verschlüsseln Sie ihn zuerst (das cryptography-Paket bringt Fernet genau dafür mit) und Base64 erst dann den Chiffrat, falls Ihr Speicher Text verlangt.

Wenn der Payload in Stücken ankommt

Die Standardbibliothek hat keinen inkrementellen Base64-Dekodierer: Es gibt kein Update-und-Finish-Paar, also brauchen gestreamte Daten ein wenig Buchhaltung von Ihnen selbst. Die Rechnung ist einfach und gleichzeitig streng. Vier kodierte Zeichen machen drei Bytes, also können Sie nur komplette Vierer-Gruppen dekodieren, und Sie müssen den Rest in den nächsten Chunk mitnehmen:

import base64
def chunked_decode(chunks):
  out = []
  leftover = b""
  for chunk in chunks:
    buffer = leftover + chunk
    whole = len(buffer) // 4 * 4
    if whole:
      out.append(base64.b64decode(buffer[:whole]))
    leftover = buffer[whole:]
  if leftover:
    out.append(base64.b64decode(leftover + b"=" * (-len(leftover) % 4)))
  return b"".join(out)

Füttern Sie es mit einem Socket-Puffer, einer Datei, die in 64-KB-Stücken gelesen wird, oder einem Generator von Zeilen mit entfernten Zeilenumbrüchen, und die Ausgabe ist identisch zum Dekodieren des Ganzen in einem Zug. Wenn Ihre Eingabe garantiert sauber und nicht umgebrochen ist, halten Sie die Strenge, indem Sie jede komplette Gruppe mit validate=True dekodieren, und denken Sie daran, dass der letzte Rest den Padding-Fix brauchen kann, weshalb der Helfer ihn vor dem letzten Dekodieren ergänzt. Das ist dieselbe Naht-Logik, die die Kodierer auf der anderen Seite verwenden, nur mit vier Zeichen statt drei Bytes.

Von der Kommandozeile

Das base64-Modul dient zugleich als winziges Kommandozeilen-Tool, was praktisch ist, wenn der Payload in Ihrem Terminal statt in Ihrem Code sitzt. Kodieren ist der Standard; -d (oder sein Zwilling, -u) dekodiert:

echo -n "hello world" | python3 -m base64
aGVsbG8gd29ybGQ=
echo -n "aGVsbG8gd29ybGQ=" | python3 -m base64 -d
hello world

Es liest aus stdin, wenn Sie ihm keine Datei geben, oder aus der Datei, die Sie benennen, und unter der Haube ist es die Legacy-Datei-zu-Datei-Schnittstelle, also kommt die Ausgabe bei 76 Zeichen umgebrochen mit einem abschließenden Zeilenumbruch auf jeder Zeile. Zum Einsetzen eines Payloads in eine Sitzung mit aufgedrehter Strenge ist die Einzeilen-Version des Dekodierers eine schöne Gewohnheit:

import base64
import sys
print(base64.b64decode(sys.stdin.read(), validate=True))

Neun Wege, sich zu verbrennen

Jeder Base64-Dekodier-Bug in Python ist einer dieser. Halten Sie die Liste irgendwo bereit, wo Sie sie in der Panik finden, denn sie hat mehr Nachmittage gerettet als jedes andere einzelne Dokument, das Sie dieses Jahr lesen werden. Die ersten drei kommen mit Code, denn sie bleiben besser im Gedächtnis, einmal das Trümmerfeld gesehen:

Das fehlende Padding. Der häufigste Crash von allen, normalerweise, weil ein JWT-Teil oder ein API-Wert ohne seine Pads ankam:

import base64
import binascii
segment = "Zm9vYmE"
try:
  base64.urlsafe_b64decode(segment)
except binascii.Error as caught:
  print(caught)
# Incorrect padding
padded = segment + "=" * (-len(segment) % 4)
print(base64.urlsafe_b64decode(padded))
# b'fooba'

Der abgeschnittene String. Wenn der Fehler sagt, die Anzahl der Datenzeichen "cannot be 1 more than a multiple of 4", wurde der Payload unterwegs abgeschnitten, oder ein Copy-Paste hat ein Zeichen am Ende verloren. Keine Menge an Padding repariert einen String, dessen Länge eins modulo vier ist; die Daten sind einfach nicht da, und die ehrliche Antwort ist, den Payload erneut anzufragen.

Der stumme Mist. Der nachsichtige Modus dekodiert, was übrig bleibt, und gewöhnliche englische Wörter stecken voller Base64-Alphabet-Buchstaben, also wird ein verirrtes Wort vor dem Payload zu echten Bytes, geklebt an Ihren Daten:

import base64
print(base64.b64decode("junkZm9vYmFy"))
# b'\x8e\xe9\xe4foobar' - drei Bytes reine Fiktion, dann die Wahrheit

Die anderen sechs brauchen überhaupt keinen Code:

  • Sie haben einen base64url-String mit dem Standard-Dekodierer dekodiert. Die Bindestriche und Unterstriche sind nicht im Standardalphabet, also verschwanden sie still, und der Payload kam verhexter oder gar leer heraus. Verwenden Sie urlsafe_b64decode mit dem Padding-Fix.
  • Sie haben vergessen, dass das Ergebnis Bytes sind. Es an einen String zu kleben, löst einen TypeError aus, und es in eine JSON-Antwort zu schieben, serialisiert die b'...'-Darstellung. Rufen Sie .decode(encoding) an der Grenze auf, bewusst, mit der Kodierung, die Sie wirklich meinen.
  • Sie haben einen Nicht-ASCII-String übergeben. Der Dekodierer akzeptiert Strings, aber nur ASCII-Strings; alles andere ist ein ValueError. Wenn Ihr Payload aus einer Textdatei kam, die mit der falschen Kodierung gelesen wurde, beheben Sie das Lesen, nicht das Dekodieren.
  • Sie haben zweimal dekodiert. Die Daten waren bereits stromaufwärts dekodiert, oder es war Base64 von Base64, und der zweite Durchgang hat Ihr Passwort in sechs Bytes verwandelt, die kein Mensch je wieder lesen wird.
  • Sie haben den strikten Modus auf umgebrochene Daten angewendet. Ein einzelner Zeilenumbruch reicht, um validate=True zum Werfen zu bringen, also gehören MIME-Blöcke und PEM-Bodies zu den nachsichtigen Werkzeugen, nicht zum strikten.
  • Sie haben einem Pad in der Mitte vertraut. Im nachsichtigen Modus wird ein = irgendwo im String still verworfen, also kann ein beschädigter Payload mit einem falsch platzierten Pad zur "richtigen" Antwort dekodiert werden. Nur der strikte Modus bemerkt es, und er bemerkt es durch Ablehnung.

Wenn Ihr Job darin besteht, der Türsteher zu sein, hier ein kleiner Helfer, der die beiden Stimmungen zusammenarbeiten lässt: zuerst streng, dann Padding-Fix, und ein lautes Scheitern, wenn nichts hilft:

import base64
import binascii
def safe_decode(text):
  candidate = text.strip()
  try:
    return base64.b64decode(candidate, validate=True)
  except binascii.Error:
    padded = candidate + "=" * (-len(candidate) % 4)
    return base64.b64decode(padded, validate=True)
print(safe_decode("Zm9vYmE"))
# b'fooba'
print(safe_decode("Zm9vYmFy"))
# b'foobar'

Beachten Sie, dass der Helfer immer noch dem Alphabet vertraut, dem er vertrauen soll. Wenn Ihre Eingabe base64url sein könnte, füttern Sie stattdessen urlsafe_b64decode. Validierung ist ein Vertrag, und der Vertrag sagt, in welchem Dialekt die Daten sind.

Drei Jahrzehnte eines stillen Moduls

Das Modul ist seit einem Vierteljahrhundert in der Standardbibliothek, und die meiste Zeit saß es still. Wenn es sich bewegte, waren die Bewegungen klein, aber echt, und sie erklären ein paar "läuft auf meiner Maschine"-Geschichten, die durch alte Foren schwirren:

  • 1995 - Jack Jansen schrieb base64.py um, um die eigentliche Arbeit an das C-Modul binascii zu delegieren. Der Kommentar ist noch in der Datei, und die Delegation gilt noch heute.
  • 2003, ausgeliefert in Python 2.4 - Barry Warsaw fügte die vollständige RFC-3548-Unterstützung hinzu: die b16, b32 und b64-Familien, plus die standard_*- und urlsafe_*-Varianten, die Sie heute verwenden.
  • Python 3.1 - encodestring und decodestring wurden zugunsten von encodebytes und decodebytes obsolet erklärt, die Namen, die hängen blieben.
  • Python 3.3 - die Dekodier-Funktionen begannen, ASCII-Strings zu akzeptieren, und beendeten die Ära, in der jedes Dekodieren mit einem bytes-Literal begann.
  • Python 3.4 - jedes bytes-ähnliche Objekt (inklusive memoryviews) wird überall akzeptiert, und die Base85-Cousins, a85 und b85, traten zum Modul bei.
  • Python 3.9 - die lange obsolet erklärten encodestring und decodestring wurden endlich entfernt. Alte Tutorials, die sie aufrufen, brauchen eine Ein-Wort-Umbenennung.
  • Python 3.10 - b32hexencode und b32hexdecode kamen mit dem erweiterten Hex-Alphabet, dem, das kodierte Daten lexikografisch sortierbar hält.
  • Python 3.11 - binascii.a2b_base64 bekam strict_mode, worauf validate=True unter der Haube reitet.
  • Python 3.13 - z85encode und z85decode brachten ZeroMQs Z85-Dialekt in die Standardbibliothek, und das uralte uu-Modul wurde unter PEP 594 entfernt, mit einem spitzigen Hinweis, stattdessen base64 zu verwenden.
  • Python 3.14 - b16decode wurde bis zu sechsmal schneller: Seine Validierung läuft jetzt auf bytes.translate statt auf einem regulären Ausdruck, und das Modul importiert re überhaupt nicht mehr. Seine Import-Zeit landete auch auf der Liste der verbesserten Module.

Keines davon ändert, was die Funktionen tun, und das ist der stille Luxus eines Moduls dieses Alters: Code, der 2005 Base64 dekodiert hat, dekodiert es 2026 immer noch, auf derselben Zeile, mit demselben Ergebnis.

Freuden aus den Rändern

Die ernste Arbeit ist erledigt, also hier die kleinen Freuden, die das Modul in seinen Rändern versteckt:

  • Die eigene Dokumentation des Moduls hat dieselbe Demonstration seit über einem Jahrzehnt laufen: b'data to be encoded' geht hinein, b'ZGF0YSB0byBiZSBlbmNvZGVk' kommt heraus. Wenn Sie die base64-Seite irgendeiner Python-Version in den letzten zwanzig Jahren gelesen haben, haben Sie dieses Paar schon einmal getroffen.
  • Das Wort junk ist ein vollkommen gültiger Base64-String. Alle vier Buchstaben sind im Alphabet, deshalb wird ein verirrtes Wort am Anfang eines Payloads zu drei Bytes Fiktion statt zu einem Fehler, und deshalb verdient sich der nachsichtige Modus seinen Spitznamen.
  • urlsafe_b64decode ist zufällig zweisprachig. Es übersetzt zuerst sein Alphabet und dekodiert dann nachsichtig, also liest es auch einen Standardalphabet-String mit + und / darin. Eine Funktion, zwei Dialekte, null Beschwerden.
  • Die Fehlermeldungen sind ein stabiles Mini-Lexikon, das sich seit der C-Implementierung nicht bewegt hat: Incorrect padding, Only base64 data is allowed, Excess padding not allowed, Leading padding not allowed. Lernen Sie sie, und Sie können einen kaputten Payload triagieren, ohne eine einzige Zeile Code auszuführen.
  • Der leere String ist die einzige Eingabe, die überhaupt keine Reaktion hervorruft: b'' hinein, b'' heraus, in beiden Stimmungen. Nichts hinein, nichts heraus, keine Alarmglocke.
  • Die Docstring des Moduls nennt immer noch RFC 3548, die Ausgabe von 2003 der Spezifikation. RFC 4648 ist seit 2006 der aktuelle Standard, und das Modul folgt ihm treu, ohne sich die Mühe zu machen, den Satz zu aktualisieren.
  • Python 2 hatte auf der Dekodier-Seite keine Typmauer: ein gewöhnlicher str hinein, ein gewöhnlicher str heraus. Die Bytes-Überholung von 2007 in der Entwicklung von Python 3 änderte das, und die alten Python-2-Tutorials sind der Ort, auf den die meisten "warum ist mein Dekodieren kaputt"-Threads immer noch zeigen.

Und hier ist die gesamte Philosophie in vier Regeln. Übergeben Sie validate=True für alles, was Sie nicht selbst kodiert haben, und behandeln Sie die Ausnahme als echte Antwort, nicht als Vorschlag. Wissen Sie, welchen Dialekt Sie in der Hand halten, Standard, base64url oder MIME-umwickelt, denn der Dekodierer wird es Ihnen nicht sagen; er wird nur raten, indem er alles wegwirft, was nicht passt. Behandeln Sie das Ergebnis als Bytes, bis Sie bewiesen haben, dass es Text ist, und fragen Sie dann, wem das Charset gehört. Und denken Sie daran, dass die freundlichste Eigenschaft dieser Funktion, die Willigkeit, Dinge zu dekodieren, die nicht ganz Base64 sind, dieselbe Eigenschaft ist, die sie gefährlich macht, also entscheiden Sie bei jedem Aufruf, wie viel Vertrauen die Eingabe verdient hat.

Wenn Sie irgendwann den anderen Weg gehen müssen, frische Bytes in das freundliche Band aus Buchstaben zurückwickeln für ein Token, einen Anhang oder ein Inline-Bild, dann ist die ganze Geschichte von b64encode im verwandten Base64-Kodierungs-Artikel unten auf dieser Seite im Detail abgedeckt. Die beiden Richtungen sind Spiegelbilder, aber jede hat ihr eigenes Set an Überraschungen, und dieses hier kennen Sie jetzt auswendig. Frohes Dekodieren.

Zuletzt aktualisiert: 2026-09-08

Verwandter Artikel: Base64-Kodierung in Python: Ein vollständiger Leitfaden