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 Go: Ein vollständiger Leitfaden

In einer API-Antwort versteckt sich ein langer String, der so tut, als wäre er ein Wert, während er in Wirklichkeit eine Datei, ein Token, ein Bild oder eine Nachricht von einem System ist, das drei Jahre älter ist als Ihres. Irgendwo in Ihren Log-Zeilen, Datenbankreihen und JSON-Payloads tauchen Base64-Strings ständig auf: das Standard-Alphabet mit 64 Zeichen, manchmal mit Plus und Schrägstrich, manchmal mit Bindestrich und Unterstrich, und gelegentlich zwei Gleichheitszeichen, die am Ende wie eine Signatur parken.

Die Startseite dieser Site erklärt das Format selbst schon: 64 druckbare Zeichen, die jeweils 6 Bits tragen, vier Zeichen pro drei Eingabe-Bytes, und Padding, damit der Job zu Ende geht. Dieser Artikel springt deshalb direkt zu der Hälfte der Arbeit, in der die interessanten Entscheidungen sitzen: das Öffnen dieser Strings in Go. Die gute Nachricht ist, dass Go ein wunderbarer Ort dafür ist. Ein Standardbibliotheks-Paket, null Abhängigkeiten, ein Dekodierer, der von Haus aus streng ist, aber Zeilenumbrüche nachsichtig behandelt, und Fehlermeldungen, die auf das exakte Byte zeigen, das schiefgegangen ist.

Was Go mitbringt

Alles, was Sie brauchen, ist bereits in der Standardbibliothek. Das Paket heißt encoding/base64, seine Quelldatei trägt noch den Copyright-Hinweis aus dem Jahr 2009, dem Geburtsjahr der Sprache, und es gibt keine Erweiterung, die Sie aktivieren, kein Modul, das Sie holen, und keine Einstellung, die Sie umlegen müssten. Wenn go version auf Ihrer Maschine irgendetwas ausgibt, besitzen Sie bereits das gesamte Werkzeug.

Zur Zeit des Schreibens ist die neueste Version Go 1.27.1, herausgekommen am 1. September 2026, mit der Go-1.26-Linie (derzeit 1.26.8) als anderer unterstützter Linie. Die base64-API ist auf beiden identisch, und dank des Go-1-Kompatibilitätsversprechens wird ein Programm, das heute Base64 dekodiert, auch in jeder zukünftigen Version exakt das Gleiche tun. Go selbst bekommen Sie von den offiziellen Tarballs auf go.dev/dl (etwa go1.27.1.linux-amd64.tar.gz, entpackt nach /usr/local), vom Paketmanager Ihrer Distribution (sudo apt install golang-go auf Ubuntu-basierten Systemen) oder über den golang.org/dl-Wrapper, wenn Sie mehrere Go-Versionen nebeneinander mögen.

Sobald Go installiert ist, gibt go doc encoding/base64 die komplette API in einer lesbaren Spalte aus, und das ist der schnellste Weg, um Ihr Gedächtnis aufzufrischen. Das einzige Add-on, das dieser Artikel irgendwo verwendet, ist golang.org/x/text für Legacy-Charsets, installiert mit go get golang.org/x/text. Es taucht einmal auf, in seinem eigenen Abschnitt, und der Rest ist reine Standardbibliothek.

Ihr erster Decode

Neunzig Prozent des Dekodier-Alltags in Go sind eine Methode auf dem Encoding-Typ:

func (enc *Encoding) DecodeString(s string) ([]byte, error)

Geben Sie ihm einen Base64-String, und er gibt Ihnen die Bytes zurück, die er darstellt, plus einen Fehler, wenn sich die Eingabe nicht benehmt:

package main

import (
  "encoding/base64"
  "fmt"
)

func main() {
  decoded, err := base64.StdEncoding.DecodeString("TWFu")
  if err != nil {
    fmt.Println("decode failed:", err)
    return
  }
  fmt.Println(string(decoded)) // Man
}

Zwei Dinge an dieser Signatur lohnt sich auswendig zu lernen. Erstens ist das Ergebnis ein []byte, kein String, denn die Bytes, die Sie entpacken, können völlig gültiges Base64 und völlig furchtbarer Text sein: ein PNG-Header, ein komprimiertes Archiv, ein binäres Protokoll. Hüllen Sie es in string(...) erst ein, wenn Sie wissen, dass der Payload Text ist. Zweitens gibt die Methode immer zwei Werte zurück. Ein nil-Fehler bedeutet, der String war sauberes Base64; ein nicht-nil-Fehler bedeutet, die Eingabe war irgendwo kaputt, und der Byte-Slice, den Sie erhalten haben, kann ein Teil-Ergebnis sein, statt leer zu sein. Beide Seiten dieses Verhaltens sehen Sie im Fehler-Abschnitt unten.

Vier Dekodierer, eine Frage: Welches Alphabet?

Go bringt vier fertige Encoding-Werte mit, und die Wahl des richtigen ist die erste echte Entscheidung in jedem Decode. Die Tabelle unten ist der Sitzplan:

Variable Alphabet Padding Wo Sie ihn treffen werden
StdEncoding A-Z a-z 0-9 + / = MIME-E-Mail, Data-URLs, HTTP Basic Auth, PEM-Dateien, allgemeines JSON
URLEncoding A-Z a-z 0-9 - _ = URL-Pfade und -Queries, Dateinamen
RawStdEncoding A-Z a-z 0-9 + / kein Standard-Base64 ohne Padding von kompakten Erzeugern
RawURLEncoding A-Z a-z 0-9 - _ kein JWT-Segmente, kompakte API-Identifikatoren

Der schnellste Weg zu entscheiden ist, auf die Daten selbst zu schauen. Ein String, der + oder / enthält, kann nur ein Standard-Alphabet-String sein, also braucht er einen der zwei Std-Dekodierer. Ein String, der - oder _ enthält, ist die URL-sichere Variante aus RFC 4648, also braucht er einen der zwei URL-Dekodierer. Dann prüfen Sie das Ende: =-Zeichen am Schluss bedeuten die Variante mit Padding, und ihr Fehlen bedeutet die Raw-Variante. So fühlt sich die falsche Wahl an:

decoded, err := base64.StdEncoding.DecodeString("P29_")
// err: illegal base64 data at input byte 3
// der Unterstrich ist nicht im Standard-Alphabet, deshalb
// hält der Dekodierer bei dem letzten Zeichen, das er nicht erkennt, an
decoded, err = base64.URLEncoding.DecodeString("P29_")
// decoded sind die drei Bytes 0x3f 0x6f 0x7f, err ist nil

Wenn Sie Daten dekodieren, deren Erzeuger ein eigenes 64-Zeichen-Alphabet definiert hat, baut base64.NewEncoding("...64 chars...") Ihnen einen Dekodierer dafür. Das Alphabet muss exakt 64 eindeutige Byte-Werte sein und darf keinen Zeilenumbruch enthalten - andernfalls panickt die Funktion - und während die Dokumentation verlangt, dass das Alphabet das Padding-Zeichen ausschließt, erzwingt die Funktion das nicht - ein Alphabet mit '=' wird ohne Panik akzeptiert. Im Tagesgeschäft brauchen Sie es selten, aber es ist da, und es ist der einzige Weg, ein privates Schema zu dekodieren.

Das Toleranz-Problem: Welche Eingabe akzeptiert Go?

Jeder Base64-Dekodierer muss eine unangenehme Entscheidung treffen: Wie viel Müll ist er bereit zu schlucken? Go antwortet mit einer sorgfältig gezogenen Linie. Auf der nachsichtigen Seite überspringt der Dekodierer Wagenrücksetzer und Zeilenumbrüche an jeder Stelle der Eingabe, so dass ein String, der von einem E-Mail-Client oder einem PEM-Werkzeug über viele Zeilen umgebrochen wurde, ohne jede Vorverarbeitung dekodiert wird:

decoded, err := base64.StdEncoding.DecodeString("T\nW\nF\r\nu")
// decoded ist "Man", err ist nil
// jedes \r und \n im String wurde einfach ignoriert

Auf der strengen Seite ist alles andere tabu. Ein Leerzeichen, ein Tab, ein Nullbreiten-Zeichen, das aus einer PDF kopiert wurde, ein irrtümlich eingeschlichener Doppelpunkt aus einem Header: Im Moment, in dem der Dekodierer auf ein Zeichen trifft, das weder im Alphabet ist noch ein Zeilenumbruch, hält er an und meldet den Offset. Und behält dabei alles, was er bereits dekodiert hat:

decoded, err := base64.StdEncoding.DecodeString("TWFu junk")
// decoded ist "Man" (der Teil vor dem Leerzeichen),
// err ist: illegal base64 data at input byte 4

Diese Kombination überrascht die Leute: Ein fehlgeschlagenes Dekodieren kann Ihnen trotzdem ein brauchbares Halbergebnis aushändigen. Ob das eine Funktion oder eine Gefahr ist, liegt an Ihnen; der Punkt ist, dass err == nil die einzige Bedingung ist, unter der die Daten vollständig sind.

Padding hat seine eigenen Regeln, und sie unterscheiden sich zwischen den Varianten mit Padding und den Raw-Varianten. Die Dekodierer mit Padding arbeiten in Gruppen: Eine Gruppe besteht entweder aus vier echten Zeichen oder aus zwei echten Zeichen, gefolgt von ==. Ein einzelnes Zeichen ist nie eine vollständige Gruppe, deshalb scheitert "T", und "TWF" scheitert auch, denn drei Zeichen brauchen ein Padding-Zeichen, das fehlt. Die Raw-Dekodierer streichen die Padding-Pflicht, aber sie können trotzdem keine Länge akzeptieren, bei der einer Gruppe drei von ihren vier Zeichen fehlen würden, deshalb scheitert "T" dort ebenfalls, während "TW" fröhlich zu einem einzelnen Byte dekodiert.

base64.StdEncoding.DecodeString("T")        // Fehler bei Eingabe-Byte 0
base64.StdEncoding.DecodeString("TWF")      // Fehler bei Eingabe-Byte 0
base64.RawStdEncoding.DecodeString("TW")    // 1 Byte, kein Fehler
base64.StdEncoding.DecodeString("TWFu====") // "Man" plus Fehler bei Byte 4

Es gibt noch einen weiteren Stimmungswechsel: Strict(), in Go 1.8 hinzugefügt. Im strikten Modus erzwingt der Dekodierer die kanonische Form aus Abschnitt 3.5 von RFC 4648: Die ungenutzten End-Bits der letzten Gruppe müssen null sein. Der normale Modus kümmert sich nicht darum, weil diese Bits schlicht nie verwendet werden, deshalb dekodiert "Qm==" ohne Beschwerde zu dem Byte B. Der strikte Modus verweigert es stattdessen:

decoded, err := base64.StdEncoding.DecodeString("Qm==")
// decoded ist "B", err ist nil (die End-Bits wurden verworfen)
decoded, err = base64.StdEncoding.Strict().DecodeString("Qm==")
// err ist: illegal base64 data at input byte 2

Beachten Sie, dass auch im strikten Modus Zeilenumbrüche weiterhin übersprungen werden, wie die Dokumentation hervorhebt. Verwenden Sie Strict(), wenn Sie ein Protokoll sprechen, das auf kanonische Kodierung achtet, oder wenn Sie nachlässige Erzeuger ablehnen wollen, statt ihre Bits stillschweigend aufzusaugen.

Fehler, die Ihnen sagen, wo es klemmt

Jeder Fehler in diesem Paket kommt als konkreter, untersuchbarer Wert an. Wenn die Eingabe etwas enthält, das das Alphabet nicht kennt, oder das Padding falsch ist, gibt der Dekodierer einen base64.CorruptInputError zurück, und seine Nachricht enthält den Byte-Offset des Problems:

type CorruptInputError int64

func (e CorruptInputError) Error() string {
  return "illegal base64 data at input byte " + strconv.FormatInt(int64(e), 10)
}

Dieser Offset ist der Unterschied zwischen "etwas ist schiefgelaufen" und "das 4.102. Zeichen dieses 900-Kilobyte-Strings ist ein Tab, das von der Zwischenablage hereingekommen ist". Fangen Sie es mit dem üblichen Go-Idiom auf:

package main

import (
  "encoding/base64"
  "errors"
  "fmt"
)

func main() {
  _, err := base64.StdEncoding.DecodeString("TWF$")
  var corrupt base64.CorruptInputError
  if errors.As(err, &corrupt) {
    fmt.Printf("bad byte at offset %d: %v\n", int(corrupt), err)
    // bad byte at offset 3: illegal base64 data at input byte 3
    return
  }
  fmt.Println("not a corrupt-input error:", err)
}

Hier ist die Symptom-Tabelle für die Eingaben, die die Leute am meisten verwirren:

Eingabe (StdEncoding) Ergebnis Warum
TWF$ Fehler bei Byte 3 $ ist nicht im Alphabet
T Fehler bei Byte 0 ein einzelnes Zeichen ist nie eine vollständige Gruppe
TWF Fehler bei Byte 0 drei Zeichen brauchen ein fehlendes =
TWFu junk Man plus Fehler bei Byte 4 Leerzeichen ist kein Zeilenumbruch, daher hält das Dekodieren dort an
TWFu\t Man plus Fehler bei Byte 4 Tabs werden nicht übersprungen, nur \r und \n
T\nW\nF\nu Man, kein Fehler Zeilenumbrüche werden überall ignoriert
==== Fehler bei Byte 0 Padding am Anfang einer Gruppe ist ungültig
(leerer String) leeres Ergebnis, kein Fehler null Bytes Base64 dekodieren zu null Bytes

Ein praktischer Tipp: Wenn ein Decode in der Produktion fehlschlägt, loggen Sie den Offset und ein kurzes Fenster darum. Zu neunzig Prozent ist das "kaputte" Byte ein Leerzeichen, das der Transport, die Zwischenablage oder ein PDF-Viewer in den String geschmuggelt hat, und die Lösung ist ein Trimmen oder Entfernen, kein Neuentwurf.

Dateien öffnen

Base64-Dateien sind einfach Textdateien, die Base64 enthalten, also gelten die üblichen Dateiwerkzeuge von Go. Für eine Datei, die bequem in den Speicher passt, lesen Sie sie komplett und dekodieren Sie den String:

package main

import (
  "encoding/base64"
  "fmt"
  "io"
  "os"
)

func main() {
  f, err := os.Open("payload.b64")
  if err != nil {
    fmt.Println("open failed:", err)
    return
  }
  defer f.Close()

  raw, err := io.ReadAll(f)
  if err != nil {
    fmt.Println("read failed:", err)
    return
  }
  decoded, err := base64.StdEncoding.DecodeString(string(raw))
  if err != nil {
    fmt.Println("decode failed:", err)
    return
  }
  fmt.Println("decoded", len(decoded), "bytes")
}

Für große Dateien ist das bessere Muster das Streaming, und es benutzt die andere Hälfte der Paket-API: NewDecoder hüllt jeden io.Reader in einen base64-dekodierenden Reader, so dass Sie Datei auf Datei pitchen können, ohne jemals den ganzen Payload im Speicher zu halten:

in, err := os.Open("payload.b64")
if err != nil {
  panic(err)
}
defer in.Close()

dec := base64.NewDecoder(base64.StdEncoding, in)
out, err := os.Create("payload.bin")
if err != nil {
  panic(err)
}
defer out.Close()

written, err := io.Copy(out, dec)
if err != nil {
  panic(err)
}
fmt.Println("wrote", written, "bytes")

Es gibt eine Mittelvariante, wenn Sie die zusätzliche Allokation vermeiden wollen, die DecodeString vornimmt: Decode schreibt in einen Zielpuffer, den Sie kontrollieren. Bestimmen Sie seine Größe mit DecodedLen, das die maximale Anzahl an Ausgabe-Bytes für eine gegebene Eingabelänge zurückgibt:

raw, err := os.ReadFile("payload.b64")
if err != nil {
  panic(err)
}
buf := make([]byte, base64.StdEncoding.DecodedLen(len(raw)))
n, err := base64.StdEncoding.Decode(buf, raw)
if err != nil {
  panic(err)
}
data := buf[:n] // die tatsächlich dekodierte Größe
fmt.Println(len(data), "bytes")

Seien Sie bei diesem letzten aber vorsichtig: Decode vertraut darauf, dass Sie den Puffer richtig groß machen. Wenn er zu klein ist, gibt die Methode keinen Fehler zurück; sie panickt mit index out of range. DecodedLen ist die Zahl, die Sie verwenden, nicht len(raw).

Eine Base64-Kommandozeile für Go

Unix-Systeme liefern ein base64-Utility mit coreutils aus, aber Go liefert kein äquivalentes Binary. Die idiomatische Antwort in der Go-Welt ist kein Paket, das Sie installieren, sondern ein Programm, das Sie besitzen: ein kleines Kommandozeilen-Tool, aufgebaut um encoding/base64, das flag-Paket und die Standardeingabe. Hier ist ein komplettes, etwa vierzig Zeilen lang, das alles dekodiert, was hereingeleitet wird, und die rohen Bytes ausgibt:

package main

import (
  "encoding/base64"
  "flag"
  "fmt"
  "io"
  "os"
)

func main() {
  urlSafe := flag.Bool("url", false, "use the URL-safe alphabet")
  flag.Parse()

  enc := base64.StdEncoding
  if *urlSafe {
    enc = base64.URLEncoding
  }

  raw, err := io.ReadAll(os.Stdin)
  if err != nil {
    fmt.Fprintln(os.Stderr, "read failed:", err)
    os.Exit(1)
  }
  decoded, err := enc.DecodeString(string(raw))
  if err != nil {
    fmt.Fprintln(os.Stderr, "decode failed:", err)
    os.Exit(1)
  }
  os.Stdout.Write(decoded)
}

Bauen Sie es einmal mit go build -o b64 ., und es wird ein plattformübergreifender Dekodierer, den Sie in eine Makefile, eine CI-Pipeline oder eine Shell-Funktion stecken können: printf 'TWFu' | ./b64 gibt Man aus, und ./b64 -url < token.b64 > token.bin entpackt einen URL-sicheren Token in eine Datei. Zwei Eigenschaften des Designs sind es wert, angemerkt zu werden. Weil es die gesamte Standardeingabe liest, bevor es dekodiert, dekodiert umgebrochene Eingabe mit Zeilenumbrüchen ohne Problem, dank der Zeilenumbruch-Toleranz des Dekodierers. Und weil es bei schlechter Eingabe mit Status 1 endet und die Beschwerde nach stderr schreibt, benimmt es sich wie ein Werkzeug in einer Pipeline und nicht wie ein Skript, das sich entschuldigt. Das ist die ganze Kunst einer Go-CLI: ein Paket, eine Flagge, Standard-Eingabe, Standard-Ausgabe und ein Exit-Code.

URL-sicheres Dekodieren

Die URL-sichere Variante existiert, weil das Standard-Alphabet mit der Grammatik von URLs kollidiert: + wird in Query-Strings oft als Leerzeichen gelesen, und / beginnt ein neues Pfadsegment, also muss ein in eine URL eingebetteter Standard-Base64-String zeichenweise percent-escaped werden, was langsam zu parsen und hässlich zu lesen ist. Das alternative Alphabet aus RFC 4648 tauscht + und / gegen - und _ aus, beides ohne Escaping in URL-Pfaden, Queries und Dateinamen legal.

In Go ist der Wechsel einfach eine andere Dekodierer-Variable. Wenn Ihre Daten URL-sicher sind und Padding haben, verwenden Sie URLEncoding; wenn sie URL-sicher und ungepuffert sind, verwenden Sie RawURLEncoding. Der klassische Fall ist ein Identifikator, der in einer URL oder einem Dateinamen lebt:

decoded, err := base64.RawURLEncoding.DecodeString("-w9n")
// decoded sind die drei Bytes 0xfb 0x0f 0x67
// der Bindestrich und der Unterstrich sind Teil des URL-sicheren Alphabets,
// deshalb schafft RawURLEncoding sie, wo StdEncoding scheitern würde

Wo Sie es in echtem Go-Code antreffen: JWT-Segmente (nächster Abschnitt), intransparente Identifikatoren, die Systeme erzeugen und in URLs speichern, Dateinamen, die keinen Webserver oder Cloud-Objekt-Store kaputtmachen dürfen, und jede API, die in ihrer Dokumentation "base64url" verspricht. Eine Warnung: URL-sicherheit ist ein Vertrag zwischen Erzeuger und Konsument, keine Eigenschaft der Daten. Wenn der String ein + oder ein / enthält, dann ist er nicht URL-sicher, Punkt, und kein Maß an Versuchen mit dem URL-Dekodierer wird helfen. Sehen Sie sich zuerst die Zeichen an, dann wählen Sie den Dekodierer.

In JWTs reinschauen

Ein JSON Web Token besteht aus drei base64url-Segmenten, getrennt durch Punkte: ein Header, ein Payload mit Claims und eine Signatur, und kein Segment hat Padding. Das macht ein JWT zu einem der häufigsten Dinge, die Sie in Go dekodieren werden, und der Header und der Payload sind ohne jeden Schlüssel lesbar, was sich sowohl fürs Debugging als auch für Sicherheits-Reviews zu merken lohnt:

package main

import (
  "encoding/base64"
  "fmt"
  "log"
  "strings"
)

func main() {
  token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiR28gRGV2ZWxvcGVyIiwic3ViIjoiMTIzNDU2Nzg5MCJ9.NwJQAKfJpMJQuK0gEECtXtO8cIoFnDp0ovXyl7dY1BQ"
  parts := strings.Split(token, ".")
  if len(parts) != 3 {
    log.Fatal("not a JWT: expected three dot-separated parts")
  }
  for i, name := range []string{"header", "payload"} {
    plain, err := base64.RawURLEncoding.DecodeString(parts[i])
    if err != nil {
      log.Fatalf("bad %s: %v", name, err)
    }
    fmt.Printf("%s: %s\n", name, plain)
  }
  // header:  {"alg":"HS256","typ":"JWT"}
  // payload: {"name":"Go Developer","sub":"1234567890"}
}

Achten Sie auf die Dekodierer-Wahl: RawURLEncoding, nicht StdEncoding. JWT-Segmente verwenden das URL-sichere Alphabet und tragen kein Padding, und ein Segment, dessen Länge eins oder zwei unter einem Vielfachen von vier liegt, scheitert bei einem Dekodierer, der Padding erwartet, ganz am Ende, was ein verwirrend zu jagender Fehler ist. Das Signatur-Segment können Sie ohne den Schlüssel nicht lesen, und Sie sollten nichts allein auf Basis des Payloads vertrauen, denn nichts hält einen Client davon ab, die ersten beiden Segmente zu fälschen. Wenn Sie Verifikation brauchen, verwenden Sie eine gepflegte Bibliothek. Die de-facto-Bibliothek ist github.com/golang-jwt/jwt/v5 (installieren Sie sie mit go get github.com/golang-jwt/jwt/v5):

package main

import (
  "fmt"
  "log"

  "github.com/golang-jwt/jwt/v5"
)

func main() {
  secret := []byte("hmac-secret")
  token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiR28gRGV2ZWxvcGVyIiwic3ViIjoiMTIzNDU2Nzg5MCJ9.NwJQAKfJpMJQuK0gEECtXtO8cIoFnDp0ovXyl7dY1BQ"

  parsed, err := jwt.Parse(token, func(t *jwt.Token) (any, error) {
    if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
      return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
    }
    return secret, nil
  })
  if err != nil {
    log.Fatal("token rejected:", err)
  }
  claims, _ := parsed.Claims.(jwt.MapClaims)
  fmt.Println("subject:", claims["sub"])
}

Zwei Details aus der Bibliothek sollten Sie kennen. Erstens werden das base64url-Kodieren und -Dekodieren der drei Segmente im Inneren abgewickelt, also berühren Sie encoding/base64 nie direkt, wenn Sie signieren oder verifizieren. Zweitens verweigert die v5-Bibliothek Tokens mit alg=none, außer Sie übergeben explizit ihre UnsafeAllowNoneSignatureType-Konstante, was Sie vor dem klassischen "ungesignetes Token akzeptiert"-Fehler schützt.

Data-URLs

Eine Data-URL ist eine URL, deren Payload die Daten selbst sind. Die Syntax, aus RFC 2397, lautet data:[mediatype][;base64],data: ein optionaler Medientyp, eine optionale ;base64-Flagge, ein Komma und dann der Inhalt. Wenn die ;base64-Flagge vorhanden ist, ist der Inhalt Standard-Base64, und deshalb teilen sich Data-URLs und dieser Artikel einen Abschnitt. Browser verwenden sie, um Bilder und Schriften direkt in HTML und CSS einzubetten, damit die Seite eine Anfrage weniger braucht:

<img src="data:image/png;base64,iVBORw0KGgo=" alt="pixel">

Die Go-Standardbibliothek hat keinen Data-URL-Helfer, aber das Format ist einfach genug, um es mit strings von Hand zu parsen, und das tun die meisten Go-Programme:

package main

import (
  "encoding/base64"
  "fmt"
  "strings"
)

func main() {
  url := "data:image/png;base64,iVBORw0KGgo="
  if !strings.HasPrefix(url, "data:") {
    fmt.Println("not a data URL")
    return
  }
  rest := url[len("data:"):]
  comma := strings.Index(rest, ",")
  if comma == -1 {
    fmt.Println("missing comma")
    return
  }
  meta := rest[:comma]      // image/png;base64
  encoded := rest[comma+1:] // iVBORw0KGgo=
  if !strings.HasSuffix(meta, ";base64") {
    fmt.Println("this variant is percent-encoded, not base64")
    return
  }
  mediaType := strings.TrimSuffix(meta, ";base64")
  decoded, err := base64.StdEncoding.DecodeString(encoded)
  if err != nil {
    fmt.Println("decode failed:", err)
    return
  }
  fmt.Println(mediaType, "carries", len(decoded), "bytes")
}

Drei Stolperfallen, die im Kopf bleiben sollten. Erstens ist die ;base64-Flagge optional, und ohne sie ist der Payload percent-kodiertes ASCII statt Base64, also prüfen Sie den Suffix, bevor Sie einen Dekodierer rufen. Zweitens ist der Standard, wenn der Medientyp weggelassen wird, text/plain;charset=US-ASCII, was für Bilder selten wichtig ist, aber Leute, die anderen Inhalt parsen, überrascht. Drittens sind Data-URLs ein Kniff für kleine Payloads: Der RFC selbst sagt, das Schema sei nur für kurze Werte nützlich, und die 33-prozentige Größenerweiterung von Base64 macht aus einem 500-Kilobyte-Logo einen 666-Kilobyte-String, der in Ihrem HTML klebt, nicht cachebar und nicht teilbar. Verwenden Sie sie für Icons und Thumbnails, nicht für Videos.

HTTP- und API-Arbeit

Der mit Abstand häufigste Decode in Go-Webservices ist das JSON-Body-Feld: Ein Upload-Formular, eine API-Antwort oder ein Webhook reicht Ihnen einen String, der in Wirklichkeit eine Datei ist. Unmarshalen Sie in ein Struct, und dekodieren Sie dann das Feld:

package main

import (
  "encoding/base64"
  "encoding/json"
  "fmt"
)

type payload struct {
  Avatar string `json:"avatar"`
}

func main() {
  body := []byte(`{"avatar": "iVBORw0KGgo="}`)
  var p payload
  if err := json.Unmarshal(body, &p); err != nil {
    fmt.Println("bad JSON:", err)
    return
  }
  img, err := base64.StdEncoding.DecodeString(p.Avatar)
  if err != nil {
    fmt.Println("bad avatar:", err)
    return
  }
  fmt.Println("avatar is", len(img), "bytes")
}

Wenn Ihre API sowohl Standard- als auch URL-sichere Strings akzeptiert, ist das pragmatische Muster, einen Dekodierer zu versuchen, und wenn er mit einem CorruptInputError in der Nähe des Endes fehlschlägt, den anderen zu versuchen, bevor Sie aufgeben. Führen Sie diesen Tanz nicht öfter als einmal aus, und fallen Sie niemals auf "Gleichheitszeichen entfernen und hoffen" als allgemeine Strategie zurück.

Für HTTP Basic Auth dekodieren Sie gar nichts, denn Go macht es für Sie. Request.BasicAuth, verfügbar seit Go 1.4, trennt den Authorization-Header für Sie auf und gibt Benutzername und Passwort zurück, nachdem der Standard-Base64-Dekodierer das user:pass-Paar verarbeitet hat, das RFC 2617 definiert:

package main

import (
  "fmt"
  "net/http"
)

func main() {
  mux := http.NewServeMux()
  mux.HandleFunc("/api/", func(w http.ResponseWriter, r *http.Request) {
    user, pass, ok := r.BasicAuth()
    if !ok || user != "alice" || pass != "s3cret" {
      w.Header().Set("WWW-Authenticate", `Basic realm="api"`)
      w.WriteHeader(http.StatusUnauthorized)
      return
    }
    fmt.Fprintln(w, "hello", user)
  })
  http.ListenAndServe(":8080", mux)
}

Denken Sie daran, dass Basic Auth Authentifizierung ist, nicht Schutz: Der Header ist Base64, nicht verschlüsselt, also darf er nur über HTTPS reisen. Wenn Sie der Client sind, ist der spiegelnde Aufruf req.SetBasicAuth(user, pass), der denselben Header für Sie mit dem Standard-Encoder baut.

Eine defensive Angewohnheit für API-Handler: Beschränken Sie den Body, bevor Sie ihn dekodieren, mit http.MaxBytesReader oder einer äquivalenten Längenprüfung. Ein Base64-String dekodiert auf ungefähr drei Viertel seiner eigenen Länge, also hält ein Body-Limit von N Bytes das dekodierte Ergebnis unter N Bytes, und der Speicher bleibt beschränkt, egal was ein böswilliger Client postet. Einen unbegrenzten Body zu dekodieren ist ein klassischer Vektor für Speichererschöpfung, denn der Angreifer kontrolliert, wie viele Megabytes Text er in binäre Daten verwandeln kann.

Legacy-Charsets

Das Dekodieren von Base64 gibt Ihnen Bytes, und in modernen Systemen sind diese Bytes fast immer UTF-8, in dem Fall ist string(decoded) die ganze Geschichte. Aber Base64 ist ein altes Format, und viel davon wurde von Systemen erzeugt, die Windows-1252, ISO-8859-1, Shift JIS oder ein anderes Legacy-Charset mit einem oder zwei Bytes verwendeten. Wenn der Erzeuger das getan hat, sind die Bytes, die Sie dekodieren, kein gültiges UTF-8, und Go wird nicht so tun, als wäre es gültiges UTF-8: Es zeigt Ihnen Ersetzungszeichen an, wo immer eine Sequenz kaputt ist.

Go antwortet mit dem Modul golang.org/x/text, das legacy-kodierte Bytes in UTF-8 verwandelt (und zurück) für die üblichen Charsets. Der Umwandlungs-Slot sitzt direkt nach dem Decode, und es kostet einen Funktionsaufruf:

package main

import (
  "encoding/base64"
  "fmt"

  "golang.org/x/text/encoding/charmap"
  "golang.org/x/text/transform"
)

func main() {
  // "Café" von einem Legacy-Tool in Windows-1252 gespeichert,
  // dann base64-kodiert für den Transport
  encoded := "Q2Fm6Q=="
  raw, err := base64.StdEncoding.DecodeString(encoded)
  if err != nil {
    fmt.Println("decode failed:", err)
    return
  }
  utf8, _, err := transform.Bytes(charmap.Windows1252.NewDecoder(), raw)
  if err != nil {
    fmt.Println("charset conversion failed:", err)
    return
  }
  fmt.Println(string(utf8)) // Café
}

Das Modul hat ein Unterpaket pro Charset-Familie: charmap für die Windows- und ISO-Einbyte-Tabellen, japanese für Shift JIS und EUC-JP, korean für EUC-KR, simplifiedchinese für GB18030 und traditionalchinese für Big5. Die Faustregel lautet, nur umzuwandeln, wenn Sie das Charset des Erzeugers tatsächlich kennen, denn UTF-8-Bytes ein zweites Mal umzuwandeln scheitert nicht mit lautem Knall; es verbastelt nur den Text. Im Zweifel behandeln Sie den Payload als Bytes und überlassen dem nachgelagerten Konsumenten die Entscheidung.

Streaming und Dekodieren in Chunks

Sie haben NewDecoder im Datei-Abschnitt gesehen; hier ist, warum es einen eigenen Abschnitt wert ist. Es ist ein echter Streaming-Adapter: Es zieht nur so viel aus dem zugrunde liegenden Reader, wie es braucht, dekodiert am Ort und gibt einen CorruptInputError zurück, sobald der Stream kaputt wird. Der ganze Stream kann Terabytes groß sein; der Speicher, den Sie halten, ist Ihr Puffer und die Ausgabe, die Sie schreiben. Die zwei üblichen Verbraucher-Muster sind io.ReadAll für kleine Streams und io.Copy für alles andere:

small, err := io.ReadAll(base64.NewDecoder(base64.StdEncoding, r))
// passt für einen Konfig-Blob oder einen kleinen Anhang

w, err := io.Copy(out, base64.NewDecoder(base64.StdEncoding, r))
// passt für ein Video, ein Tar-Archiv oder einen Restore-Job

Seit Go 1.22 hat das Paket außerdem AppendDecode, das in einen Puffer dekodiert, den Sie wiederverwenden, statt pro Aufruf einen frischen Slice zu allozieren. Es ist das Werkzeug für heiße Pfade, die in einer Schleife viele Chunks dekodieren, wie ein Zeilenverarbeiter oder ein Protokoll-Dekodierer:

var buf []byte
for _, chunk := range chunks {
  buf, err = base64.StdEncoding.AppendDecode(buf, chunk)
  if err != nil {
    return err
  }
  process(buf)
}

Die Methode hängt den dekodierten Chunk an das an, was buf bereits hält, und gibt den erweiterten Slice zurück, wobei sie das dahinterliegende Array bei Bedarf vergrößert. Im Dauerbetrieb, wo der Puffer bereits auf die richtige Größe gewachsen ist, führt sie pro Chunk null Allokationen durch, was in einem Benchmark klar sichtbar wird. Wenn Ihre Arbeitslast "einmal dekodieren, selten" ist, ist DecodeString die einfachere Wahl; wenn sie "tausendemale in einer engen Schleife dekodieren" ist, ist AppendDecode das, wonach Sie greifen.

Sicher bleiben

Ein paar Sicherheitsnotizen, die genau auf die Art und Weise eingehen, wie Go-Programme dieses Paket tatsächlich verwenden. Erstens: Base64 ist Kodierung, nicht Verschlüsselung. Ein Base64-String ist für jeden lesbar, der die Entwickler-Tools eines Webbrowsers hat, also ist "wir base64-ieren das Passwort, bevor wir es senden" keine Sicherheitsmaßnahme; es ist eine Transport-Bequemlichkeit. Die Vertraulichkeit muss von TLS kommen, nicht vom Alphabet.

Zweitens: Beschränken Sie Ihre Eingaben. Die dekodierte Größe eines Base64-Strings ist höchstens DecodedLen seiner Länge, also prüfen Sie diese Zahl gegen ein Limit, bevor Sie allozieren, und wickeln Sie Request-Bodies in eine Größen-Obergrenze, bevor irgendetwas einen Dekodierer berührt. Beide Prüfungen sind jeweils eine Zeile, und zusammen machen sie aus einem unbegrenzten Decode einen begrenzten.

Drittens: Entscheiden Sie Ihre Haltung gegenüber nachlässiger Eingabe. Der normale Modus wirft die ungenutzten End-Bits der letzten Gruppe stillschweigend weg, was bedeutet, dass zwei verschiedene Strings zu denselben Bytes dekodiert werden können. Für die meisten Daten spielt das keine Rolle. Für alles, was Teil eines Protokolls, einer signierten Nachricht oder eines Werts ist, der verglichen oder gespeichert wird, ist Strict() die konservative Wahl, denn es macht die kanonische Form zur einzigen akzeptierten Form.

Viertens: Seien Sie vorsichtig, wohin dekodierte Bytes gehen. Wenn ein dekodierter Wert zu einem Dateinamen, einem Pfad, einem SQL-Fragment oder einem Kommandoargument wird, hat die Base64-Ebene Sie vor nichts geschützt: Die Bytes sind jetzt die unvertraute Eingabe Ihres Programms, und die üblichen Sanifizierungs-Regeln gelten genauso wie für jede andere Benutzereingabe.

Wie schnell ist der Dekodierer?

Base64 in Go ist schnell, und es bleibt auch bei großen Daten schnell, denn die Implementierung ist eine einfache Tabellen-Suchschleife ohne Reflexion und ohne Allokation pro Zeichen. Auf einer aktuellen Desktop-CPU mit Go 1.26 dekodiert ein 500-Byte-String in ungefähr einem Viertel einer Mikrosekunde mit einer Allokation, was in der Größenordnung von zwei Gigabytes pro Sekunde liegt. Ein Megabyte Base64 dekodiert in gut unter einer Millisekunde, ein Gigabyte in gut unter einer Sekunde. Die Zahlen bewegen sich mit der Hardware, aber die Form tut es nicht: Base64-Dekodieren ist fast nie der Engpass; das Netzwerk oder die Platte drumherum ist es in der Regel.

Wenn Sie in einer heißen Schleife sind, ist das Allokationsprofil das, worauf Sie achten sollten. DecodeString alloziert den Ergebnis-Slice bei jedem Aufruf. Decode mit vorabdimensioniertem Ziel und AppendDecode mit wiederverwendetem Puffer vermeiden diese Allokation im Dauerbetrieb komplett. Für ein Dekodieren, das ein paar Mal pro Request passiert, spielt das alles keine Rolle; für ein Dekodieren, das ein paar Millionen Mal pro Sekunde passiert, ist es der Unterschied zwischen einem flachen Speicherprofil und einem ratternden Garbage Collector.

Eine kurze Geschichte des Pakets

Das base64-Paket ist einer der ältesten Teile der Go-Standardbibliothek. Der Copyright-Hinweis der Quelldatei lautet 2009, das Jahr, in dem die Sprache erstellt wurde, und das Paket ist seit der allerersten stabilen Version, Go 1.0 im März 2012, Teil der Standardbibliothek. Das heißt, das DecodeString, das Sie heute aufrufen, ist dieselbe API, mit demselben Verhalten, die Go-Programme seit über einem Jahrzehnt aufrufen.

Das Wachstum seitdem war bescheiden und nützlich. Go 1.5 im August 2015 fügte die RawStdEncoding- und RawURLEncoding-Werte für Ausgabe ohne Padding hinzu, die die Tür zu JWT-artigen kompakten Strings öffneten. Go 1.8 im Februar 2017 fügte Strict() hinzu und gab Protokollen so die Möglichkeit, kanonische Eingabe zu verlangen. Go 1.22 im Februar 2024 fügte AppendDecode und AppendEncode zur ganzen Familie der Basis-Kodierungen hinzu und machte WithPadding strenger, indem es Unsinn-Argumente ablehnt. Und Stand September 2026, mit Go 1.27.1 als neuester Version und Go 1.26 als der anderen unterstützten Linie, ist die API exakt die, die in diesem Artikel beschrieben wird: vier fertige Kodierungen, ein Stream-Dekodierer, ein strikter Modus und eine Append-Familie für Performance.

Die tiefere Tatsache ist das Kompatibilitätsversprechen. Die Garantie von Go 1 bedeutet, dass das Paket für immer dieselben Eingaben akzeptieren und ablehnen wird, also wird ein Dekodierer, den Sie dieses Jahr für ein in 2015 erzeugtes Datenformat schreiben, weiterhin funktionieren. Für ein Format, das so alt und so langweilig ist, ist das die beste Nachricht, die es gibt.

Dinge, die Sie überraschen werden

Nach einer Weile in Go lassen Sie sich von Base64 nicht mehr überraschen, aber die ersten paar Male treffen einige dieser Fakten hart - deshalb sind sie hier:

  • Der Dekodierer überspringt \r und \n überall in der Eingabe, aber kein Leerzeichen, kein Tab und kein Nullbreiten-Leerzeichen. Die Nachsicht ist absichtlich; sie existiert, damit MIME-umgebundene Eingabe funktioniert, und sie hört genau dort auf, wo die Spezifikation aufhört.
  • Ein fehlgeschlagenes Dekodieren kann trotzdem echte Daten zurückgeben. Das Teil-Ergebnis ist alles, was vor dem schlechten Byte dekodiert wurde, und der Fehler kommt zusammen mit ihm, nicht an seiner Stelle.
  • CorruptInputError ist buchstäblich nur ein int64 mit einer angehängten Methode. Der "Fehler" ist der Offset, und die Nachricht wird auf Abruf gebaut.
  • Sowohl Decode als auch Encode vertrauen darauf, dass Sie ihre Zielpuffer richtig groß machen. Geben Sie ihnen einen Puffer, der zu klein ist, und Sie erhalten keinen Fehler; Sie erhalten einen Panic.
  • Ein einzelnes Zeichen ist keine gültige Eingabe für die vier eingebauten Kodierungen. Ein Base64-Zeichen trägt sechs Bits, und ein Byte braucht acht, also gibt es in einem Zeichen keine vollständige Gruppe, mit Padding oder ohne.
  • Stand August 2026 listen mehr als 244.000 öffentliche Pakete auf pkg.go.dev encoding/base64 unter ihren Imports. Es ist unauffällig eines der Pakete, auf die das Ökosystem am meisten angewiesen ist.

Wo Decodes schiefgehen

Das sind die Dekodier-Fehler, die immer wieder in Go-Codebasen auftauchen, in etwa der Reihenfolge, in der sie in Support-Threads erscheinen:

  • StdEncoding für URL-sichere Daten wählen (oder umgekehrt). Das Symptom ist ein Fehler beim ersten -, _, + oder /, und die Lösung ist, den String anzuschauen, bevor Sie den Dekodierer wählen.
  • Einen String aus einem Terminal, einer E-Mail oder einer PDF einfügen, was Leerzeichen, Tabs oder Zeilenende-Artefakte einschleust. Go überspringt echte Zeilenumbrüche, aber ein Leerzeichen mitten im String ist ein kaputtes Byte, und der Offset im Fehler zeigt genau darauf.
  • Daran zu vergessen, dass das Ergebnis ein []byte ist. Ausgegeben im Rohezustand gibt es eine Liste von Zahlen, und um es an eine Funktion zu füttern, die einen String erwartet, brauchen Sie eine string(...)-Umwandlung.
  • Den Fehler zu prüfen und dann trotzdem die Teildaten zu verwenden. Das halb dekodierte Präfix ist echt, aber es ist nicht der Payload, und Code, der es so behandelt, scheitert in der Produktion mit Daten, die exakt die halbe richtige Länge haben.
  • Den Puffer für Decode mit len(src) statt mit DecodedLen(len(src)) dimensionieren. Die erste Größe ist in der falschen Richtung falsch, und der Panic, den sie auslöst, passiert nur bei großen Eingaben, was ihn zu einem Favoriten von Staging-Umgebungen macht.
  • Anzunehmen, dass JWT-Segmente Padding tragen. Das tun sie nicht, und ein Dekodierer, der Padding erwartet, scheitert beim letzten Zeichen mit einem Fehler, der wie ein Rätsel liest. Verwenden Sie RawURLEncoding.
  • Glauben, dass alle Leerzeichen übersprungen werden. Das stimmt nicht. Nur die zwei Zeilenumbruch-Zeichen werden übersprungen, und "Weißraum" in der Zwischenablage ist eine viel größere Familie als das.
  • Zweimal zu dekodieren oder nicht zweimal zu dekodieren, wenn der Wert Base64 von Base64 ist (eine Datei, die an eine E-Mail angehängt war, die ihrerseits angehängt war). Die Prüfung ist eine Runde hin und zurück: einmal dekodieren, schauen, ob das Ergebnis noch wie Base64 aussieht, und erst dann erneut dekodieren.

Die andere Hälfte der Arbeit

Das ist die ganze Dekodier-Seite der Geschichte: ein Paket, vier fertige Dekodierer, ein Stream-Dekodierer für große Daten, ein strikter Modus für knifflige Protokolle und Fehlermeldungen, die Ihnen das Byte sagen, wo die Dinge schiefgelaufen sind. Lernen Sie die Toleranz-Regeln, wählen Sie Ihren Dekodierer, indem Sie auf die Zeichen schauen, beschränken Sie Ihre Eingaben, und Base64 in Go wird die langweilige, vorhersehbare, abhängigkeitsfreie Utilität, die es sein soll.

Wenn der Job sich dreht und Ihr Go-Programm Base64-Strings erzeugen muss, statt sie zu öffnen, deckt der verwandte Artikel über Base64-Kodierung in Go diese Seite im Detail ab: die API des Encoders mit einer einzigen Methode, der Close-Aufruf, der Ihre letzten zwei Bytes stillschweigend schluckt, wenn Sie ihn vergessen, der Zeilenumbruch für MIME und wie sich die vier Kodierungen auf die Kanäle verteilen, durch die sie reisen.

Zuletzt aktualisiert: 2026-09-08

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