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

Ab und zu muss Ihr Go-Programm binäre Daten an eine Welt übergeben, die nur Text akzeptiert: ein JSON-Feld, das ein String bleiben muss, eine URL, die ein einzelner Token bleiben muss, ein E-Mail-Anhang, der Server durchquert, die sich an die 7-Bit-Tage erinnern, ein Bild, das im HTML leben will, damit die Seite eine Anfrage spart. Base64 ist der Kurier genau für diesen Job, und die Startseite dieser Site erklärt das Format bereits im Detail, also geht dieser Artikel direkt zur Kunst des Verpackens: das Erzeugen von Base64-Strings in Go, die jeder Dekodierer auf dem Planeten ohne Kampf öffnen kann.

Die gute Nachricht vorweg: Der Encoder ist die sanfte Hälfte der Geschichte. Eine Methode, keine Fehler-Rückgabe, kein Fehlermodus, byte-identische Ausgabe in jeder Go-Version seit der ersten stabilen. Das gesamte Drama lebt um diese Methode herum: das richtige Alphabet für den Kanal zu wählen, durch den der String reisen wird, der Close-Aufruf, der Ihre letzten zwei Bytes stillschweigend verschluckt, wenn Sie ihn vergessen, die Größensteuer, die das Format mit sich trägt, und die Tatsache, dass Go, wie Python, Java und Node, seine Ausgabe nie bei 76 Zeichen umbrechen lässt. Lernen Sie zuerst die Funktion kennen, dann die Fallen.

Verpacken ohne zu scheitern

Neunzig Prozent des Kodier-Alltags in Go sind eine Methode auf dem Encoding-Typ, und wie jeder Kodier-Einstiegspunkt im Paket (Encode, AppendEncode) hat sie keine Fehler-Rückgabe:

func (enc *Encoding) EncodeToString(src []byte) string

Reichen Sie ihm Bytes, und er gibt Ihnen einen String, und das ist der gesamte Vertrag:

package main

import (
  "encoding/base64"
  "fmt"
)

func main() {
  packed := base64.StdEncoding.EncodeToString([]byte("Man"))
  fmt.Println(packed) // TWFu
}

Es gibt keinen Fehler-Wert, weil es nichts gibt, was schiefgehen könnte: Jedes Byte ist legale Eingabe, das Alphabet deckt es immer ab, und die Ausgabe ist immer reines ASCII. Drei Eigenschaften lohnen sich zu merken, denn sie beantworten die Hälfte aller künftigen Fragen. Erstens ist die Ausgabelänge eine reine arithmetische Funktion der Eingabelänge, und das Paket reicht Ihnen die Formel sogar als Methode: EncodedLen(n) liefert (n+2)/3*4 für Kodierungen mit Padding, also werden 3 Eingabe-Bytes zu 4 Zeichen, 6 zu 8, und so weiter. Zweitens trägt das Format eine Größensteuer: Jede 3 Bytes Daten kommen als 4 Zeichen zurück, was die vertraute Erweiterung von rund 33 Prozent ist, die in Ihren Bandbreiten-Rechnungen und Speicher-Quotas auftaucht. Drittens ist die Methode deterministisch: dieselben Bytes produzieren immer denselben String, auf jeder Maschine, in jeder Version von Go, für immer. Dieser Determinismus ist es, der Base64 zu einem Serialisierungsformat macht und nicht zu einem Rätsel.

Ein Go-spezifischer Hinweis zur Eingabe-Seite: Die Methode nimmt []byte, nicht string, und die []byte(...)-Umwandlung ist an jeder Aufrufstelle explizit - Go wandelt nie einen String in einen Slice für Sie um - und sie erzeugt eine unabhängige Kopie der String-Bytes. Der Compiler kann diese Kopie weglassen, wenn der Slice nur gelesen wird und nicht entweicht, deshalb ist die Kosten normalerweise unmerkbar; aber wenn der Slice gespeichert oder zurückgegeben wird, zahlt die Laufzeit eine echte O(n)-Kopie. Text in einem Go-Programm ist nach Konvention UTF-8, also kodieren Sie, wenn Sie einen String kodieren, dessen UTF-8-Bytes, und genau das erwartet jeder moderne Dekodierer auf der anderen Seite. Mehr dazu im Abschnitt Text, Bytes und Unicode.

Wie Go es ausliefert

Wie alles in diesem Artikel kommt der Encoder aus dem Standardbibliotheks-Paket encoding/base64, das seit der ersten Version der Sprache ausgeliefert wird und dessen Quelldatei noch ihren Copyright-Hinweis aus 2009 trägt. Es gibt kein Modul, das Sie holen, keine Feature-Flagge, die Sie umlegen, und keinen Plattform-Knick: Wenn go version funktioniert, gibt go doc encoding/base64 Ihnen die ganze API aus.

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. Installieren Sie Go von den offiziellen Tarballs auf go.dev/dl, vom Paketmanager Ihrer Distribution (sudo apt install golang-go) oder über den golang.org/dl-Wrapper, wenn Sie Versionen jonglieren. Die base64-API ist auf beiden unterstützten Linien identisch, und die Tabelle unten ist die komplette Geschichte dessen, was sich je geändert hat - eine kurze Liste für ein so zentrales Paket:

Version Jahr Was sich in encoding/base64 geändert hat
Go 1.0 2012 Paket von Tag eins an stabil; Quell-Copyright 2009
Go 1.5 2015 RawStdEncoding und RawURLEncoding für Ausgabe ohne Padding hinzugefügt
Go 1.8 2017 Strict() für kanonisches Dekodieren hinzugefügt (Dekodierer-Seite)
Go 1.22 2024 AppendEncode und AppendDecode hinzugefügt; WithPadding lehnt nun falsche Argumente ab
Go 1.27.1 2026 Aktuelle Version; API unverändert, Verhalten byte-stabil durch das Go-1-Versprechen

Die praktische Konsequenz dieser Geschichte: Code, der 2015 gegen diese API geschrieben wurde, kompiliert und benimmt sich heute identisch, und die Strings, die Ihr Programm 2026 kodiert, dekodieren sich in jeder Go-Version korrekt, vergangener oder zukünftiger. Für ein Serialisierungsformat ist das die stille Superkraft.

Ein Alphabet für das Ziel wählen

Kodieren hat eine echte Entscheidung, und es ist eine Frage der Reise: Wohin wird dieser String gehen? Go gibt Ihnen vier fertige Encoder, und jeder ist auf einen anderen Kanal eingestellt:

Encoder Alphabet Padding Schicken Sie ihn dahin, wenn der String reist durch
StdEncoding A-Z a-z 0-9 + / = JSON-Bodies, MIME-Teile von E-Mails, Data-URLs, HTTP Basic Auth, PEM, die meisten APIs
URLEncoding A-Z a-z 0-9 - _ = URL-Pfade und -Queries, Dateinamen, überall dort, wo + oder / escaped werden müssten
RawStdEncoding A-Z a-z 0-9 + / kein Kompakte Standard-Alphabet-Strings, wo Padding nicht auftauchen darf
RawURLEncoding A-Z a-z 0-9 - _ kein JWT-Segmente, kompakte Identifikatoren, Tokens, die in URLs eingebettet sind

Die Begründung hinter den Varianten ist die Begründung hinter dem Format selbst. Das Standard-Alphabet ist das, was MIME und die meisten APIs erwarten, also ist es der Default und die sichere Antwort, wenn Ihnen niemand anderes gesagt hat. Das URL-sichere Alphabet existiert, weil + und / reservierte Zeichen in URLs sind: Ein Plus in einem Query-String wird oft als Leerzeichen gelesen, und ein Schrägstrich beginnt ein neues Pfadsegment, also bricht Standard-Base64 in einer URL entweder ab oder braucht ein Percent-Escaping für die Zeichen, die +, / oder = tragen - ein paar Prozent eines typischen Tokens. Sie durch - und _ zu ersetzen, die in Pfaden, Queries und Dateinamen ohne Escaping legal sind, ist die Lösung, die RFC 4648 standardisiert hat. Die Raw-Varianten streichen die schließenden Gleichheitszeichen komplett, was in Kontexten wichtig ist, wo Padding entweder verboten ist oder schlicht nie verwendet wird, wie bei JWT-Segmenten. Die Regel, die Sie vor den meisten Debugging-Runden rettet: Der Encoder, den Sie wählen, und der Dekodierer, den die andere Seite verwendet, sind ein einziger Vertrag, und der Vertrag wird vom Ziel geschrieben, nicht von Ihnen.

Wenn ein System, mit dem Sie sprechen, ein privates 64-Zeichen-Alphabet definiert hat, baut base64.NewEncoding("...64 chars...") Ihnen einen Encoder dafür, und WithPadding(rune) lässt Sie das Padding-Zeichen tauschen oder es mit NoPadding abschalten. Beide Funktionen paniken bei ungültigen Argumenten (falsche Alphabetlänge, doppeltes Zeichen, Zeilenumbruch im Alphabet, Padding-Zeichen, das mit dem Alphabet kollidiert), also bauen Sie Ihre benutzerdefinierten Encoder einmal, beim Start, nie in einem heißen Pfad.

Die Close-Falle

Hier ist die berühmteste Falle in diesem Paket, und sie erscheint nur, wenn Sie einen Stream kodieren statt eines Strings. NewEncoder hüllt jeden io.Writer in einen base64-kodierenden Writer, und weil Base64 in Blöcken aus drei Eingabe-Bytes arbeitet, die vier Ausgabe-Zeichen produzieren, muss der Encoder Ihre letzten ein oder zwei Bytes puffern und warten, ob noch mehr kommt. Sie werden nur geflusht, wenn Sie ihn schließen:

package main

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

func main() {
  var buf bytes.Buffer
  enc := base64.NewEncoder(base64.StdEncoding, &buf)
  enc.Write([]byte("hello"))
  fmt.Println(buf.String()) // aGVs  -- wo ist das "lo"?

  buf.Reset()
  enc = base64.NewEncoder(base64.StdEncoding, &buf)
  enc.Write([]byte("hello"))
  enc.Close()
  fmt.Println(buf.String()) // aGVsbG8=  -- die komplette Kodierung von "hello"
}

Die erste Ausgabe ist die ganze Lektion: Ohne Close emittierte der Encoder nur den ersten vollständigen Block, drei Bytes von "hello" wurden zu "aGVs", und die restlichen zwei Bytes verschwanden einfach in dem internen Puffer. Die zweite Ausgabe, nach Close, ist der korrekte, vollständige String. Die Lösung ist eine Gewohnheit, keine Technik: Im Moment, in dem Sie einen Encoder erzeugen, richten Sie auch seine Aufräumung ein:

enc := base64.NewEncoder(base64.StdEncoding, w)
defer enc.Close() // in Produktionscode daran denken, den zurückgegebenen Fehler zu prüfen

Zwei Details machen diese Falle schärfer, als sie aussieht. Erstens leistet Close echte Arbeit: Es flusht den ausstehenden Teil-Block und es kann fehlschlagen, weil es in den zugrunde liegenden Writer schreibt, also prüft die idiomatische Version seinen Fehler, besonders wenn das Ziel ein Netzwerk oder eine Platte ist. Zweitens sagt die Dokumentation, dass es ein Fehler ist, Write nach Close zu nennen, aber die Laufzeit erzwingt diesen Satz nicht. Wenn Sie nach dem Schließen wieder schreiben, startet der Encoder still einen frischen Block und hängt ihn an, und produziert einen String mit Padding in der Mitte, was ungültiges Base64 ist, das die meisten Dekodierer mit einem verwirrenden Offset ablehnen werden. Den Vertrag einzuhalten liegt bei Ihnen.

Zeilenumbruch, der Go-Weg

Jede andere große Base64-Implementierung, die Sie je benutzt haben, bricht ihre Ausgabe um: MIME will Zeilen von höchstens 76 Zeichen, PEM verwendet 64, E-Mail-Clients auf der ganzen Welt setzen ab und zu ein CRLF ein. Go's Encoder tut keins davon. Er emittiert eine durchgehende Zeile, egal wie groß der Payload ist, und tut das seit der Geburt des Pakets. Die Ausgabe für ein Megabyte Daten ist eine einzelne Zeile von einem Megabyte und einem Drittel, von Anfang bis Ende, ohne Unterbrechung.

Das ist eine bewusste Wahl, kein Versehen. Das Format funktioniert identisch mit oder ohne die Zeilenumbrüche, Go's eigener Dekodierer überspringt sie überall in der Eingabe, und ein Encoder, der still CRLFs in Ihre Daten einfügt, würde Programme überraschen, die den String in einer Datenbankspalte speichern oder ihn auf Gleichheit vergleichen. Der Preis ist, dass Sie selbst umbrechen müssen, wenn der Kanal es verlangt, und das ist ein kleiner Helfer:

package main

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

func wrapAt(s string, width int) string {
  var out bytes.Buffer
  for i := 0; i < len(s); {
    end := i + width
    if end > len(s) {
      end = len(s)
    }
    out.WriteString(s[i:end])
    out.WriteByte('\n')
    i = end
  }
  return out.String()
}

func main() {
  raw := base64.StdEncoding.EncodeToString(bytes.Repeat([]byte{0x42}, 100))
  fmt.Print(wrapAt(raw, 76))
}

Ein Hinweis zur Reise-Richtung: Weil Go's Dekodierer Zeilenumbrüche überall ignoriert, dekodiert umgebrochene Eingabe auf der Go-Seite jeder Brücke perfekt. Die andere Richtung braucht Aufmerksamkeit: Wenn Sie umgebrochene Ausgabe an einen Konsumenten senden, der keine Umbrüche erwartet (ein JSON-Feld, eine URL, ein Token), entfernen Sie sie zuerst, denn dieser Konsument könnte einen Zeilenumbruch als kaputtes Zeichen behandeln. Wissen Sie, in welcher Konvention Ihr Kanal lebt, und emittieren Sie sie mit Absicht.

Verpacken für E-Mail und MIME

E-Mail ist das älteste Zuhause von Base64. Das ursprüngliche SMTP-Protokoll wurde so entworfen, dass es 7-Bit-ASCII transportiert, also wurden Anhänge vor dem Versand base64-kodiert und bei der Ankunft dekodiert, und der MIME-Standard (RFC 2045) formalisierte die Praxis: Der Header Content-Transfer-Encoding: base64 markiert ein Teil, und der Body sollte in Zeilen von höchstens 76 Zeichen umgebrochen werden, mit CRLF dazwischen.

Go's net/smtp-Paket sendet die Bytes, die Sie ihm geben, und es wird keine MIME-Teile für Sie bauen, also sieht der Base64-Teil in einem Programm, das E-Mail verfasst, so aus:

package main

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

func main() {
  body := []byte("hi from Go")
  var part bytes.Buffer
  part.WriteString("Content-Transfer-Encoding: base64\r\n")
  part.WriteString("Content-Type: text/plain; charset=utf-8\r\n\r\n")

  encoded := base64.StdEncoding.EncodeToString(body)
  for i := 0; i < len(encoded); i += 76 {
    end := i + 76
    if end > len(encoded) {
      end = len(encoded)
    }
    part.WriteString(encoded[i:end] + "\r\n")
  }
  fmt.Print(part.String())
}

Drei Dinge zu bemerken. Der Standard-Encoder ist hier der richtige, denn MIME ist der ursprüngliche Standard-Alphabet-Kontext. Die Zeilenumbrüche sind CRLF, nicht die native Zeilenumbruch-Einheit der Plattform, denn das ist es, was der RFC vorgibt und was E-Mail-Parsers erwarten. Und wenn Ihr Programm echte E-Mails in Masse sendet, wird eine gepflegte MIME-Bibliothek die ganze Nachricht für Sie bauen; der Punkt dieses Beispiels ist die Base64-Hälfte, der Teil, der zu diesem Paket gehört. Bringen Sie das Alphabet und die Zeilen-Konvention richtig hin, und der Rest von MIME ist jemand anderes Problem.

Dateien verpacken

Für Dateien, die in den Speicher passen, ist das Muster dieselben zwei Zeilen wie überall sonst: lesen, dann EncodeToString. Für Dateien, die das nicht tun, hält das Streaming Ihren Speicher flach, und das Rezept ist eine Datei, ein Encoder, eine Kopie und zwei Closes in der richtigen Reihenfolge:

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

out, err := os.Create("photo.b64")
if err != nil {
  panic(err)
}
enc := base64.NewEncoder(base64.StdEncoding, out)
if _, err := io.Copy(enc, in); err != nil {
  panic(err)
}
if err := enc.Close(); err != nil {
  panic(err) // flusht den letzten Teil-Block
}
if err := out.Close(); err != nil {
  panic(err)
}

Die Reihenfolge der Closes ist der feine Teil, und sie ist die Dateiversion der Close-Falle: Der Encoder muss vor der Datei geschlossen werden, denn enc.Close ist es, was den letzten Teil-Block in die Datei schreibt, und die Datei zuerst zu schließen würde diesen Block in einem Puffer lassen, der ins Nichts schreibt. Bei defer denken Sie daran, dass deferred Aufrufe in umgekehrter Reihenfolge laufen, also ist es das, was die Sequenz sicher macht: out.Close zuerst und enc.Close zweitens zu registrieren (oder, wie im Beispiel oben, den Encoder explizit zu schließen, bevor die Datei deferred wird).

Halten Sie die Größensteuer im Kopf, wenn Sie mit diesem Muster planen: Ein 10-Megabyte-Foto wird zu rund 13,3 Megabytes Text, und ein 100-Megabyte-Archiv wird zu einem 133-Megabyte-String auf der Platte. Wenn das Ziel ein Quota, ein Limit oder einen Preis pro Byte hat, wird die Base64-Version Ihrer Datei gezählt, nicht das Original.

Verpacken fürs Web: Data-URLs

Browser laden ein Bild oder eine Schrift gerne aus einem String, der im HTML oder CSS selbst lebt, und dieser String ist eine Data-URL: der Medientyp, die ;base64-Flagge, ein Komma und der Payload, alles in einer URL. Go hat keinen Data-URL-Helfer, aber einen zu bauen ist String-Konkatenation, denn das Format ist ein Vertrag, den Sie geschrieben sehen können:

package main

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

func main() {
  img, err := os.ReadFile("logo.png")
  if err != nil {
    panic(err)
  }
  url := "data:image/png;base64," + base64.StdEncoding.EncodeToString(img)
  fmt.Println(url)
  // data:image/png;base64,iVBORw0KGgo...
}

Zwei Regeln halten Data-URLs aus den Dornen. Nehmen Sie immer den Medientyp auf: Er ist in der Grammatik optional (der Standard ist text/plain;charset=US-ASCII), aber ein Browser, der den Typ Ihres binären Payloads erraten soll, ist kein Szenario, das Sie wollen. Und behandeln Sie Data-URLs als Trick für kleine Assets. Der RFC sagt, das Schema sei nur für kurze Werte nützlich, und die 33-prozentige Erweiterung ist der Unterschied zwischen einem 2-Kilobyte-Icon, das eine Anfrage spart, und einem 5-Megabyte-Foto, das jeden Seitenaufruf aufbläht, ohne Cache, der es teilt, und ohne URL, die man jemandem geben könnte. Icons, Favicons, kleine Sprites: ja. Produktfotografie: nein.

Verpacken für HTTP

Drei HTTP-Kontexte dominieren Base64 in Go-Services, und zwei davon kommen mit eingebauter Hilfe. Der erste ist der JSON-Body, das Arbeitstier: Sie kodieren einen Wert vor dem Marshalen, und das Feld trägt einen einfachen String über die Leitung:

package main

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

type avatar struct {
  Data string `json:"data"`
}

func main() {
  png := []byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}
  a := avatar{Data: base64.StdEncoding.EncodeToString(png)}
  body, err := json.Marshal(a)
  if err != nil {
    panic(err)
  }
  fmt.Println(string(body))
  // {"data":"iVBORw0KGgo="}
}

Wenn ein Typ an vielen Stellen auftritt, ist der saubere Go-Zug, MarshalJSON und UnmarshalJSON dafür zu implementieren, damit der Base64-Schritt für jede Aufrufstelle unsichtbar ist. Der zweite Kontext ist HTTP Basic Auth, wo die Standardbibliothek den ganzen Job macht: Request.SetBasicAuth(user, pass) baut den Authorization-Header für Sie und führt den Standard-Encoder über das user:pass-Paar, das RFC 2617 vorgibt. Die eine Regel dort ist, nicht zu improvisieren: Basic Auth ist Standard-Base64 mit einem Basic -Präfix, und ein URL-sicheres Alphabet oder ein fehlendes Padding-Zeichen wird einen funktionierenden Login in einen 401 verwandeln, den niemand erklären kann.

Der dritte Kontext sind URLs, wo der String der Payload eines Pfadsegments oder eines Query-Parameters ist. Hier ist das Standard-Alphabet eine schlechte Wahl, denn +, / und = kollidieren alle mit der URL-Grammatik, und jedes Vorkommen braucht ein Percent-Escaping. Kodieren Sie stattdessen mit der URL-sicheren Variante, und der Token überlebt die URL unversehrt. Wenn der Konsument ihn trotzdem percent-escapt, ist nichts kaputt, aber wenn er es nicht tut, haben Sie sich eine Klasse von 404ern erspart.

URL-sichere Ausgabe

URL-sicheres Base64 verdient in Go seinen eigenen Abschnitt, denn es ist die Variante, zu der Sie häufiger greifen werden als zur Standard-Variante, und weil Go den Wechsel umsonst macht. Das alternative Alphabet aus RFC 4648 ersetzt + durch - und / durch _, so dass die Ausgabe in URL-Pfaden, Queries oder Dateinamen kein Escaping braucht und sich in einer Log-Zeile als ein sauberer Token liest. Die zwei fertigen Encoder sind URLEncoding (mit Padding) und RawURLEncoding (ohne Padding):

raw := []byte{0xfb, 0x0f, 0x67, 0x01}
fmt.Println(base64.StdEncoding.EncodeToString(raw))     // +w9nAQ==
fmt.Println(base64.URLEncoding.EncodeToString(raw))     // -w9nAQ==
fmt.Println(base64.RawURLEncoding.EncodeToString(raw))  // -w9nAQ

Ein Input, drei Outputs: Die Standard-Version braucht ein Percent-Escaping für ihr Plus-Zeichen, die URL-sichere Version ist ein Token, und die Raw-Version streicht auch das Padding. Die typischen Go-Jobs für jede: intransparente Identifikatoren, die ein Service erzeugt und dann in URLs, Routen oder Dateinamen speichert; API-Tokens, die Clients in Query-Strings einfügen; alles, was in einer Log-Zeile auftauchen wird, wo ein Plus oder ein Schrägstrich ein Zeichen von der Verwechslung mit Syntax entfernt ist.

Die Disziplin, die das sauber hält, ist dieselbe wie überall in diesem Artikel: Die Variante ist ein Vertrag mit dem Konsumenten. Wenn die andere Seite Standard-Base64 erwartet und Sie URL-sicher senden, scheitert ihr Dekodierer beim ersten Bindestrich, und der Fehler wird ein Byte-Offset in der Nähe des Endes eines einwandfreien Strings sein, was kein offensichtliches Ding zum Debuggen ist. Im Zweifel fragen Sie, was die andere Seite erwartet, lesen Sie die Spezifikation, auf die sie verweist, und wählen Sie den Encoder vom Ziel aus, nicht aus der Gewohnheit.

JWTs verpacken

JSON Web Tokens sind der sichtbarste Konsument von Base64 in modernen APIs, und sie fixieren die exakte Variante: Die JWS-Kompakt-Serialisierung laut RFC 7515 besteht aus drei base64url-Segmenten ohne Padding, verbunden durch Punkte. Header, Payload, Signatur. Das heißt, der Encoder der Wahl für alles, was Sie von Hand bauen, ist RawURLEncoding:

package main

import (
  "crypto/hmac"
  "crypto/sha256"
  "encoding/base64"
  "encoding/json"
  "fmt"
)

func main() {
  secret := []byte("hmac-secret")
  header, _ := json.Marshal(map[string]string{"alg": "HS256", "typ": "JWT"})
  payload, _ := json.Marshal(map[string]any{"sub": "1234567890"})

  signingInput := base64.RawURLEncoding.EncodeToString(header) + "." +
    base64.RawURLEncoding.EncodeToString(payload)

  mac := hmac.New(sha256.New, secret)
  mac.Write([]byte(signingInput))
  signature := base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
  fmt.Println(signingInput + "." + signature)
}

Lesen Sie das Beispiel als Lektion darüber, was das Format ist, nicht als Empfehlung, es zu verschiffen: Es zeigt genau, wo das Base64 sitzt (zweimal vor dem Signieren, einmal danach) und warum die Signatur die kodierten Segmente abdeckt, nicht das rohe JSON. In der Produktion signieren und verifizieren Sie mit einer gepflegten Bibliothek, denn JWT hat einen langen Schweif von Fehlern (Uhrdrift bei der Ablaufzeit, Algorithmus-Verwechslung, fehlende Audience-Prüfungen), die die Base64-Ebene nicht sehen kann. Die de-facto-Go-Bibliothek ist github.com/golang-jwt/jwt/v5, installiert mit go get github.com/golang-jwt/jwt/v5:

package main

import (
  "fmt"
  "log"
  "time"

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

func main() {
  secret := []byte("hmac-secret")
  token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
    "sub": "1234567890",
    "exp": time.Now().Add(time.Hour).Unix(),
  })
  signed, err := token.SignedString(secret)
  if err != nil {
    log.Fatal("signing failed:", err)
  }
  fmt.Println(signed)
}

Die Bibliothek führt die base64url-Kodierung jedes Segments intern durch, also berühren Sie encoding/base64 überhaupt nicht, was das beste Ergebnis ist: eine Stelle weniger, an der sich ein Padding- oder Alphabet-Fehler verstecken kann. Und beachten Sie den Schutz, den sie Ihnen gratis gibt: v5 lehnt Tokens ab, die alg=none behaupten, außer Sie opten explizit mit ihrer UnsafeAllowNoneSignatureType-Konstante ein, was der Schutz ist, den Sie wollen, ohne darüber nachzudenken.

Text, Bytes und Unicode

Go's Haltung zu dieser Frage ist die kürzeste aller großen Sprachen, und sie ist der Grund, warum Base64 hier so angenehm ist: Ein string in Go ist eine schreibgeschützte Folge von Bytes, und der Text in Ihrem Programm ist UTF-8. Es gibt keine versteckte Kodierungsebene, keine "der String ist eigentlich UTF-16"-Überraschung und keine Charset-Flagge, die Sie setzen müssten. Wenn Sie EncodeToString([]byte(myText)) schreiben, kodieren Sie die UTF-8-Bytes des Textes, Punkt:

s := "Café ☕"
packed := base64.StdEncoding.EncodeToString([]byte(s))
fmt.Println(packed) // Q2Fmw6kg4piV

Diese eine Zeile ist die ganze Geschichte für modernen Text, einschließlich Emoji und CJK: Base64 arbeitet auf Bytes, UTF-8 ist einfach eine Byte-Folge, und jeder Dekodierer auf der anderen Seite, der derselben Konvention folgt, gibt Ihnen denselben String zurück. Die []byte(...)-Umwandlung ist eine unabhängige Kopie, die der Compiler weglässt, wenn der Slice nur gelesen wird und nicht entweicht - in der Praxis kostet sie also nichts, was Sie messen könnten.

Der eine Fall, in dem die Geschichte länger wird, ist Legacy-Daten: Bytes, die von einem Windows-1252-, Shift JIS- oder ISO-8859-1-System erzeugt wurden und kein gültiges UTF-8 sind. Wenn Sie diese Bytes so wie sie sind base64-kodieren, haben Sie kaputten Text treu transportiert, was niemand wollte. Die Lösung ist, zu normalisieren, bevor Sie kodieren, mit golang.org/x/text, damit der Base64-String sauberes UTF-8 trägt, von dem Moment an, in dem er Ihr Programm verlässt:

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

legacy := []byte{0x43, 0x61, 0x66, 0xE9} // "Café" in Windows-1252
utf8, _, err := transform.Bytes(charmap.Windows1252.NewDecoder(), legacy)
if err != nil {
  panic(err)
}
packed := base64.StdEncoding.EncodeToString(utf8)
// Q2Fmw6k=  -- das gleiche "Café", jetzt saubere UTF-8-Bytes, bereit zu reisen

Dasselbe Modul deckt neben charmap auch japanese, korean, simplifiedchinese und traditionalchinese ab. Die praktische Regel: Einmal umwandeln, an der Grenze, wo Legacy-Bytes Ihr Programm betreten, und ab dann ist alles, was Sie kodieren, UTF-8. Nicht zweimal umwandeln, nicht raten, und nie einen nicht-UTF-8-Payload in einen Base64-String schmuggeln lassen, den ein moderner Konsument dekodieren und anzeigen wird.

Den Encoder messen

Der Encoder ist eine Tabellen-Suche ohne Verzweigung auf der Eingabe und ohne Allokation über den Ausgabe-String hinaus, und das zeigt sich in den Zahlen. Auf einer aktuellen Desktop-CPU mit Go 1.26 dauert das Kodieren von 500 Bytes ungefähr drei Zehntel einer Mikrosekunde mit zwei Allokationen, was in der Größenordnung von eineinhalb Gigabytes pro Sekunde liegt. Ein Megabyte Daten kodiert in gut unter einer Millisekunde; der Encoder wird selten etwas sein, das Sie spüren.

Der eine Hebel, den es zu kennen lohnt, ist das Allokationsprofil in heißen Schleifen. EncodeToString alloziert den Ausgabe-String bei jedem Aufruf, was der richtige Kompromiss für den 99-Prozent-Fall ist. Wenn Sie tausende Chunks pro Sekunde in einen wachsenden Puffer kodieren, hängt AppendEncode, hinzugefügt in Go 1.22, die kodierten Bytes an einen Slice, den Sie wiederverwenden, und führt im Dauerbetrieb keine Allokation durch, sobald der Puffer auf Größe gewachsen ist:

var out []byte
for _, chunk := range chunks {
  out = base64.StdEncoding.AppendEncode(out, chunk)
}

Verwenden Sie EncodeToString für Einmal-Dinge, AppendEncode für enge Schleifen und NewEncoder für Streams und Dateien. Welche Sie auch wählen: Denken Sie daran, dass das Netzwerk oder die Platte um den Encoder herum fast immer der langsame Teil ist, also profilieren Sie den ganzen Pfad, bevor Sie das Alphabet optimieren.

Sicherheits-Betrachtungen

Der wichtigste Satz zur Sicherheit in diesem Artikel: Base64 ist keine Verschlüsselung, und "wir base64-ieren es zuerst" ist keine Sicherheitsmaßnahme. Das Alphabet macht Daten text-sicher, nicht geheim, und jeder mit den Entwickler-Tools eines Browsers kann Ihr Base64 in einem Moment lesen. Die Vertraulichkeit kommt von TLS und von der Zugriffskontrolle, und der Job von Base64 ist es, Bytes durch einen nur-Text-Kanal zu bringen, ohne sie zu kaputtzumachen. Halten Sie diese beiden Jobs getrennt in Ihrem Design und in Ihrer Dokumentation, und Sie vermeiden den klassischen Review-Kommentar "das Passwort ist geschützt, schau, es ist Base64".

Die zweite Betrachtung ist die Größe. Weil das Format um ein Drittel aufbläht, hat jedes Limit in Ihrem System eine Base64-Version: Eine API, die 4 Megabytes JSON akzeptiert, akzeptiert rund 3 Megabytes Originaldaten, wenn der Payload ein Base64-Feld ist, eine URL mit einem Längen-Budget wird in rohen Bytes kürzer, wenn der Token URL-sicher und ohne Padding ist, und eine Datenbankspalte, die für den rohen Wert dimensioniert ist, kann für den kodierten zu klein sein. Machen Sie die Rechnung mit EncodedLen, bevor Sie speichern, senden oder begrenzen, und denken Sie daran, dass die Erweiterung auf der Eingabe liegt, mit der Sie anfangen, nicht auf dem String, mit dem Sie enden.

Drittens: Denken Sie darüber nach, wo der kodierte String beobachtet werden kann. Base64-Strings sind log-freundlich und bildschirm-freundlich, was eine Eigenschaft ist, bis ein 20-Megabyte-Anhang zu 26 Megabytes Text wird, die Ihr Access-Log bei jedem Request pflichtbewusst aufzeichnet. Loggen Sie die Länge, die ersten paar Dutzend Zeichen und den Identifikator, nicht den Payload, und Sie halten Ihre Logs lesbar und Ihre Platte am Leben. Zuletzt: Bevorzugen Sie in URLs die URL-sichere Variante, damit Ihre Tokens keine ihrer Zeichen als Percent-Escapes ausgeben, was die URL aufbläht und gelegentlich ein Gateway oder einen Proxy auslöst, der eine strenge Meinung darüber hat, was in einen Query-String gehört.

Spaß-Fakten und Go-Eigenheiten

Ein paar Fakten, die spezifisch für dieses Paket sind, für die Momente, in denen Sie in einem Code-Review recht haben wollen:

  • EncodeToString ist das Arbeitstier, und wie jeder Kodier-Einstiegspunkt im Paket (Encode, AppendEncode) hat es keine Fehler-Rückgabe - Kodieren kann in Go nicht fehlschlagen, was eine seltene und stille Art von Freiheit ist: Jedes Byte ist legale Eingabe, und der einzige Weg, einen schlechten String zu bekommen, ist, das falsche Alphabet für den Kanal zu wählen.
  • EncodedLen ist reine Arithmetik, (n+2)/3*4 für Kodierungen mit Padding, berechnet ohne Allokation und ohne Schleife. Es existiert, damit Sie Puffer und Quotas dimensionieren können, ohne jemals ein Byte zu kodieren.
  • Der interne Stream-Encoder versteckt einen 3-Byte-Eingabepuffer und einen 1024-Byte-Ausgabepuffer, deshalb schreibt NewEncoder in Chunks und deshalb kann der letzte Teil-Block nur durch Close hinaus. Die Puffer sind der Grund für die Falle.
  • Die Dokumentation sagt, dass es ein Fehler ist, nach dem Nennen von Close zu schreiben, aber die Laufzeit erzwingt den Satz nicht. Ein verspätetes Write wird akzeptiert, hängt einen frischen Block an und produziert einen String mit Padding in der Mitte: ungültiges Base64, höflich erzeugt, ohne dass ein Fehler-Wert in Sicht wäre.
  • Go's Encoder hat seine Ausgabe nie bei 76 Zeichen umgebrochen - wie die Encoder von Python, Java und Node produziert Go's Encoder für ein Megabyte Daten eine Zeile. Ihr MIME-Umbruch-Helfer ist ein persönliches Projekt, was auch ein guter Weg ist, sich zu merken, dass die Zeilenumbrüche in E-Mail-Base64 eine MIME-Konvention sind, keine Base64-Anforderung.
  • Stand August 2026 importieren mehr als 244.000 öffentliche Pakete auf pkg.go.dev encoding/base64. Was immer Ihr Go-Programm ist, es macht fast sicher irgendwo Base64, ob Sie es wissen oder nicht.
  • Das Go-1-Kompatibilitätsversprechen gilt für dieses Paket mit besonderer Kraft: Die Ausgabe eines Programms, das 2013 einen String kodiert hat, ist auf Go 1.27 heute byte-identisch. Base64-Strings sind in Go praktisch unsterblich.

Die Fehler, die immer wieder auftauchen

Die Kodier-Fehler, die immer wieder in Go-Codebasen auftauchen, in etwa der Reihenfolge, in der sie eintreffen:

  • Close am Stream-Encoder zu vergessen und einen String zu verschiffen, dem seine letzten ein oder zwei Bytes fehlen. Der Bug überlebt jeden Test, der Eingaben mit einer Länge verwendet, die ein Vielfaches von drei ist, und so erreicht er die Produktion.
  • Die Datei vor dem Encoder zu schließen, so dass der letzte Teil-Block in einen Datei-Handle flusht, der längst weg ist. Die Ausgabe ist um genau die gleiche Menge abgeschnitten, und der Fehler zeigt sich nur bei ungerade großen Eingaben.
  • 76-Zeichen-Zeilenumbrüche in MIME- oder E-Mail-Ausgabe zu erwarten und verwirrt zu sein, wenn Go Ihnen eine lange Zeile händigt. Das Umbrechen ist eine Kanal-Konvention, und in Go ist es die Aufgabe Ihres Codes, sie anzuwenden.
  • Das Standard-Alphabet in URLs zu verwenden und dann einen Nachmittag damit zu verbringen, 404ern und 400ern nachzujagen, die eigentlich ein Percent-Kodierungs-Problem sind. Wenn der String in einer URL leben wird, beginnen Sie mit URLEncoding oder RawURLEncoding.
  • Padding auszugeben, wo der Konsument es verbietet: JWT-Segmente, einige Token-Formate, ein paar strenge Parser. Die Raw-Varianten existieren genau aus diesem Grund, und die Fehlermeldung von der anderen Seite ist oft ein Byte-Offset ganz am Ende Ihres Strings.
  • Ein Geheimnis zu base64-ieren und es Schutz zu nennen. Es ist das nicht. Der Header, der Token, das "verschlüsselte" Feld: von jedem in einer halben Sekunde lesbar. Verwenden Sie TLS, verwenden Sie Hashing, wo das Protokoll einen Hash will, und lassen Sie Base64 seinen einen ehrlichen Job machen.
  • Die 33 Prozent zu vergessen, wenn Sie Limits setzen: Body-Größen, Spaltenbreiten, URL-Budgets, Quota-Prüfungen. Die Arithmetik ist ein Aufruf von EncodedLen, und der Preis, es wegzulassen, ist ein 413 oder eine abgeschnittene Spalte in der Produktion.
  • Text zu kodieren, der kein UTF-8 ist, was die Beschädigung treu transportiert. Normalisieren Sie Legacy-Charsets mit golang.org/x/text, bevor Sie kodieren, damit der Base64-String saubere Bytes trägt.
  • Nach dem Schließen in den Encoder zu schreiben, aus Gewohnheit oder aus einer Wiederholungs-Schleife. Es wird kein Fehler ausgegeben, und die Ausgabe ist stillschweigend ungültig.
  • Anzunehmen, dass der Dekodierer auf der anderen Seite so nachsichtig ist wie Go's. Go überspringt Zeilenumbrüche überall, aber andere Sprachen und Parser sind strenger mit Leerzeichen und mit der Zeilenlänge, also passen Sie der Konvention des Kanals an und nicht der Laune der Go-Laufzeit.

Die andere Seite

Das ist die Kodier-Seite der Geschichte: eine Methode, die nicht fehlschlagen kann, vier Encoder, die auf die Kanäle abgestimmt sind, durch die ihre Strings reisen, ein Stream-Encoder mit einem verpflichtenden Close und ein Format, das Ihre Daten um ein Drittel aufbläht und seine Zeilen nie, nie umbrechen lässt. Wählen Sie das Alphabet vom Ziel aus, schließen Sie Ihre Encoder, machen Sie die Größenrechnung von vorne, und Base64 in Go bleibt die stille, abhängigkeitsfreie Utilität, die es seit 2009 ist.

Und wenn sich der Verkehr umkehrt, wenn Ihr Programm einen dieser Strings empfängt und ihn öffnen muss, deckt der verwandte Artikel über Base64-Dekodierung in Go diese Seite im Detail ab: die Toleranz-Regeln des Dekodierers, die Fehler-Offsets, die Ihnen das Byte sagen, wo die Eingabe schiefgeht, der strikte Modus für knifflige Protokolle und dieselben vier Kodierungen aus der anderen Richtung.

Zuletzt aktualisiert: 2026-09-08

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