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

Da starrt Sie ein Wert an, der sich weigert, lesbar zu sein: SGVsbG8sIFdvcmxkIQ==. Ein langer Zug aus Buchstaben und Ziffern, zwischendurch ein gelegentliches + oder /, und meistens noch ein oder zwei =-Zeichen, die am Ende baumeln. Das ist Base64, und dieser Leitfaden dreht sich darum, daraus wieder zu machen, was es einmal war - ein Satz, ein Bild, ein Zertifikat, ein binärer Klumpen - auf die Kotlin-Art. Eine kurze Auffrischung, bevor wir einsteigen: Base64 packt jeweils drei Bytes in vier Zeichen aus einem Alphabet von 64 Symbolen, und ein kurzes =-Padding markiert, wo die echten Daten aufhörten. Die vollständige Format-Rundfahrt gibt es auf der Startseite, also widmen wir ihr hier nur einen Satz - und noch einen: Weil vier Zeichen das tragen, was drei Bytes trugen, ist die Textform ungefähr ein Drittel länger als die ursprünglichen Daten.

Die gute Nachricht bei Kotlin: Sie brauchen kein einziges Paket. Die Standardbibliothek bringt seit Jahren ihre eigene Base64-Implementierung mit; sie ist seit Kotlin 2.2 vollständig stabil und läuft auf jeder Plattform, auf der Kotlin läuft, von der JVM auf Ihrem Laptop über ein Android-Handy bis zu Node.js und einer WASI-Edge-Funktion. Alles unten funktioniert mit dem Kotlin, das bei Ihrem Projekt mitgeliefert wird.

Erst die gute Nachricht: Was Sie wirklich brauchen

Es gibt kein base64-Artefakt, das Sie zu Gradle hinzufügen müssten, kein NuGet-artiges Paket, kein npm-Modul. Die Klasse, die Sie suchen, ist kotlin.io.encoding.Base64, Teil der Kotlin-Standardbibliothek selbst. Wenn Sie println schreiben können, können Sie Base64 dekodieren. Drei APIs können in einem Kotlin-Projekt Base64-Arbeit erledigen, und die Wahl der richtigen ist die erste echte Entscheidung:

API Wo sie läuft Wann Sie danach greifen
kotlin.io.encoding.Base64 Jede Kotlin-Plattform: JVM, Android, JS, Native, Wasm Standardwahl. Stabil seit Kotlin 2.2, Multiplattform, moderne API
java.util.Base64 Nur JVM (Java 8+; auf Android API 26+) Codebasen, die nur auf der JVM laufen und ohnehin im Java-Interop-Umfeld zuhause sind
android.util.Base64 Nur Android (API 8+) Legacy-Android-Code, oder wenn Sie gezielt seine Flaggen-Konstanten brauchen

Zwei Versionsnotizen, die sich zu kennen lohnen. Erstens: Die Standardbibliotheksklasse erschien erstmals in Kotlin 1.8.20 (April 2023) hinter einem @ExperimentalEncodingApi-Tor; Kotlin 2.0.20 brachte den withPadding-Regler und die strenge Padding-Regel, und Kotlin 2.2.0 (Juni 2025) machte die API stabil und ergänzte die Pem-Instanz. Mit Kotlin 2.2 oder neuer - einschließlich der aktuellen stabilen Linie, 2.4.x - nutzen Sie also alles aus diesem Leitfaden ohne eine einzige Annotation. Zweitens: Legt Ihr Projekt eine Kotlin-Version zwischen 1.8 und 2.1 fest, existiert dieselbe Klasse, ist aber als experimentell markiert, und der Compiler lässt Sie sie nur mit einer @OptIn-Annotation auf der Funktion verwenden.

Eine Installationsfalle, die schon mehr als einen Nachmittag gekostet hat: Das kotlin-Paket in den Debian- und Ubuntu-Repositories ist Version 1.3.31, die ganz vor der Standardbibliothek-Base64-API liegt, und es kann kein einziges Beispiel aus diesem Artikel kompilieren. Holen Sie den Compiler stattdessen aus den Kotlin-Releases auf GitHub oder von SDKMAN, und legen Sie in Gradle-Projekten das Plugin explizit fest:

plugins {
  kotlin("jvm") version "2.4.10"
}

Ihr erstes Decode: zwei Zeilen und ein Ergebnis in Bytes

Die ganze Zeremonie passt in zwei Anweisungen, und die klassische TWFu-Zeichenfolge ist ein guter Einstieg:

import kotlin.io.encoding.Base64
fun main() {
  val packed = "TWFu"
  val bytes = Base64.decode(packed)
  println(bytes.decodeToString())  // Man
}

Lesen Sie das langsam, denn darin verstecken sich drei Designentscheidungen. Erstens: Base64.decode(...) ohne irgendein .Default ist kein Tippfehler - Default ist das Companion-Objekt der Klasse, und die Funktion auf der Klasse selbst aufzurufen, ist eine Kurzform für den Aufruf auf Base64.Default. In älteren Tutorials sehen Sie auch Base64.Default.decode(...), und es bedeutet exakt dasselbe. Zweitens, und das ist wichtiger, als es aussieht: decode übergibt Ihnen ein ByteArray, niemals einen String. Die Nutzlast könnte ein JPEG sein, ein X.509-Zertifikat oder ein Satz, und die API weigert sich zu raten, welches davon, also ist der Sprung von Bytes zu Text ein eigener, bewusster Schritt. Drittens: Genau dort wohnt die Zeichensatz-Entscheidung, und genau dort entstehen die meisten "Mein Base64 kam als Unfug zurück"-Bugs. Wir kommen gleich dazu; zuerst ein Roundtrip, um zu beweisen, dass das Decode treu ist:

import kotlin.io.encoding.Base64
fun main() {
  val original = "Hello, World!".encodeToByteArray()
  val packed = Base64.encode(original)
  val back = Base64.decode(packed)
  println(packed)                    // SGVsbG8sIFdvcmxkIQ==
  println(back.contentEquals(original))  // true
}

Vier Schemata, vier Persönlichkeiten

Die Klasse wird nie instanziert; Sie wählen eine von vier vorgefertigten Instanzen, und jede dekodiert mit einem anderen Temperament:

import kotlin.io.encoding.Base64
fun main() {
  val data = "Hello?".encodeToByteArray()
  println(Base64.Default.encode(data))  // SGVsbG8/
  println(Base64.UrlSafe.encode(data))  // SGVsbG8_
  println(Base64.Mime.encode(data))     // SGVsbG8/
  println(Base64.Pem.encode(data))      // SGVsbG8/
}
Instanz Alphabet Wie sie dekodiert
Base64.Default A-Z a-z 0-9 + / Streng: Jedes Zeichen außerhalb des Alphabets wirft; Padding ist erforderlich
Base64.UrlSafe A-Z a-z 0-9 - _ Streng, aber gegen das URL-Alphabet; ein + oder / in der Eingabe wirft
Base64.Mime A-Z a-z 0-9 + / Nachsichtig: Ignoriert Zeilentrenner und andere Nicht-Alphabet-Zeichen, aber nach dem =-Padding darf nichts folgen; Padding ist erforderlich
Base64.Pem A-Z a-z 0-9 + / Nachsichtig, dieselben Regeln wie Mime; das ist die PEM/PKI-Variante desselben Alphabets

Die Aufteilung in nachsichtig und streng ist das Nützlichste, das Sie sich einprägen können. Default und UrlSafe behandeln jedes fremde Zeichen wie eine Tatstelle und werfen sofort. Mime und Pem lassen Zeilenumbrüche, Leerzeichen und überflüssige Satzzeichen mit einem Achselzucken zu - denn genau das enthalten echte E-Mails und Zertifikatdateien - aber sie sind nicht unbegrenzt: Im Moment, in dem ein Datenzeichen nach dem Padding auftaucht, werfen auch sie. Die genauen Fehlermeldungen sehen Sie im Fehlerleitfaden später in diesem Artikel.

Noch eine Folge der Persönlichkeiten: Ein Schema kann die Ausgabe eines anderen nicht lesen. Geben Sie ein base64url-Token an Base64.Default, und das --Zeichen ist nicht in seinem Alphabet, also bekommen Sie IllegalArgumentException: Invalid symbol '-'(55) at index .... Im Zweifel, woher eine Zeichenfolge stammt, wählen Sie das Schema, das zum Erzeuger passt, nicht das, das Ihrer Laune entspricht.

URL-Sicheres Base64 und JWTs

Zwei Zeichen im Standard-Alphabet machen Probleme, sobald Daten eine URL durchqueren müssen. Ein + im Query-String wird bis zum Lesen routinemäßig als Leerzeichen umgedeutet, und / ist ein Pfadtrenner, der in einem URL-Segment gar nicht erst auftreten darf. RFC 4648, Abschnitt 5, löst das, indem es die letzten zwei Symbole des Alphabets austauscht: + wird zu - und / zu _. Der Name, den Sie am häufigsten hören werden, ist base64url, und in Kotlin heißt es Base64.UrlSafe.

Der größte Verbraucher von base64url ist der JSON Web Token. Ein JWT in seiner kompakten Form besteht aus drei base64url-Teilen, verbunden durch Punkte: header.payload.signature. RFC 7515 definiert diese Teile als base64url ohne Padding, was eine zweite Abweichung vom einfachen Alphabet ist, nicht nur die Zeichen. Hier wird ein Token zum Untersuchen geöffnet:

import kotlin.io.encoding.Base64
fun main() {
  val token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
  val (header, payload, signature) = token.split(".")
  val lenient = Base64.UrlSafe.withPadding(Base64.PaddingOption.PRESENT_OPTIONAL)
  println(lenient.decode(header).decodeToString())
  // {"alg":"HS256"}
  println(lenient.decode(payload).decodeToString())
  // {"sub":"1234567890","name":"John Doe"}
  println(signature.length)  // 43
}

Zwei Dinge fallen auf. Die Token-Teile tragen kein Padding, aber Base64.UrlSafe verlangt ab Werk Padding, also leistet die Zeile withPadding(PRESENT_OPTIONAL) echte Arbeit: Sie akzeptiert gepolsterte und ungepolsterte Eingaben gleichermaßen. Und das split(".") plus Destrukturierung ist einfach Kotlin, das tut, was das Format verlangt. Eine ernste Warnung: Ein JWT aufzumachen ist etwas fürs Ansehen eines Tokens, nicht fürs Vertrauen in es. Header und Payload sind nach dem Dekodieren einfache Daten; nur eine verifizierte Signatur besagt, dass das Token echt ist, und dafür wollen Sie eine echte JWT-Bibliothek, kein handgemachtes String-Zerlegen.

Padding-Modi und der Strenge-Regler

Padding ist in Kotlin keine feste Tatsache über Base64, sondern eine Einstellung. Jede Instanz trägt eine PaddingOption, alle vier vorgefertigten Instanzen starten auf PRESENT, und withPadding übergibt Ihnen eine neue Instanz mit einer anderen Einstellung, während die Originalinstanz unangetastet bleibt. Hier ist der Regler, Option für Option:

Option Eingabe ohne Padding Eingabe mit korrektem Padding
PRESENT (Standard überall) Wirft Dekodiert
ABSENT Dekodiert Wirft
PRESENT_OPTIONAL Dekodiert Dekodiert
ABSENT_OPTIONAL Dekodiert Dekodiert
import kotlin.io.encoding.Base64
fun main() {
  val data = "Hello".encodeToByteArray()
  println(Base64.Default.withPadding(Base64.PaddingOption.ABSENT).encode(data))
  // SGVsbG8
  println(Base64.Default.encode(data))
  // SGVsbG8=
  val eitherWay = Base64.Default.withPadding(Base64.PaddingOption.PRESENT_OPTIONAL)
  println(eitherWay.decode("SGVsbG8").decodeToString())   // Hello
  println(eitherWay.decode("SGVsbG8=").decodeToString())  // Hello
}

Auf der Dekodier-Seite ist PRESENT_OPTIONAL Ihr Sicherheitsnetz: Das ist die Option, die sagt "Ich weiß nicht, ob der Absender gepolstert hat, und ich habe vor, trotzdem weiterzuarbeiten." Die Fehlermeldungen für die anderen Kombinationen sind ungewöhnlich hilfreich, also erkennen Sie sie sofort, wenn ein strenger Dekodierer die falsche Eingabe trifft: Fehlendes Padding bei PRESENT erzeugt The padding option is set to PRESENT, but the input is not properly padded, und Padding bei ABSENT erzeugt The padding option is set to ABSENT, but the input has a pad character at index 7. Ein Verhalten verdient eine Hervorhebung, weil es Menschen überrascht: Ein doppeltes Padding wie SGVsbG8== ist nicht "extra, aber in Ordnung". Das erste = beendet die Daten, und das zweite ist ein Zeichen an einer Stelle, an der Daten erwartet wurden, also lehnen es selbst die nachsichtigsten Dekodierer ab.

Versteckt ist hier auch eine Versionsgeschichte. Wenn Sie Code geerbt haben, der gegen die experimentelle 1.8.x-API geschrieben wurde, denken Sie daran, dass das alte decode Eingaben mit oder ohne Padding akzeptierte. In Kotlin 2.0.20 ist Default zur strengen PRESENT-Regel übergegangen, also wirft eine damals noch funktionierende ungepolsterte Eingabe nach einem Upgrade über diese Punkt-Version hinaus. Die Reparatur ist eine Zeile: withPadding(Base64.PaddingOption.PRESENT_OPTIONAL), oder Sie normalisieren Ihre Eingaben vor dem Dekodieren.

Von Bytes zu Text: Zeichensätze und Unicode

Sobald Sie Ihr ByteArray haben, ist die Frage, was es bedeutet. Wenn die Nutzlast Text ist, heißt die Standardantwort decodeToString(), die die Bytes als UTF-8 interpretiert und auf jeder Plattform funktioniert. Für den üblichen Fall moderner APIs, E-Mails und Web-Daten ist das alles, was Sie je brauchen werden, und Emojis inklusive:

import kotlin.io.encoding.Base64
fun main() {
  val original = "héllo 😀"
  val packed = Base64.encode(original.encodeToByteArray())
  println(packed)                                  // aMOpbGxvIPCfmIA=
  println(Base64.decode(packed).decodeToString())  // héllo 😀
}

Sobald der Absender aber irgendetwas anderes als UTF-8 benutzt hat, liegt die Zeichensatz-Entscheidung bei Ihnen. Die eingebauten Textkonvertierungen von Kotlin sind bewusst nur UTF-8: decodeToString() hat keinen Zeichensatz-Parameter, und es gibt auch keine String-nach-Bytes-Funktion mit einem. Auf der JVM steigen Sie zur Plattform-Zeichensatz-API ab, die ehrlich und explizit ist:

import kotlin.io.encoding.Base64
import java.nio.charset.Charset
fun main() {
  val latinOne = "héllo".toByteArray(Charsets.ISO_8859_1)
  val packed = Base64.encode(latinOne)
  println(packed)  // aOlsbG8=
  val asUtf8 = Base64.decode(packed).decodeToString()
  val asLatin = String(Base64.decode(packed), Charsets.ISO_8859_1)
  println(asUtf8)   // h?llo  (das é-Byte ist kein gültiges UTF-8)
  println(asLatin)  // héllo
  val byName = String(Base64.decode(packed), Charset.forName("ISO-8859-1"))
  println(byName)   // héllo
}

Das ? in der mittleren Zeile ist kein Font-Problem; es ist U+FFFD, das Unicode-Ersetzungszeichen, das für ein Byte einsteht, das kein gültiges UTF-8 bildet. Wenn Sie nach dem Dekodieren eine Kette davon sehen, ist Ihre Nutzlast in Ordnung - Ihre Zeichensatz-Annahme ist es nicht. Beachten Sie auch die Asymmetrie, die Menschen beißen: Auf der Kodier-Seite existiert die JVM-Erweiterung toByteArray(charset); auf der Dekodier-Seite ist der passende Konstruktor String(bytes, charset). Keiner nimmt einen Zeichensatz-Namen; dafür brauchen Sie Charset.forName("..."), das für einen erfundenen Namen UnsupportedCharsetException wirft, also schlägt ein Tippfehler in einem Konfigurationswert sofort fehl, statt still einen anderen Zeichensatz auszuwählen.

Während wir schon mal im Byte-Land sind, eine Kotlin-spezifische Falle: Ein Char ist ein 16-Bit-Wert, und toByte() darauf behält stillschweigend nur die niedrigen acht Bits bei. Wenn Sie Bytes von Hand aus Zeichen basteln, gibt Ihnen "中".first().code.toByte() die Zahl 45, eine Zahl, die mit dem Zeichen nichts zu tun hat. Der korrekte Weg ist immer encodeToByteArray(), das die eigentliche Kodierarbeit erledigt - dasselbe Zeichen sind drei UTF-8-Bytes, und ihre Base64-Form ist 5Lit. Lassen Sie die Standardbibliothek kodieren; packen Sie niemals von Hand Zeichen in Bytes.

Dateien, Teilstücke und große Eingaben

Base64-Daten sind nicht immer eine nette Zeichenfolge im Speicher. Manchmal ist es eine Datei, ein Ausschnitt einer größeren Antwort, oder zu groß, um alles auf einmal zu halten. Kotlin gibt Ihnen alle drei Türen.

Dateien sind der langweilige Fall, auf die beste Art: Bytes lesen, dekodieren, fertig. Beide Standard-Datei-APIs funktionieren, je nachdem, was Ihr Projekt bereits nutzt:

import java.io.File
import kotlin.io.encoding.Base64
import kotlin.io.path.Path
import kotlin.io.path.readBytes
fun main() {
  val fromFile = File("payload.b64").readBytes()
  println(Base64.decode(fromFile.decodeToString()).size)  // Anzahl der dekodierten Bytes
  val fromPath = Path("payload.b64").readBytes()
  println(Base64.decode(fromPath.decodeToString()).size)  // dieselbe Zahl
}

Teilstücke sind der Fall, für den sich die CharSequence-Overloads bezahlt machen. decode akzeptiert jede Zeichenfolge mit Start- und Endindex, also können Sie ihr einen Ausschnitt eines langen Antwortkörpers übergeben, ohne zuerst eine Kopie des Ausschnitts zu erstellen:

import kotlin.io.encoding.Base64
fun main() {
  val body = "prefix junk SGVsbG8= trailing junk"
  val bytes = Base64.decode(body, 12, 20)
  println(bytes.decodeToString())  // Hello
}

Wenn Sie die Ausgabegröße schon kennen und einen Puffer wiederverwenden wollen, schreibt decodeIntoByteArray in ein Zielfeld Ihrer Wahl und sagt Ihnen, wie viele Bytes es geschrieben hat. Geben Sie einen zu kleinen Puffer, und es wirft IndexOutOfBoundsException mit der benötigten Kapazität in der Meldung, also dient der Fehler gleich als Ihr Größen-Hinweis.

Für wirklich große Ströme auf der JVM gibt es eine dritte Tür: die Streaming-Dekodierer. Sie sind weiterhin als experimentell markiert - daher die Opt-in-Annotation - und sie existieren nur für die JVM, aber sie dekodieren auf dem Laufenden, statt alles im Speicher zu halten:

import java.io.ByteArrayInputStream
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi
import kotlin.io.encoding.decodingWith
@OptIn(ExperimentalEncodingApi::class)
fun main() {
  val stream = ByteArrayInputStream("SGVsbG8gV29ybGQh".toByteArray())
  stream.decodingWith(Base64.Default).use {
    println(it.readBytes().decodeToString())  // Hello World!
  }
}

Zwei praktische Details. Die Erweiterungs-Funktionen liegen auf der obersten Ebene des Pakets, also importieren Sie sie namentlich (ein Stern-Import funktioniert auch, aber Namen sind freundlicher). Und der Dekodierer behandelt das Padding als harten Stopp: Wenn der zugrunde liegende Stream nach dem Base64-Abschnitt weitergeht, endet das Lesen aus dem dekodierten Stream am =, und die übrig gebliebenen Bytes bleiben im Original-Stream verfügbar. Das macht es sauber für Formate, die Base64 vor etwas anderem anhängen.

Im Feld: HTTP-APIs und JSON-Körper

JSON kann keine rohen Bytes tragen - es ist ein Text-Protokoll - also wrappen APIs, die Binärdaten bewegen müssen (Bilder, Zertifikate, beliebige Klumpen), fast immer Base64 in einem String-Feld ein. Das Muster lautet: JSON parsen, Feld nehmen, dekodieren. Mit der offiziellen Serialisierungsbibliothek ist der JSON-Teil zwei Annotationen entfernt:

import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlin.io.encoding.Base64
@Serializable
data class ImageResponse(val name: String, val data: String)
fun main() {
  val body = """{"name":"icon.png","data":"iVBORw0KGgo="}"""
  val response = Json.decodeFromString<ImageResponse>(body)
  val bytes = Base64.decode(response.data)
  println("${response.name}: ${bytes.size} bytes")  // icon.png: 8 bytes
}

Dieses Beispiel braucht das Serialisierungs-Plugin und die Bibliothek, einmalig im Build hinzugefügt:

plugins {
  kotlin("jvm") version "2.4.10"
  kotlin("plugin.serialization") version "2.4.10"
}
dependencies {
  implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.11.0")
}

Ohne die Bibliothek funktioniert dieselbe Idee auf der rohen Zeichenfolge, was für schnelle Skripte nützlich ist: Das Feld mit substringBetween herausziehen und dekodieren. Die Stolperfallen sind die bekannten aus der API-Arbeit: Das Feld kann tatsächlich eine vollständige Data URL sein (mit dem data:image/png;base64,-Präfix, später in diesem Artikel behandelt), die Nutzlast kann MIME-umhüllt mit Zeilenumbrüchen sein, und die kodierte Nutzlast kann etwa ein Drittel größer als die ursprünglichen Binärdaten sein, also behalten Sie Ihr Speicherbudget bei großen Antworten im Auge.

Im Feld: E-Mails und MIME-umhüllte Eingabe

E-Mail ist eine 7-Bit-Text-Welt, und die Antwort von RFC 2045 auf binäre Anhänge ist Base64 mit einem Twist: Die kodierte Ausgabe muss so umgebrochen werden, dass keine Zeile länger als 76 Zeichen wird. Wenn Sie schon mal einen Anhang als Text erhalten haben, sieht er deshalb wie eine eingerückte Spalte aus Base64 aus. Für genau diese Eingabe ist Base64.Mime der richtige Dekodierer, denn er ignoriert auf dem Weg Zeilentrenner und andere Nicht-Alphabet-Zeichen:

import kotlin.io.encoding.Base64
fun main() {
  val wrapped = "SGVs\nbG8=\r\n"
  println(Base64.Mime.decode(wrapped).decodeToString())  // Hello
  val withJunk = "Y@{mFz!Z!TY}0"
  println(Base64.Mime.decode(withJunk).decodeToString())  // base64
}

Die Nachsicht ist echt, aber begrenzt. Umhüllen Sie die Eingabe, streuen Sie ein, zwei Leerzeichen ein, kein Problem. Hängen Sie aber ein Datenzeichen nach dem letzten = an, und selbst Mime wirft: Symbol 'e'(145) at index 7 is prohibited after the pad character. Und denken Sie daran: Mime verlangt weiterhin, dass das Padding vorhanden und korrekt ist; ein MIME-Dekodierer, der auch fehlendes Padding schlucken würde, würde Ärger einfordern. Das praktische Rezept für unordentliche eingehende E-Mail-Nutzlasten lautet: zuerst Mime-Dekodierung, und wenn die wirft, schauen Sie sich die Meldung an - sie sagt Ihnen genau, welches Symbol an welchem Index die Regeln gebrochen hat.

Im Feld: Bilder und Data URLs

Eine Data URL ist der Web-Weg, eine Datei direkt in ein Dokument einzubetten: ein Medientyp, ein base64,-Marker und die Nutzlast, alles in einer Zeichenfolge. Browser, CSS und eingebettete UIs lieben sie für kleine Assets - Icons, Avatare, Platzhalter-Grafiken - denn es muss keine zweite Anfrage gestellt werden. Das Format sieht so aus:

data:image/png;base64,iVBORw0KGgoAAAANSUhEUg==

Eine davon in Kotlin zu dekodieren, ist eine String-Operation, gefolgt von einem Base64-Decode. Das Präfix trägt kein Geheimnis; alles nach dem Komma ist die Nutzlast:

import kotlin.io.encoding.Base64
fun main() {
  val dataUrl = "data:image/png;base64,iVBORw0KGgo="
  val mediaType = dataUrl.substringBefore(";")
  val packed = dataUrl.substringAfter("base64,")
  val bytes = Base64.decode(packed)
  println(mediaType)          // data:image/png
  println(bytes.size)         // 8
  println(bytes.contentToString())  // [-119, 80, 78, 71, ...]
}

Dieses erste Byte, -119 (das ist 0x89), gefolgt von den Buchstaben PNG, ist die magische Zahl, die eine PNG-Datei identifiziert. Die ersten vier oder acht Bytes nach dem Dekodieren zu prüfen, ist ein billiger Weg, um zu bestätigen, dass eine Data URL wirklich enthält, was ihr Präfix behauptet. Zwei ehrliche Vorbehalte: Base64 erhöht die Größe um etwa ein Drittel, also ist eine Data URL ein Tausch, bei dem Sie Größe bezahlen, um eine Netzwerk-Roundtrip einzusparen, und für alles Große sind Sie in der Regel besser beraten, die Datei von einer echten URL aus zu liefern und den Cache seine Arbeit machen zu lassen.

Im Feld: Konfiguration, Umgebungsvariablen und Datenbanken

Base64 taucht in Konfigurationsdateien und Umgebungsvariablen auf, wann immer ein Binärwert einen nur-Text-Kanal nehmen muss: ein kleines eingebettetes Icon in einer Properties-Datei, ein Token, das in einer Env-Variable eines Containers gespeichert ist, ein Byte-Klumpen, der in einer Textspalte parkt, weil das Schema älter ist als ein ordentlicher Binär-Typ. Die Dekodier-Seite sind überall dieselben zwei Schritte - Text lesen, dekodieren:

import kotlin.io.encoding.Base64
fun main() {
  val line = "icon: UE5HREFUQQ=="
  val packed = line.substringAfter("icon: ").trim()
  val bytes = Base64.decode(packed)
  println(bytes.decodeToString())  // PNGDATA
  val fromEnv: String? = System.getenv("MY_ICON_B64")
  if (fromEnv != null) {
    println(Base64.decode(fromEnv).size)
  }
}

Die Stolperstelle in dieser ganzen Nachbarschaft passt in eine Zeile: Base64 ist keine Verschlüsselung. Es ist ein Transport-Trick, kein Schloss. Niemand sollte einen Base64-Wert lesen und denken, die Daten darin wären versteckt; sie sind einen Funktionsaufruf von sichtbar entfernt, und sie sind in jeder Log-Zeile sichtbar, die Sie schreiben. Wenn ein Wert sensibel ist, halten Sie ihn von Anfang bis Ende sensibel - ein Secret-Store, eine verschlüsselte Spalte, was auch immer Ihr Stack bietet - und benutzen Sie Base64 nur, damit die Bytes durch Text reisen können, nicht um sie zu schützen.

Im Feld: Die Kommandozeile

Der älteste Anwendungsfall von allen: Einen Base64-Klumpen an der Kommandozeile in eine Datei verwandeln. Ein komplettes Tool sind acht Zeilen Kotlin, denn die Standardbibliothek erledigt die schwere Arbeit. Kompilieren Sie es einmal mit dem Kotlin-Compiler, und es ist für immer Ihres:

import java.io.File
import kotlin.io.encoding.Base64
fun main(args: Array<String>) {
  val packed = if (args.isNotEmpty()) args[0] else readlnOrNull().orEmpty()
  val bytes = Base64.decode(packed.trim())
  File("decoded.bin").writeBytes(bytes)
  println("Wrote ${bytes.size} bytes to decoded.bin")
}

Führen Sie es mit einem Argument für einen Einmal-Wert aus, oder leiten Sie eine Datei hinein für Batch-Arbeit: Das Programm liest das erste Argument, wenn vorhanden, sonst fällt es auf die Standard-Eingabe zurück. Das trim() leistet hier stillen Dienst, denn Shell-Argumente und eingefügte Werte kommen gerne mit überflüssigem Weißraum an, den der strenge Dekodierer ablehnen würde. Und wenn Ihre Nutzlasten base64url sind, tauschen Sie Base64.decode gegen Base64.UrlSafe.withPadding(Base64.PaddingOption.PRESENT_OPTIONAL).decode aus, und das Tool ist auch für Tokens bereit.

Ein Feldleitfaden zu Decode-Fehlern

Jeder Dekodierer in diesem Leitfaden scheitert mit einem von zwei Ausnahme-Typen, und jede Meldung ist spezifisch genug, um Ihnen genau zu sagen, was schiefgelaufen ist. Hier ist die vollständige Karte, mit den exakten Meldungen, die die Standardbibliothek erzeugt:

Situation Ausnahme Meldung (wie erzeugt)
Zeichen außerhalb des Alphabets (Leerzeichen, Zeilenumbruch, Symbol des falschen Schemas) IllegalArgumentException Invalid symbol ' '(40) at index 5
Datenzeichen nach dem Padding IllegalArgumentException Symbol 'e'(145) at index 7 is prohibited after the pad character
Fehlendes Padding, während die Option PRESENT ist IllegalArgumentException The padding option is set to PRESENT, but the input is not properly padded
Padding vorhanden, während die Option ABSENT ist IllegalArgumentException The padding option is set to ABSENT, but the input has a pad character at index 7
Index außerhalb der Grenzen der Quelle IndexOutOfBoundsException startIndex: 0, endIndex: 100, size: 8
startIndex größer als endIndex IllegalArgumentException startIndex: 3 > endIndex: 2
Zielpuffer zu klein für decodeIntoByteArray IndexOutOfBoundsException The destination array does not have enough capacity, destination offset: 0, destination size: 2, capacity needed: 8

Beachten Sie das Muster in den ersten beiden Zeilen: Die Meldung nennt das Übeltäter-Symbol, seinen numerischen Code in Klammern und seinen Index. Das ist ein Debugging-Geschenk. Wenn ein Decode in Produktion wirft, loggen Sie die ersten paar Dutzend Zeichen der Eingabe und den Index aus der Meldung, und Sie finden den Schuldigen fast immer in Sekunden, ob es ein eingefügter Zeilenumbruch, eine abgeschnittene Nutzlast oder eine base64url-Zeichenfolge ist, die in einen Standard-Dekodierer gewandert ist.

Fallen, die Kotlin-Entwickler besonders schmerzen

  • Annehmen, die Nutzlast sei Text. decode liefert bewusst ein ByteArray. decodeToString() auf einem JPEG aufzurufen, weil es "wahrscheinlich Text ist", ergibt eine Wand aus Ersetzungszeichen. Entscheiden Sie, was die Bytes sind, bevor Sie sie konvertieren.
  • Kopier- und Einfüge-Weißraum. Der Standard-Dekodierer ist streng, und ein Wert, den Sie aus einer Chat-Nachricht oder einem Log gezogen haben, kommt fast immer mit einem Zeilenumbruch am Ende oder einem Leerzeichen am Anfang an. Trimmen Sie vor dem Dekodieren, oder dekodieren Sie über Mime, oder akzeptieren Sie die IllegalArgumentException und behandeln Sie sie.
  • Upgrade aus der Experimentier-Ära. Code, der für die 1.8.x-Experimentier-API geschrieben wurde, trug @OptIn(ExperimentalEncodingApi::class)-Annotationen und verließ sich darauf, dass Padding optional war. Ab 2.0.20 kann dieselbe Eingabe werfen. Die Reparatur ist PRESENT_OPTIONAL, oder das Säubern der Eingaben, bevor sie den Dekodierer erreichen.
  • Das Schema zum falschen Erzeuger passen. Ein JWT-Teil, der mit Base64.Default dekodiert wird, scheitert an seinen -- und _-Zeichen; eine Nutzlast im Standard-Alphabet, die mit UrlSafe dekodiert wird, scheitert an + und /. Die Ausnahme nennt das exakte Symbol, aber die Reparatur ist zu wissen, woher die Zeichenfolge stammt.
  • Die Zeichensatz-Lücke. decodeToString() ist nur UTF-8, ohne Overload für andere Kodierungen. Wenn der Absender Latin-1 oder Windows-1252 benutzt hat, planen Sie String(bytes, charset) auf der JVM ein, und erwarten Sie U+FFFD-Ersetzungszeichen als Symptom, wenn Sie es vergessen.
  • Der Compiler aus dem Distributions-Paket. apt install kotlin auf Debian und Ubuntu liefert 1.3.31, von vor der Existenz dieser API. Wenn Ihre Beispiele plötzlich mit "unresolved reference" die Kompilierung verweigern, prüfen Sie, welcher Compiler tatsächlich auf dem PATH ist.

Best Practices für das Dekodieren

  • Zuerst nach Bytes dekodieren, zweitens interpretieren. Halten Sie Base64.decode und die Textkonvertierung als getrennte Schritte. Das macht den Zeichensatz explizit, hält binäre Nutzlasten binär, und macht Tests trivial: vergleichen Sie Byte-Arrays, keine Strings.
  • Die Instanz wählen, die zum Erzeuger passt. JWT und an URLs gebundene Daten bedeuten UrlSafe; E-Mails und PEM-Dateien bedeuten Mime oder Pem; alles andere startet bei Default. Die nachsichtigen Dekodierer sind für bekannt unordentliche Eingaben, nicht für ein allgemeines Sicherheitsnetz.
  • Unvertraute Eingaben einmal und billig normalisieren. Ein trim() und, wenn das Format bekanntermaßen sauber ist, ein Entfernen von Weißraum, vor einem strengen Decode fängt mehr echte Realwelt-Fehler als jede Menge try-catch. Ein kleiner Helfer mit PRESENT_OPTIONAL-Ausweichweg ist ein gutes Muster für Werte aus unbekannten Quellen.
  • Die Größe vor der Allokation im Budget haben. Die dekodierte Ausgabe ist höchstens drei Viertel der Eingabelänge (vier Symbole tragen drei Bytes), also sagt Ihnen eine schnelle Längenprüfung die Zielgröße, bevor Sie dekodieren - genau das, was Sie wollen, bevor Sie einen vorallozierten Puffer füllen oder eine mehr-Megabyte-große Zeichenfolge akzeptieren.
  • Der Fehlermeldung vertrauen. Die Standardbibliothek berichtet das Symbol, seinen Code und seinen Index. Loggen Sie die Nachbarschaft dieses Indexes für unvertraute Eingaben und hören Sie auf zu raten.
  • Nicht dekodieren, um Dinge zu verstecken, und nicht dekodieren, um Dinge zu beweisen. Base64 ist eine Transport-Kodierung. Es fügt keine Geheimhaltung und keine Integrität hinzu; wenn Sie eines davon brauchen, ist das die Aufgabe der Kryptographie, nicht die des Dekodierers.

Wie Base64 in Kotlin kam

Base64 ist um ein paar Jahrzehnte älter als Kotlin - die MIME-Spezifikation, die die Zeilenregel von 76 Zeichen gab, stammt aus 1993, und das Alphabet selbst aus RFCs der mittleren 1990er - aber die Kotlin-spezifische Geschichte ist kurz und frisch. Das Paket kotlin.io.encoding kam in Kotlin 1.8.20 im April 2023, mit Base64 und drei Instanzen - Default, UrlSafe und Mime - hinter der @ExperimentalEncodingApi-Annotation, zusammen mit den nur-für-JVM-Streaming-Erweiterungen, die heute noch experimentell sind. Zwei Jahre lang bedeutete die Nutzung eine Opt-in-Zeile in jeder Funktion und eine kleine Chance, dass die API umzieht.

Kotlin 2.2.0, veröffentlicht im Juni 2025, änderte den Vertrag. Die ganze API wurde in einem Release stabil, und die Pem-Instanz trat zur Familie hinzu (die RFC-1421-Variante mit 64-Zeichen-Zeilen, die um PKI herum benutzt wird). Die Strenge, die padding-optionalen Code aus der 1.8-Ära nach einem Upgrade Aufmerksamkeit abverlangt, kam einen Schritt früher: in 2.0.20, als withPadding mit seinen vier PaddingOption-Werten das alte feste Verhalten ersetzte und der Dekodierer anfing, Padding zu verlangen. Das 2.2-Release stabilisierte auch die Geschwisterklasse HexFormat in kotlin.text, die Hex-Formatierungs-API, die seit Kotlin 1.9 experimentell war, sodass Textkodierungen auf Byte-Ebene jetzt einen festen Platz in der Standardbibliothek haben. Und eine Wartungsnotiz: Seit Kotlin 2.4.0 gibt es bei der JVM-Standardbibliothek pro Release-Linie ein 18-Monate-Unterstützungsfenster, was ein weiterer Grund ist, warum ein Projekt auf der aktuellen 2.4.x-Linie diese API als festen Punkt statt als beweglichen behandeln kann.

Fun-Fakten

  • Das Companion-Objekt leistet Arbeit. Weil Default das Companion von Base64 ist, dient der Klassenname doppelt als Standardinstanz: Base64.decode(x) und Base64.Default.decode(x) sind derselbe Aufruf. Das ist der Grund, warum das zwei-Zeilen-Beispiel oben in diesem Artikel bei zwei Zeilen bleibt.
  • String-Dekodierung bekommt einen Geschwindigkeits-Trick auf der JVM. Die übliche Dekodier-Schleife arbeitet auf Bytes, aber Kotlin-Strings sind Zeichenfolgen. Die JVM-Implementierung umgeht die Konvertierung, indem sie die Zeichen eines String vor dem Lauf der geteilten Schleife als Ein-Byte-ISO-8859-1-Werte neu interpretiert - ein Trick, von dem die Quellcode-Kommentare behaupten, dass er bis zu zehnmal schneller ist als der übliche Weg, und deshalb wirkt decode(String) selbst auf langen Nutzlasten sofort.
  • Der Paketname ist ein Hinweis. Sie finden diese API in kotlin.io.encoding, nicht in kotlin.text, denn der ganze Punkt ist, dass die Daten Bytes sind - Eingang und Ausgang sind I/O-förmig, und der Text ist nur das, was danach mit dem Ergebnis passiert.
  • Die Fehlermeldungen enthalten den Code des Zeichens. Symbol 'e'(145) berichtet den Wert des Übeltäter-Symbols in Oktal, nicht nur seine Darstellung. Praktisch, wenn der Schuldige Weißraum ist: ' '(40) sagt Ihnen, dass es ein Leerzeichen war, lange bevor Sie eines vermuten.
  • PEM kam spät. Base64.Pem war nicht Teil der ursprünglichen 1.8.20-API; es tauchte mit der 2.2-Stabilisierung auf. Wenn ein Blog-Beitrag aus 2023 oder 2024 nur drei Instanzen auflistet, ist er nicht falsch - er ist nur zwei Releases veraltet.
  • Es ist pro Plattform geschrieben, nicht delegiert. Die Standardbibliothek implementiert den Codec für jedes Ziel separat mit expect/actual-Funktionen. Auf der JVM gibt es sogar eine auskommentierte Optimierung, die die Arbeit an java.util.Base64 abgeben würde, deaktiviert hinter einem offenen Compiler-Issue, und deshalb ist das Verhalten der Kotlin-Implementierung das Referenzverhalten auf jeder Plattform.

Zusammenfassung

Base64 in Kotlin zu dekodieren kommt auf eine kurze Liste bewusster Entscheidungen hinaus: die Instanz, die dazu passt, woher die Daten kamen, der Padding-Modus, der dazu passt, wie sie gesendet wurden, der Puffer oder der Stream, der dazu passt, wie groß sie sind, und der Zeichensatz, der dazu passt, was sie bedeuten. Die Standardbibliothek übergibt Ihnen alle vier als einfache Funktionen ohne Abhängigkeiten, und ihre Fehlermeldungen sind spezifisch genug, dass ein Fehler eine Diagnose ist, kein Rätsel. Die Gegenrichtung - das richtige Schema, das richtige Padding und den richtigen Zeilenumbruch zu wählen, wenn Sie selbst die Base64-Erzeugerseite sind - hat ihre eigenen Entscheidungen und ihre eigenen Fallen, und der verwandte Artikel auf der Schwestersite behandelt Base64-Kodierung in Kotlin im Detail.

Zuletzt aktualisiert: 2026-09-08

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