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

Sie starren auf einen Wert, der sich partout nicht lesen lassen will: SGVsbG8sIFdvcmxkIQ==. Eine Kette aus Buchstaben und Ziffern, hier und da ein + oder / dazwischen, und meist ein oder zwei =-Zeichen, die am Ende baumeln. Das ist Base64, und dieser Leitfaden dreht sich darum, daraus wieder das zu machen, was es einmal war - einen Satz, ein Bild, ein Zertifikat, einen binären Blob - auf die Kotlin-Art. Ein Refresher in einer Zeile, bevor wir eintauchen: Base64 packt jeweils drei Bytes in vier Zeichen aus einem Alphabet mit 64 Symbolen, und ein kurzer Schwanz aus =-Padding markiert, wo die echten Daten aufhörten. Die komplette Format-Tour gibt es auf der Startseite, also widmen wir ihr hier nur einen Satz, und einen zweiten: Weil vier Zeichen das tragen, was drei Bytes trugen, ist die Textform etwa ein Drittel länger als die Originaldaten.

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

Die guten Nachrichten zuerst: Was Sie wirklich brauchen

Es gibt kein base64-Artefakt, das Sie zu Gradle hinzufügen müssten, kein NuGet-ähnliches Paket, kein npm-Modul. Die Klasse, die Sie suchen, ist kotlin.io.encoding.Base64, ein 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 richtige zu wählen ist die erste echte Entscheidung:

API Wo sie läuft Wann Sie nach ihr greifen
kotlin.io.encoding.Base64 Jede Kotlin-Plattform: JVM, Android, JS, Native, Wasm Standardwahl. Stabil seit Kotlin 2.2, Multiplatform, moderne API
java.util.Base64 Nur JVM (Java 8+; auf Android API 26+) JVM-only-Codebasen, die ohnehin im Java-Interop-Land leben
android.util.Base64 Nur Android (API 8+) Legacy-Android-Code, oder wenn Sie gezielt ihre Flaggen-Konstanten brauchen

Zwei Versionsnotizen, die sich zu kennen lohnen. Erstens: Die Standardbibliotheks-Klasse tauchte zum ersten Mal in Kotlin 1.8.20 (April 2023) hinter einem @ExperimentalEncodingApi-Tor auf; 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 fügte die PEM-Instanz hinzu. Somit: Auf Kotlin 2.2 oder neuer - einschließlich der aktuellen stabilen Linie, 2.4.x - können Sie alles aus diesem Leitfaden mit null Annotationen verwenden. Zweitens: Wenn Ihr Projekt eine Kotlin-Version zwischen 1.8 und 2.1 pinnt, existiert dieselbe Klasse, ist aber als experimentell markiert, und der Compiler lässt sie Ihnen nicht ohne eine @OptIn-Annotation auf der Funktion durch.

Eine Installationsfalle, die manchem schon den einen oder anderen Nachmittag gekostet hat: Das kotlin-Paket in den Debian- und Ubuntu-Repositories ist Version 1.3.31, was komplett vor der Base64-API der Standardbibliothek liegt, sodass es nicht ein einziges Beispiel aus diesem Artikel kompilieren kann. Holen Sie den Compiler stattdessen von den Kotlin-Releases auf GitHub oder von SDKMAN, und pinnen Sie in Gradle-Projekten das Plugin ausdrücklich:

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

Ihr erstes Dekodieren: Zwei Zeilen und ein Bytes-Ergebnis

Die gesamte Zeremonie passt in zwei Aussagen, und der klassische TWFu-String ist ein guter Ausgangspunkt:

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 Design-Entscheidungen. Erstens: Base64.decode(...) ohne jedes .Default ist kein Tippfehler: Default ist das Companion-Objekt der Klasse, also ist der Aufruf der Funktion auf der Klasse selbst die Kurzform für den Aufruf auf Base64.Default. In älteren Tutorials sehen Sie auch Base64.Default.decode(...), und es bedeutet genau dasselbe. Zweitens, und das ist wichtiger, als es aussieht: decode übergibt Ihnen ein ByteArray, nie einen String. Das Payload könnte ein JPEG, ein X.509-Zertifikat oder ein Satz sein, und die API weigert sich zu raten, welches es ist, also ist der Sprung von Bytes zu Text ein eigener, bewusster Schritt. Drittens: Genau in diesem Schritt wohnt die Zeichensatz-Entscheidung, und hier entstehen die meisten "Mein Base64 kam als Müll zurück"-Bugs. Wir kommen in einem Moment dazu; zuerst ein Roundtrip, der beweist, dass das Dekodieren 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 instanziiert; Sie wählen eine von vier fertigen 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 + / Strikt: wirft bei jedem Zeichen außerhalb des Alphabets; Padding ist erforderlich
Base64.UrlSafe A-Z a-z 0-9 - _ Strikt, 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

Diesen Strikt/Nachsichtig-Split sollten Sie sich mehr als alles andere einprägen. Default und UrlSafe behandeln jedes fremde Zeichen wie einen Tatort und werfen sofort. Mime und Pem zucken nur mit den Schultern über Zeilenumbrüche, Leerzeichen und verirrte Interpunktion - denn genau das enthält echte E-Mail- und Zertifikatsdateien - aber sie sind auch nicht grenzenlos: Sobald ein Datenzeichen nach dem Padding auftaucht, werfen sogar sie. Die exakten Fehlermeldungen sehen Sie im Feldführer für Dekodierfehler später in diesem Artikel.

Eine weitere Folge dieser Persönlichkeiten: Ein Schema kann die Ausgabe eines anderen nicht lesen. Füttern Sie Base64.Default einen base64url-Token, und das --Zeichen ist nicht in dessen Alphabet, also bekommen Sie IllegalArgumentException: Invalid symbol '-'(55) at index .... Im Zweifel über die Herkunft eines Strings wählen Sie das Schema, das zum Erzeuger passt, nicht das, das zu Ihrer Stimmung passt.

URL-sicheres Base64 und JWTs

Zwei Zeichen im Standardalphabet machen sofort Ärger, wenn Daten durch eine URL reisen müssen. Ein + in einem Query-String wird, bis irgendetwas sie liest, routinemäßig als Leerzeichen neu interpretiert, und / ist ein Pfadtrenner, also darf es in einem URL-Segment gar nicht vorkommen. RFC 4648, Abschnitt 5, löst das, indem er die letzten zwei Alphabet-Symbole tauscht: + wird - und / wird _. 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 das JSON Web Token. Ein JWT in seiner kompakten Form ist drei base64url-Teile, verbunden durch Punkte: header.payload.signature. RFC 7515 spezifiziert diese Teile als base64url ohne Padding, was ein zweiter Unterschied zum einfachen Alphabet ist, nicht nur die Zeichen. Hier wird ein Token zum Hinsehen auseinandergenommen:

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 zu beachten. Die Token-Teile tragen kein Padding, aber Base64.UrlSafe verlangt standardmäßig Padding, also leistet die withPadding(PRESENT_OPTIONAL)-Zeile echte Arbeit: Sie akzeptiert gepaddete und ungepaddete Eingaben gleichermaßen. Und das split(".") plus Destrukturierung ist einfach reines Kotlin, das tut, was das Format verlangt. Eine ernste Warnung: Das Aufmachen eines JWTs dient dazu, ein Token anzuschauen, nicht dazu, ihm zu vertrauen. Header und Payload sind nach dem Dekodieren einfache Daten; nur eine verifizierte Signatur bestätigt, dass das Token echt ist, und dafür brauchen Sie eine echte JWT-Bibliothek, kein von Hand gerolltes String-Splitting.

Padding-Modi und der Strenge-Regler

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

Option Eingabe ohne Padding Eingabe mit korrektem Padding
PRESENT (überall Standard) 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 Dekodierungsseite ist PRESENT_OPTIONAL Ihr Sicherheitsnetz: Es ist die Option, die sagt: "Ich weiß nicht, ob der Sender gepadded hat, und ich gedenke weiterzuarbeiten." Die Fehlermeldungen der anderen Kombinationen sind ungewöhnlich hilfreich, also erkennen Sie sie sofort, wenn ein strikter Decoder auf die falsche Eingabe trifft: Fehlendes Padding unter PRESENT erzeugt The padding option is set to PRESENT, but the input is not properly padded, und Padding unter ABSENT erzeugt The padding option is set to ABSENT, but the input has a pad character at index 7. Ein Verhalten verdient einen Hinweis, weil es die Leute überrascht: Ein doppeltes Padding wie SGVsbG8== ist nicht "zusätzlich, aber in Ordnung". Das erste = beendet die Daten, und das zweite ist ein Zeichen an der Stelle, an der Daten erwartet wurden, also lehnen selbst die nachsichtigsten Decoder es ab.

Hier steckt 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 gewechselt, also wirft eine einmal funktionierende ungepaddete Eingabe beim Upgrade über diese Punkt-Version hinaus aus. Die Korrektur ist eine Zeile: withPadding(Base64.PaddingOption.PRESENT_OPTIONAL), oder normalisieren Sie Ihre Eingaben vor dem Dekodieren.

Von Bytes zu Text: Zeichensätze und Unicode

Sobald Sie das ByteArray haben, ist die Frage, was es bedeutet. Wenn das Payload Text ist, lautet die Standardantwort decodeToString(), das die Bytes als UTF-8 interpretiert und auf jeder Plattform funktioniert. Für den üblichen Fall von modernen APIs, E-Mails und Webdaten ist das alles, was Sie je brauchen werden, und Emojis sind auch dabei:

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 😀
}

Im Moment allerdings, in dem der Sender irgendetwas anderes als UTF-8 verwendet hat, liegt die Zeichensatz-Entscheidung bei Ihnen. Kotlins eingebaute Textumwandlungen sind absichtlich nur UTF-8: decodeToString() hat keinen Zeichensatz-Parameter, und es gibt auch keine Zeichen-zu-Bytes-Funktion mit einem. Auf der JVM steigen Sie auf die 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 Schriftart-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 Ihr Payload in Ordnung - Ihre Zeichensatz-Annahme nicht. Beachten Sie auch die Asymmetrie, die Leute beißt: Auf der Kodierungsseite existiert die JVM-Extension toByteArray(charset), auf der Dekodierungsseite ist der passende Konstruktor String(bytes, charset). Keiner nimmt einen Zeichensatz-namen; dafür brauchen Sie Charset.forName("..."), das für erfundene Namen UnsupportedCharsetException wirft, also schlägt ein Tippfehler in einem Konfigurationswert schnell fehl, statt still eine andere Kodierung zu wählen.

Da wir sowieso im Byte-Land sind, eine Kotlin-spezifische Falle: Ein Char ist ein 16-Bit-Wert, und toByte() darauf behält stillschweigend nur die unteren acht Bits bei. Wenn Sie Bytes von Hand aus Zeichen basteln, liefert "中".first().code.toByte() 45, eine Zahl, die mit dem Zeichen nichts zu tun hat. Der richtige Weg ist immer encodeToByteArray(), das die echte Kodierungsarbeit erledigt - dasselbe Zeichen ist drei UTF-8-Bytes, und seine Base64-Form ist 5Lit. Lassen Sie die Standardbibliothek kodieren; packen Sie niemals von Hand Zeichen in Bytes.

Dateien, Teilstrings und große Eingaben

Base64-Daten sind nicht immer ein sauberer String im Speicher. Manchmal ist es eine Datei, ein Ausschnitt aus 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, welche auch immer Ihr Projekt schon 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
}

Teilstrings sind der Ort, an dem die CharSequence-Overloads sich bezahlt machen. decode akzeptiert jede Zeichenfolge mit Start- und Endindex, also können Sie ihm einen Ausschnitt aus einem langen Antwortkörper übergeben, ohne zuerst eine Kopie des Ausschnitts anzulegen:

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 Buffer wiederverwenden wollen, schreibt decodeIntoByteArray in ein Ziel-Array Ihrer Wahl und sagt Ihnen, wie viele Bytes es geschrieben hat. Geben Sie ihm einen zu kleinen Buffer, und es wirft IndexOutOfBoundsException mit der nötigen Kapazität in der Meldung, sodass der Fehler gleich als Größen-Hinweis dient.

Für wirklich große Streams auf der JVM gibt es eine dritte Tür: die Streaming-Dekoder. Sie sind immer noch als experimentell markiert - daher die Opt-in-Annotation - und 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 Extension-Funktionen leben auf der obersten Ebene des Pakets, also importieren Sie sie beim Namen (ein Stern-Import funktioniert auch, aber Namen sind freundlicher). Und der Dekoder behandelt das Padding als harten Stopp: Wenn der zugrunde liegende Stream nach dem Base64-Abschnitt weitergeht, endet das Lesen aus dem dekodierten Stream beim =, und die übrig gebliebenen Bytes bleiben im Original-Stream verfügbar. Das macht es sauber für Formate, die Base64 vor irgendetwas anderes tackern.

Im Schützengraben: HTTP-APIs und JSON-Bodies

JSON kann keine rohen Bytes tragen - es ist ein Textprotokoll - also wickeln APIs, die Binärdaten bewegen müssen (Bilder, Zertifikate, beliebige Blobs), sie fast immer in Base64 ein und legen sie in ein String-Feld. Das Muster ist: JSON parsen, das Feld nehmen, dekodieren. Mit der offiziellen Serialisierungsbibliothek ist der JSON-Teil nur 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, einmal zum 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 dem rohen String, was für schnelle Skripte praktisch ist: Ziehen Sie das Feld mit substringBetween heraus und dekodieren Sie es. Die Fallen sind die vertrauten API-Fallen: Das Feld könnte tatsächlich eine vollständige Data-URL sein (mit dem data:image/png;base64,-Präfix, das später in diesem Artikel behandelt wird), das Payload könnte MIME-mäßig mit Zeilenumbrüchen umwickelt sein, und das kodierte Payload kann etwa ein Drittel größer sein als die ursprünglichen Binärdaten, also achten Sie bei großen Antworten auf Ihr Speicher-Budget.

Im Schützengraben: E-Mail und MIME-umwickelte Eingaben

E-Mail ist eine 7-Bit-Textwelt, und die Antwort von RFC 2045 auf binäre Anhänge ist Base64 mit einem Kniff: die kodierte Ausgabe muss umgebrochen werden, damit keine Zeile länger als 76 Zeichen wird. Wenn Sie schon einmal einen Anhang als Text erhalten haben, wissen Sie, warum er wie eine eingerückte Spalte aus Base64 aussieht. Für genau diese Eingabe ist Base64.Mime der richtige Dekoder, denn es 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 Nachsichtigkeit ist real, aber begrenzt. Wickeln Sie die Eingabe um, streuen Sie ein, zwei Leerzeichen ein, kein Problem. Hängen Sie aber ein Datenzeichen nach dem letzten = an, und sogar Mime wirft: Symbol 'e'(145) at index 7 is prohibited after the pad character. Und denken Sie daran, dass Mime nach wie vor verlangt, dass das Padding vorhanden und korrekt ist. Ein MIME-Dekoder, der auch fehlendes Padding verschluckt, würde sich Ärger einhandeln. Das praktische Rezept für unordentliche eingehende E-Mail-Payloads lautet: erst mit Mime dekodieren, und wenn das wirft, die Meldung lesen - sie sagt Ihnen genau, welches Symbol an welchem Index die Regeln gebrochen hat.

Im Schützengraben: Bilder und Data-URLs

Eine Data-URL ist die Art und Weise des Webs, eine Datei direkt in ein Dokument einzubetten: ein Medientyp, eine base64,-Markierung und das Payload, alles in einem String. Browser, CSS und eingebettete UIs lieben sie für kleine Assets - Icons, Avatare, Platzhalter-Grafiken - denn es ist keine zweite Anfrage nötig. Das Format sieht so aus:

data:image/png;base64,iVBORw0KGgoAAAANSUhEUg==

Das Dekodieren einer davon in Kotlin ist eine Zeichenkettenoperation, gefolgt von einem Base64-Dekodieren. Das Präfix trägt kein Geheimnis; alles nach dem Komma ist das Payload:

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, ...]
}

Das 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, zu bestätigen, dass eine Data-URL wirklich das enthält, was ihr Präfix behauptet. Zwei ehrliche Einschränkungen: Base64 fügt etwa ein Drittel zur Größe hinzu, also ist eine Data-URL ein Größen-Kompromiss, den Sie gegen einen Netzwerk-Roundtrip eintauschen, und für alles Große lohnt es sich meist, die Datei von einer echten URL auszuliefern und den Cache seine Arbeit tun zu lassen.

Im Schützengraben: Konfiguration, Umgebungsvariablen und Datenbanken

Base64 taucht in Konfigurationsdateien und Umgebungsvariablen auf, wann immer ein binärer Wert durch einen reinen Textkanal reisen muss: ein kleines eingebettetes Icon in einer Properties-Datei, ein Token, das in einer Umgebungsvariablen auf einem Container gespeichert ist, ein Byte-Blob, der in einer Textspalte parkt, weil das Schema vor der Einführung eines echten binären Typs lag. Die Dekodierseite ist überall dieselben zwei Schritte - den Text lesen, ihn 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 Falle in dieser ganzen Gegend 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 seien verborgen; er ist einen Funktionsaufruf von sichtbar entfernt, und er ist in jeder Zeile sichtbar, die Sie in ein Log 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 nutzen Sie Base64 nur, damit die Bytes durch Text reisen, nicht um sie zu schützen.

Im Schützengraben: Die Kommandozeile

Der älteste Anwendungsfall von allen: einen Base64-Blob auf der Kommandozeile in eine Datei zu verwandeln. Ein komplettes Werkzeug sind acht Zeilen Kotlin, weil die Standardbibliothek die schwere Arbeit macht. 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 pipen Sie eine Datei hinein für Stapelarbeit: Das Programm liest das erste Argument, wenn vorhanden, und fällt sonst auf die Standardeingabe zurück. Das trim() leistet hier stillen Dienst, weil Shell-Argumente und eingefügte Werte gern mit ungewolltem Leerraum ankommen, den der strikte Dekoder ablehnen würde. Und wenn Ihre Payloads base64url sind, tauschen Sie Base64.decode gegen Base64.UrlSafe.withPadding(Base64.PaddingOption.PRESENT_OPTIONAL).decode aus, und das Werkzeug ist auch für Tokens bereit.

Ein Feldführer für Dekodierfehler

Jeder Dekoder in diesem Leitfaden schlägt mit einem von zwei Ausnahmetypen fehl, und jede Meldung ist spezifisch genug, um Ihnen genau zu sagen, was schiefgegangen ist. Hier ist die komplette Landkarte, mit den exakten Meldungen, die die Standardbibliothek produziert:

Situation Ausnahme Meldung (so wie sie erzeugt wird)
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
Ziel-Buffer 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 benennt das übertretende Symbol, seinen numerischen Code in Klammern und seinen Index. Das ist ein Debugging-Geschenk. Wenn ein Dekodieren in Produktion wirft, loggen Sie die ersten paar Dutzend Zeichen der Eingabe und den Index aus der Meldung, und Sie finden den Übeltäter fast immer in Sekunden, egal ob es ein eingefügter Zeilenumbruch ist, ein abgeschnittenes Payload oder ein base64url-String, der in einen Standard-Dekoder verirrt ist.

Fallen, die Kotlin-Entwickler besonders beißen

  • Das Payload für Text halten. decode gibt absichtlich ein ByteArray zurück. decodeToString() auf ein JPEG aufzurufen, weil es "wohl Text sein muss", gibt Ihnen eine Wand aus Ersetzungszeichen. Entscheiden Sie, was die Bytes sind, bevor Sie sie umwandeln.
  • Kopierter Leerraum. Der Standard-Dekoder ist strikt, und ein Wert, der aus einer Chat-Nachricht oder einem Log gerettet wurde, 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 experimentellen Ära. Code, der für die experimentelle 1.8.x-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 Korrektur ist PRESENT_OPTIONAL, oder die Eingaben zu bereinigen, bevor sie den Dekoder erreichen.
  • Das Schema auf den falschen Erzeuger ausrichten. Ein JWT-Teil, dekodiert mit Base64.Default, scheitert an seinen -- und _-Zeichen; ein Standard-Alphabet-Payload, dekodiert mit UrlSafe, scheitert an + und /. Die Ausnahme benennt das exakte Symbol, aber die Korrektur ist zu wissen, woher der String kommt.
  • Die Zeichensatz-Lücke. decodeToString() ist nur UTF-8, ohne Overload für andere Kodierungen. Wenn der Sender Latin-1 oder Windows-1252 verwendet hat, planen Sie String(bytes, charset) auf der JVM ein, und erwarten Sie U+FFFD-Ersetzungszeichen als Symptom, wenn Sie es vergessen.
  • Der Distribution-Paket-Compiler. apt install kotlin auf Debian und Ubuntu serviert 1.3.31, aus einer Zeit vor 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ürs Dekodieren

  • Erst zu Bytes dekodieren, zweitens interpretieren. Halten Sie Base64.decode und die Textumwandlung als separate Schritte. Das macht den Zeichensatz explizit, hält binäre Payloads binär und macht Tests trivial: vergleichen Sie Byte-Arrays, keine Strings.
  • Wählen Sie die Instanz, die zum Erzeuger passt. JWT und URL-gebundene Daten bedeuten UrlSafe; E-Mail und PEM-Dateien bedeuten Mime oder Pem; alles andere startet bei Default. Die nachsichtigen Dekoder sind für bekannte unordentliche Eingaben da, nicht als allgemeines Sicherheitsnetz.
  • Unvertraute Eingabe einmal normalisieren, günstig. Ein trim() und, wo das Format bekanntermaßen sauber ist, ein Entfernen des Leerraums vor einem strikten Dekodieren fängt mehr reale Fehler ab als jede Menge try-catch. Ein kleiner Helfer mit PRESENT_OPTIONAL-Fallback ist ein gutes Muster für Werte aus unbekannten Quellen.
  • Budgetieren Sie die Größe, bevor Sie allozieren. Dekodierte Ausgabe ist höchstens drei Viertel der Eingabelänge (vier Symbole tragen drei Bytes), also sagt ein schneller Längencheck Ihnen die Zielgröße, bevor Sie dekodieren, was genau das ist, was Sie vor dem Befüllen eines vorallozierten Buffers oder dem Akzeptieren eines mehr-Megabyte-Strings wollen.
  • Vertrauen Sie der Fehlermeldung. Die Standardbibliothek meldet das Symbol, seinen Code und seinen Index. Loggen Sie die Umgebung dieses Indexes für unvertraute Eingabe und hören Sie auf zu raten.
  • Dekodieren Sie nicht zum Verstecken, und dekodieren Sie nicht zum Beweisen. Base64 ist eine Transport-Kodierung. Sie fügt weder Geheimhaltung noch Integrität hinzu; wenn Sie eines davon brauchen, ist das die Aufgabe der Kryptographie, nicht die des Dekoders.

Wie Base64 in Kotlin kam

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

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 verwendet wird). Die Strenge, die Code aus der 1.8-Ära, in der Padding optional war, nach einem Upgrade Aufmerksamkeit kostet, kam einen Schritt früher: in 2.0.20, als withPadding mit seinen vier PaddingOption-Werten das alte feste Verhalten ersetzte und der Dekoder begann, Padding zu verlangen. Das 2.2-Release stabilisierte auch die Schwesterklasse HexFormat in kotlin.text, die Hex-Formatierungs-API, die seit Kotlin 1.9 experimentell war, sodass textuelle Kodierungen auf Byte-Ebene jetzt einen festen Wohnsitz in der Standardbibliothek haben. Und ein Pflege-Hinweis: Seit Kotlin 2.4.0 liefert die JVM-Standardbibliothek ein 18-Monate-Unterstützungsfenster pro Release-Linie aus, was ein weiterer Grund ist, warum ein Projekt auf der aktuellen 2.4.x-Linie diese API als festen Punkt behandeln kann, nicht als einen sich bewegenden.

Fun Facts

  • Das Companion-Objekt hat eine Aufgabe. Da Default das Companion von Base64 ist, dient der Klassenname auch als Standard-Instanz: 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 auf der JVM einen Geschwindigkeits-Trick. Die übliche Dekodier-Schleife arbeitet auf Bytes, aber Kotlin-Strings sind Zeichenfolgen. Die JVM-Implementation umgeht die Umwandlung, indem sie die Zeichen eines Strings, bevor die gemeinsame Schleife läuft, als einbytegroße ISO-8859-1-Werte neu interpretiert - laut Quellcode-Kommentaren bis zu zehnmal schneller als der gewöhnliche Pfad, und genau deshalb fühlt sich decode(String) auch auf langen Payloads sofort an.
  • Der Paketname ist ein Hinweis. Diese API finden Sie in kotlin.io.encoding, nicht in kotlin.text, denn der ganze Punkt ist, dass die Daten Bytes sind - Eingabe und Ausgabe haben I/O-Form, und der Text ist nur das, was danach mit dem Ergebnis passiert.
  • Die Fehlermeldungen enthalten den Code des Zeichens. Symbol 'e'(145) meldet den Wert des übertretenden Symbols in Oktal, nicht nur seine Glyphe. Praktisch, wenn der Übeltäter Leerraum 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 erschien mit der 2.2-Stabilisierung. Wenn ein Blog-Post 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 mit expect/actual-Funktionen separat für jedes Ziel. Auf der JVM gibt es sogar eine auskommentierte Optimierung, die die Arbeit an java.util.Base64 übergeben würde, deaktiviert hinter einem offenen Compiler-Problem, deshalb ist das Verhalten der Kotlin-Implementation das Referenzverhalten auf jeder Plattform.

Zum Abschluss

Base64 in Kotlin zu dekodieren kommt auf eine kurze Liste bewusster Entscheidungen an: die Instanz, die dazu passt, woher die Daten kamen, der Padding-Modus, der dazu passt, wie sie gesendet wurden, der Buffer oder Stream, der dazu passt, wie groß sie sind, und der Zeichensatz, der dazu passt, was sie bedeuten. Die Standardbibliothek gibt Ihnen alle vier als gewöhnliche Funktionen ohne Abhängigkeiten, und ihre Fehlermeldungen sind spezifisch genug, dass ein Fehler eine Diagnose ist, nicht ein Rätsel. Die entgegengesetzte Richtung - das richtige Schema, das passende Padding und den passenden Zeilenumbruch zu wählen, wenn Sie selbst das Base64 produzieren - hat ihre eigenen Entscheidungen und ihre eigenen Fallen, und der verwandte Artikel auf der Schwesternsite behandelt die Base64-Kodierung in Kotlin ausführlich.

Zuletzt aktualisiert: 2026-09-08

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