Base64-Kodierung in Kotlin: Ein vollständiger Leitfaden
Die Hälfte jedes Base64-Gesprächs dreht sich darum, gepackte Daten wieder zurück in Bytes zu lesen. Die andere Hälfte, der Teil, für den Sie auf der richtigen Site sind, dreht sich darum, diese gepackten Daten überhaupt erst zu erzeugen. Irgendwo in Ihrer Anwendung gibt es Bytes, die einen Kanal nehmen müssen, der nur Text versteht: einen JSON-String, einen HTTP-Header, eine E-Mail, eine URL, eine Konfigurationsdatei. Base64 ist die klassische Antwort, und Kotlin hat dafür in der Standardbibliothek eine erstklassige Antwort: die Base64-Klasse in kotlin.io.encoding, stabil seit Kotlin 2.2.
Dieser Leitfaden geht durch, was Sie wirklich brauchen, wenn Sie selbst Base64 erzeugen: die vier vorgefertigten Schemata, den Padding-Regler, die Zeilenumbruch-Regeln, nach denen E-Mails und Zertifikate leben, das URL-sichere Alphabet, woher die Bytes kommen, und eine Reihe realer Szenarien, in jedem davon mit Kotlin. Das Format selbst, wie aus 3 Bytes 4 Zeichen werden, woher das = kommt, ist auf der Startseite abgedeckt, also gehen wir hier direkt zum Kotlin.
Eine Klasse, vier Presets
Die gesamte API ist eine einzelne Klasse, Base64, im Paket kotlin.io.encoding. Es gibt kein encoder-Objekt, das Sie konstruieren, und keinen Builder. Stattdessen kommt die Klasse mit vier vorgefertigten Instanzen, je eine pro RFC-Schema, und einem Companion-Objekt, das still und leise für das häufigste einsteht:
| Instanz | Alphabet | Zeilenumbruch beim Kodieren | Padding beim Kodieren | Dafür einsetzen |
|---|---|---|---|---|
Base64.Default | + und / | kein | emittiert = | Allzweck, APIs, Data URLs |
Base64.UrlSafe | - und _ | kein | emittiert = (abschaltbar) | URLs, Tokens, JWTs |
Base64.Mime | + und / | CRLF alle 76 Zeichen | emittiert = | E-Mail-Körper und Anhänge |
Base64.Pem | + und / | CRLF alle 64 Zeichen | emittiert = | Zertifikate und private Schlüssel |
Das Naming-Detail, über das Menschen stolpern: Das sind Instanzen, keine Fabriken. Jede Instanz ist ein unveränderlicher Wert, und eine Verhaltensänderung, etwa das Padding, liefert eine neue Instanz statt die alte zu mutieren. Das macht die Presets sicher zum Teilen über Threads hinweg und zum Speichern in Objekten, und genau deshalb kann die ganze Klasse ein einfacher Wertetyp ohne internen Zustand sein.
Ihr erstes Encode: Bytes rein, String raus
Hier ist das kleinste nützliche Programm auf der Site: fünf Bytes rein, eine Zeichenfolge mit acht Zeichen raus. Die Eingabe ist immer ein ByteArray (oder ein Ausschnitt davon), und das Ergebnis ist ein gewöhnlicher String, den Sie überall dort hinsetzen können, wo Text erlaubt ist:
import kotlin.io.encoding.Base64
fun main() {
val bytes = "Hello".encodeToByteArray()
val packed = Base64.encode(bytes)
println(packed) // SGVsbG8=
}
Diese eine Zeile leistet mehr Arbeit, als sie aussieht. Kotlin gibt Ihnen mehrere Formen derselben Operation, und sie alle lesen sich, wie die Funktion es sagt:
encode(bytes)liefert einenStringzurück, die Form oben.encodeToByteArray(bytes)liefert einByteArrayaus ASCII-Zeichen zurück, praktisch, wenn die gepackte Form selbst in einen anderen Puffer geht.encodeIntoByteArray(bytes, destination)schreibt in einByteArray, das Sie bereits alloziert haben, was auf heißen Pfaden eine Allokation spart.encodeToAppendable(bytes, builder)hängt an alles an, wasAppendableimplementiert, wie einenStringBuilder, was der natürliche Fall ist, wenn Sie ein größeres Dokument zusammenstellen.
Alle vier akzeptieren denselben optionalen startIndex- und endIndex-Bereich, also können Sie einen Ausschnitt eines großen Puffers packen, ohne ihn vorher zu kopieren. Weil Base64.Default das Companion-Objekt ist, können Sie die Instanz auch weglassen und Base64.encode(bytes) als Zucker schreiben; beide Formen sind derselbe Aufruf.
Die 4/3-Regel: Wie lang wird es?
Bevor Sie einen Encoder ausliefern, lohnt es sich zu wissen, genau wie viel größer die Ausgabe sein wird, denn Base64 gibt Zeichen für Informationen aus, die es schon hatte. Die Mathematik ist streng: Jede drei Eingabe-Bytes ergeben exakt 4 Ausgabe-Zeichen, also verbrauchen auch verbleibende 1 oder 2 Bytes eine volle Gruppe von 4, aufgefüllt mit =, um die Gruppe zu füllen. Das Ergebnis für die ersten paar Größen:
| Eingabe-Bytes | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|---|---|---|---|---|---|---|---|
| Ausgabe-Zeichen | 4 | 4 | 4 | 8 | 8 | 8 | 12 | 12 |
Die Formel hinter der Tabelle ist 4 * ceil(bytes / 3). Im schlechtesten Fall werden aus einem einzelnen Byte 4 Zeichen, ein 300-Prozent-Aufschlag; ab drei Bytes konvergiert es auf etwa ein Drittel mehr Daten auf dem Draht. Das ist das gesamte Kostenmodell; es gibt keine pro-Instanz-Variation, und genau deshalb sollten Sie Base64 für große Nutzlasten bewusst einsetzen, statt reflexartig danach zu greifen.
Padding ist eine Einstellung, kein Schicksal
Auf der Kodier-Seite sind die =-Zeichen eine bewusste Entscheidung, und Kotlin macht sie zu einer Entscheidung erster Klasse. Jede Instanz trägt eine PaddingOption, und withPadding übergibt Ihnen eine neue Instanz mit dem Regler an einer anderen Position. Alle vier Presets starten auf PRESENT, deshalb kommt aus "Hello" ein SGVsbG8= statt SGVsbG8:
import kotlin.io.encoding.Base64
fun main() {
val bytes = "Hello".encodeToByteArray()
val noPad = Base64.Default.withPadding(Base64.PaddingOption.ABSENT)
println(Base64.encode(bytes)) // SGVsbG8=
println(noPad.encode(bytes)) // SGVsbG8
}
Es gibt vier Positionen am Regler. Das erste Wort des Namens entscheidet, was der Encoder ausgibt; die zweite Hälfte entscheidet, wie streng der Dekodierer derselben Instanz sein wird, wenn Sie (oder die andere Seite) ihn später umdreht:
| PaddingOption | Encoder emittiert = | Dekodierer akzeptiert = |
|---|---|---|
PRESENT | ja | erforderlich, alles andere schlägt fehl |
ABSENT | nein | verboten, ein überflüssiges Pad schlägt fehl |
PRESENT_OPTIONAL | ja | beides |
ABSENT_OPTIONAL | nein | beides |
Die häufigste Entscheidung zur Kodierzeit ist ABSENT mit dem UrlSafe-Alphabet, was exakt die Form ist, die JSON Web Tokens und viele URL-Schemata erwarten. Sie treffen sie gleich wieder.
Base64url: URLs, Tokens und JWTs
Das klassische Alphabet enthält + und /, und beide sind in URLs ein Desaster: Ein + im Query-String wird routinemäßig als Leerzeichen gelesen, und ein / ist der Pfadtrenner. RFC 4648, Abschnitt 5, definiert die URL-sichere Variante, in der - und _ an deren Stelle stehen, und Base64.UrlSafe ist dieses Schema. Das Kodieren derselben Bytes, die im klassischen Alphabet ein / erzeugen, zeigt den Tausch in Aktion:
import kotlin.io.encoding.Base64
fun main() {
val bytes = "Hello?".encodeToByteArray()
println(Base64.encode(bytes)) // SGVsbG8/
println(Base64.UrlSafe.encode(bytes)) // SGVsbG8_
}
Der maßgebliche Nutzer in der Praxis ist ein JWT, dessen Header und Payload base64url ohne Padding sind, verbunden durch Punkte. Hier ist die Kodier-Hälfte beim Bau eines solchen, eine Form, die Sie verstehen sollten, selbst wenn eine Bibliothek das finale Token signiert:
import kotlin.io.encoding.Base64
fun main() {
val header = """{"alg":"HS256","typ":"JWT"}"""
val payload = """{"sub":"1234567890","name":"John Doe"}"""
val noPad = Base64.UrlSafe.withPadding(Base64.PaddingOption.ABSENT)
val h = noPad.encode(header.encodeToByteArray())
val p = noPad.encode(payload.encodeToByteArray())
val token = "$h.$p.Ym9nVXNlZlNpZ25hdHVyZUZvckRlbW8"
println(token)
}
Ausgabe:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.Ym9nVXNlZlNpZ25hdHVyZUZvckRlbW8
Zwei Warnungen gehören hierher. Erstens: Das dritte Segment ist eine Signatur, und um eine echte zu erzeugen, braucht es echte Kryptographie (ein JCA/JCE-Signer oder eine JWT-Bibliothek), niemals von Hand zusammengebastelte Bytes; das obige Snippet demonstriert nur die Kodier-Form. Zweitens: Wenn Sie auf der JVM sind und aus Gewohnheit zu java.util.Base64.getUrlEncoder() greifen, beachten Sie, dass er standardmäßig Padding ausgibt, also braucht JWT-artige Ausgabe dort .withoutPadding(); das Kotlin-Preset gibt ab Werk ebenso lautstark Padding aus, und Sie opten mit einem withPadding-Aufruf aus.
Zeilenumbruch: Die Mime- und Pem-Presets
Zwei der vier Presets brechen ihre Ausgabe in kurze Zeilen um, und der Grund ist historisch. Alte E-Mail-Transporte versemmelten lange Zeilen, daher begrenzt RFC 2045, Abschnitt 6.8, MIME-Base64 auf 76 Zeichen pro Zeile; PKI-Tools, der älteren PEM-Tradition folgend, verwenden 64. Kotlin backt beide Regeln in das Preset selbst ein: Der Zeilentrenner ist CRLF, der Bruch landet exakt an der Grenze, und am allerletzten Ende gibt es keinen Trenner mehr. Eine 200-Byte-Nutzlast durch jeden Wrapper sieht so aus:
import kotlin.io.encoding.Base64
fun main() {
val data = ByteArray(200) { (it % 251).toByte() }
println(Base64.Mime.encode(data).lines().maxOf { it.length }) // 76
println(Base64.Pem.encode(data).lines().maxOf { it.length }) // 64
}
Für diese Eingabe produziert Mime 4 Zeilen und Pem 5. Die erste Mime-Zeile ist:
AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4
Die Falle, die man sich merken sollte, ist die Gegenrichtung zu der, für die Sie gerade Code schreiben: Umgebrochene Ausgabe ist keine einzelne Zeile. Wenn Sie Mime-Ausgabe an einen strengen Single-Line-Verbraucher füttern, werden die CRLFs zu einem Dekodierfehler, also wählen Sie den Umbruch nach Kanal, nicht nach Bequemlichkeit. Für APIs, Data URLs und alles Moderne ist Default der richtige Standard, und der Umbruch ist eine Sache der E-Mail und der Zertifikate.
Woher kommen die Bytes?
Ein Encoder ist nur so ehrlich, wie die Bytes, die Sie ihm übergeben, und die interessanten Entscheidungen fallen einen Schritt vorher, bevor encode aufgerufen wird. Die häufigste Quelle ist Text, und der häufigste Fehler ist, den Zeichensatz still und leise entscheiden zu lassen:
text.encodeToByteArray()ist auf jeder Plattform immer UTF-8. Für JSON, E-Mails und Web-Daten ist es die richtige Wahl, und die falsche, wenn der Text Latin-1 oder UTF-16 ist und die andere Seite entsprechend dekodiert.- Auf der JVM können Sie mit der Inline-Erweiterung
text.toByteArray(charset)explizit wählen, die seit Kotlin 1.0 in der Standardbibliothek ist und die Kotlin-seitige Antwort auf JavasgetBytes(charset)ist. Aufkotlin.Stringgibt es keingetBytes, also wird Ihnen der Compiler das sagen, wenn Sietext.getBytes()auf einem Kotlin-String schreiben; die Erweiterung ist der Weg.
import kotlin.io.encoding.Base64
fun main() {
val text = "héllo"
println(Base64.encode(text.encodeToByteArray())) // aMOpbGxv
println(Base64.encode(text.toByteArray(Charsets.ISO_8859_1))) // aOlsbG8=
}
Dieselben fünf Buchstaben, zwei verschiedene gepackte Formen, weil die Bytes anders waren, bevor überhaupt Base64 ins Spiel kam. Wenn der Dekodierer später UTF-8 annimmt, dekodiert die Latin-1-Version zu Mojibake, und kein Base64-Trick auf irgendeiner Seite kann eine Zeichensatz-Fehljustierung reparieren.
Andere Quellen von Bytes folgen derselben Form. Eine Datei ist file.readBytes() oder path.readBytes(), dann encode. Ein vorallozierter Puffer nutzt encodeIntoByteArray(bytes, destination). Ein Dokument im Bau nutzt encodeToAppendable(bytes, builder), das das Ziel zurückgibt, damit Aufrufe wie Builder-Methoden kaskadieren:
import kotlin.io.encoding.Base64
fun main() {
val sb = StringBuilder("prefix-")
Base64.encodeToAppendable("Hello".encodeToByteArray(), sb)
println(sb) // prefix-SGVsbG8=
}
Und auf der JVM gibt es eine Streaming-Form für Eingaben, die nicht in den Speicher passen, weiterhin als experimentell markiert und unter ihrem eigenen Namen importierbar. Der Twist im Naming: encodingWith wickelt einen Ausgabe-Stream ein, also kommen Schreibvorgänge durch ihn als Base64 raus, und die Base64-Bytes landen im zugrunde liegenden Stream:
import java.io.ByteArrayOutputStream
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi
import kotlin.io.encoding.encodingWith
@OptIn(ExperimentalEncodingApi::class)
fun main() {
val raw = ByteArray(10_000) { (it % 251).toByte() }
val packed = ByteArrayOutputStream()
packed.encodingWith(Base64.Default).use { encoded ->
encoded.write(raw)
}
println(packed.size()) // 13336
}
Daumenregel: in-memory encode für alles, was passt, encodingWith für die Ströme, die nicht passen, und ein expliziter Zeichensatz, wann immer die Bytes tatsächlich Text sind.
Feldnotizen: HTTP Basic Auth
HTTP-Basic-Authentifizierung ist der älteste Base64-Anwendungsfall im Internet, und sie ist in Service-zu-Service-Verkehr noch überall. RFC 7617 definiert das Schema: Benutzer und Passwort nehmen, mit einem einzelnen Doppelpunkt verbinden, das Ergebnis in Base64 kodieren, und es als Basic plus Leerzeichen plus die gepackte Zeichenfolge im Authorization-Header verschicken. In Kotlin:
import kotlin.io.encoding.Base64
fun main() {
val credentials = "alice:s3cr3t"
val header = "Basic " + Base64.encode(credentials.encodeToByteArray())
println(header) // Basic YWxpY2U6czNjcjN0
}
Warum hier Base64 und nicht etwas Stärkeres? Weil ein Header-Wert ein einzelnes druckbares Token sein muss, und Base64 das garantiert. Die ehrliche Warnung: Base64 ist Kodierung, keine Verschlüsselung. Jeder Client kann YWxpY2U6czNjcjN0 in einem Schritt zurück in alice:s3cr3t kehren, weshalb Basic Auth nur auf TLS-Verbindungen hingehört, idealerweise mit Token-Credentials statt mit menschlichen Passwörtern. Wenn Sie einen solchen Header parsen, teilen Sie am Doppelpunkt genau einmal auf, weil das Passwort legal Doppelpunkte enthalten kann.
Feldnotizen: Bilder und Data URLs
Data URLs betten binäre Assets direkt in HTML, CSS und JSON ein, damit der Browser keine zweite Anfrage stellt. Die Form ist ein Medientyp, ein Komma, das Wort base64, ein weiteres Komma und die gepackten Bytes:
import kotlin.io.encoding.Base64
fun main() {
val png = byteArrayOf(0x89.toByte(), 0x50, 0x4E, 0x47, 0x0D.toByte(), 0x0A.toByte(), 0x1A.toByte(), 0x0A.toByte())
val dataUrl = "data:image/png;base64," + Base64.encode(png)
println(dataUrl) // data:image/png;base64,iVBORw0KGgo=
}
Die Bytes oben sind die ersten acht einer PNG-Datei, die magische Zahl, die jeder Dekodierer prüft. Warum Base64 passt: Die Nutzlast muss ein URL-sicheres Text-Token innerhalb von Markup sein, und Base64 ist die einzige weit verbreitete Binär-zu-Text-Kodierung mit stabiler Grammatik. Die Stolperstelle ist die Größe. Ein 300-Kilobyte-Logo wird zu etwa 400 Kilobyte Markup, und jedes zusätzliche Kilobyte wird auf jedem Page-Load bezahlt, der es enthält. Data URLs sind ein hervorragendes Tool für Icons, Avatare und kleine Sprites; sie sind ein schlechtes Tool für Video und sogar ein mittelmäßiges für ein großes Foto. Messen Sie, bevor Sie einbetten.
Feldnotizen: E-Mail-Anhänge
SMTP ist ein Text-Protokoll, das älter ist als jeder Begriff von Binärdaten, also ist jeder Anhang in jeder E-Mail, die Sie je erhalten haben, Base64, umgebrochen bei 76 Zeichen, deklariert mit einem Content-Transfer-Encoding: base64-Header. Ein minimales MIME-Teil mit einem kleinen binären Anhang sieht so aus, mit dem von Kotlin erzeugten Körperslot, gefüllt für einen Header aus den fünf Bytes %PDF-:
From: sender@example.com
To: receiver@example.com
Subject: report
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="cut-here"
--cut-here
Content-Type: text/plain; charset="utf-8"
The quarterly report follows as an attachment.
--cut-here
Content-Type: application/pdf
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="report.pdf"
JVBERi0=
--cut-here--
(Der Körper JVBERi0= ist das Base64 der fünf Bytes %PDF-; ein echter Bericht würde sich über viele Zeilen von 76 Zeichen umbrechen.) Die Kotlin-Seite ist eine Zeile, wenn Sie die Datei-Bytes haben:
import kotlin.io.encoding.Base64
fun main() {
val pdf = byteArrayOf(0x25, 0x50, 0x44, 0x46, 0x2D) // "%PDF-"
println(Base64.Mime.encode(pdf)) // JVBERi0=
}
Die Stolperfallen sind reine Kanal-Disziplin. Verwenden Sie für den Körper Mime, nicht Default, denn ein strenger MIME-Parser erwartet den Umbruch, und eine glatte Default-Zeile mit 10.000 Zeichen wird von einigen Transporten abgelehnt oder versemmelt. Halten Sie die Schreibweise in der Content-Transfer-Encoding-Zeile exakt bei base64, und denken Sie daran, dass der Umbruch Teil des Formats ist: ungebrochene und umgebrochene Ausgabe sind verschiedene Darstellungen derselben Bytes, und der Parser auf der anderen Seite muss wissen, welche er isst.
Feldnotizen: JSON-APIs und Uploads
Wenn eine API Binärdaten in einem JSON-Dokument will, ist die Konvention ein String-Feld, das Base64 hält, und sie ist eines der bequemsten Muster im Ökosystem, weil JSON schon einen Platz für Text hat. Mit kotlinx.serialization ist der Roundtrip geradlinig:
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlin.io.encoding.Base64
@Serializable
data class UploadRequest(val name: String, val payload: String)
fun main() {
val icon = byteArrayOf(0x89.toByte(), 0x50, 0x4E, 0x47)
val request = UploadRequest("icon.png", Base64.encode(icon))
val json = Json.encodeToString(UploadRequest.serializer(), request)
println(json) // {"name":"icon.png","payload":"iVBORw=="}
}
Warum hier Base64: JSON hat keinen Binär-Typ, also muss die Nutzlast Text sein, und Base64 ist die am wenigsten überraschende Binär-Grammatik, die ein API-Verbraucher ohne Dokumentation erkennen wird. Die Stolperstelle ist der Maßstab. Der 4/3-Aufschlag wird auf jede Anfrage und jede Antwort bezahlt, und ein 10-Megabyte-Upload wird zu einem 13,3-Megabyte-JSON-String, den Ihr Parser im Speicher halten, escapen und validieren muss. Für große Dateien ist multipart/form-data oder ein binärer Körper fast immer das bessere Drahtformat; reservieren Sie Base64-im-JSON für Thumbnails, Icons, Signaturen und kleine Klumpen, wo die Bequemlichkeit die Steuer aufwiegt.
Feldnotizen: Konfiguration und die Kommandozeile
Die letzten zwei Muster sind die kleinen, die in jeder Codebase auftauchen. Konfigurationswerte, Tokens, Lizenzschlüssel, manchmal kleine Secrets, reisen oft als Base64 durch Umgebungsvariablen und Property-Dateien, weil der Transport nur Text ist und der Wert Anführungszeichen oder Zeilenumbrüche enthalten kann. Das Zurücklesen ist derselbe Zwei-Schritte-Tanz in umgekehrter Reihenfolge: System.getenv oder eine Property-Suche, dann Dekodieren. An der Kommandozeile ist das Kodieren einer Datei für Transport oder Inspektion ein Zehn-Zeilen-Programm:
import java.io.File
import kotlin.io.encoding.Base64
fun main(args: Array<String>) {
require(args.isNotEmpty()) { "usage: b64encode <file>" }
val bytes = File(args[0]).readBytes()
val encoded = Base64.encode(bytes)
File(args[0] + ".b64").writeText(encoded)
println("Wrote ${encoded.length} characters to ${args[0]}.b64")
}
Auf einer Datei ausgeführt, die die zehn Bytes hello file enthält, schreibt es 16 Zeichen, aGVsbG8gZmlsZQ==. Die Stolperstellen in beiden Fällen sind dieselben zwei: Base64 in der Konfiguration ist kein Tresor, der Wert ist einen Schritt vom Klartext entfernt und sollte ohnehin auf dem Draht wie ein Secret behandelt werden, und ein von Hand gebautes CLI-Tool sollte sein Alphabet bewusst entscheiden, denn ein Nutzer, der Ihre Ausgabe in eine URL leitet, braucht UrlSafe, nicht Default.
Was bei der Kodierung schiefgehen kann
Kodieren ist nachsichtig mit dem Inhalt: Jede Byte-Folge ist gültige Eingabe, also gibt es keinen "invalid symbol"-Fehler, wie ihn Dekodierer haben. Was tatsächlich wirft, ist die Geometrie, und die Meldungen sind präzise genug, um nützlich zu sein:
| Situation | Ausnahme | Meldung |
|---|---|---|
endIndex über das Ende des Arrays hinaus | IndexOutOfBoundsException | startIndex: 0, endIndex: 100, size: 5 |
startIndex über endIndex hinaus | IllegalArgumentException | startIndex: 3 > endIndex: 2 |
Zielfeld zu klein für encodeIntoByteArray | IndexOutOfBoundsException | The destination array does not have enough capacity, destination offset: 0, destination size: 2, capacity needed: 8 |
Zwei Kotlin-spezifische Fallen sitzen neben diesen. Die erste ist der klassische int + String-Fehler: bytes.size + " bytes" kompiliert nicht, denn Plus auf einem Int konkateniert keine Strings; die Interpolations-Form "${bytes.size} bytes" ist der Kotlin-Weg. Die zweite ist die Zeichensatz-Suche, die für einen Namen, den die JVM nicht erkennt, UnsupportedCharsetException wirft, wie bei Charset.forName("utf-9"), also ist ein Tippfehler in einem Zeichensatz-Namen eine Laufzeit-Ausnahme, kein Kompilierfehler, und er zeigt sich dort, wo der Encoder läuft, nicht dort, wo der Name getippt wurde.
Fallen, Kotlin-Style
Die Fallen unten sind die, die speziell Kotlin-Entwickler beißen, wenn sie zum ersten Mal zur Standardbibliothek greifen:
encodeToByteArray()den Zeichensatz für Sie wählen zu lassen. Es ist immer UTF-8, still und leise, und eine Latin-1- oder UTF-16-Quelle packt in Bytes, die der Dekodierer nicht zurücklesen kann. Entscheiden Sie den Zeichensatz bewusst, auf der JVM mittoByteArray(charset), wenn es nicht UTF-8 ist.- Dem Muskelgedächtnis nach
java.util.Base64zu greifen. SeinegetUrlEncoder()gibt standardmäßig Padding aus, was die falsche Form für JWTs ist, es sei denn, Sie erinnern sich an.withoutPadding(); das Kotlin-Preset macht die Wahl auf beiden Seiten explizit. - Umgebrochene Ausgabe von
MimeoderPeman einen Single-Line-Verbraucher zu füttern. Die CRLFs sind Teil der Darstellung und werden einen strengen Dekodierer scheitern lassen, der eine Zeile erwartet; umbrechen Sie nur, wenn der Kanal Umbruch erwartet. text.getBytes()auf einem Kotlin-String zu schreiben. Die Java-Methode ist aufkotlin.Stringnicht sichtbar; die Inline-toByteArray(charset)-Erweiterung, vorhanden seit Kotlin 1.0, ist der Ersatz.- Alte Toolchains auszuführen. Das System-Kotlin auf einigen Distributionen ist noch 1.3, das ganz vor der Standardbibliothek-
Base64liegt; die Klasse braucht 1.8.20, um zu existieren, 2.0.20 für die Padding-Kontrolle und 2.2, um stabil zu sein. - Base64 als Sicherheitsschicht zu behandeln. Es ist eine Transport-Kodierung mit einer öffentlichen, einstufigen Umkehrung. Alles, was ein Secret ist, sollte verschlüsselt werden, bevor es gepackt wird, niemals nur gepackt.
Gut wählen: Ein schneller Entscheidungsführer
Im Zweifel wird die Entscheidung fast immer vom Kanal getroffen, nicht vom Inhalt. Die Kurzversion:
Base64.Defaultfür APIs, JSON, Data URLs und alles, was im Wesentlichen eine Zeile Text ist. Gepolsterte Ausgabe ist die kompatibelste Form auf dem Draht.Base64.UrlSafemitABSENT-Padding für Tokens, JWTs und alles, was in einem URL-Segment oder einem Query-Parameter landet.Base64.Mimefür E-Mail-Körper und Anhänge, wo Zeilen von 76 Zeichen eine harte Anforderung des Formats sind.Base64.Pemfür Zertifikate und private Schlüssel, wo Zeilen von 64 Zeichen das sind, was jedes PKI-Tool erwartet.
Dann zwei übergreifende Gewohnheiten: den Zeichensatz explizit halten, wann immer die Eingabe Text ist, und ein Auge auf den 4/3-Aufschlag werfen, damit große Nutzlasten einen Binärkanal statt eines base64-Kanals bekommen.
Der Weg zum Standard
Der Standardbibliothek-Pfad zu Base64 ist frisch genug, dass Sie älteres Kotlin ohne sie antreffen werden. Die Klasse erschien erstmals in Kotlin 1.8.20 im April 2023, markiert als experimentell, mit drei Instanzen und einer einfacheren Oberfläche: Das Kodieren gab immer Padding aus, und es gab keinen Weg, weniger zu verlangen. Wenn Sie 1.8-Ära-Code gesehen haben, der = vom Ende eines Strings mit removeSuffix abschneidet, dann war das das einzige Werkzeug der Ära für ungepolsterte Ausgabe, und es ist eine Gewohnheit, die es sich lohnt, jetzt abzulegen. Kotlin 2.2, veröffentlicht im Juni 2025, stabilisierte die API und ergänzte das letzte Stück, die Pem-Instanz. Der PaddingOption-Regler und withPadding waren bereits in der 2.0-Linie angekommen, in 2.0.20, um genau zu sein, der erste erste-Klasse-Weg, das Padding in beide Richtungen zu kontrollieren. Die Streaming-Helfer encodingWith und decodingWith bleiben experimentell und nur für die JVM, so markiert die Standardbibliothek APIs, bei denen sie vor dem Einfrieren noch mehr Felderfahrung will. Seit der 2.4.0-Linie ist die Sprache auch auf ein 18-Monate-Unterstützungsfenster für die Standardbibliothek umgestiegen, also bekommt ein Projekt, das auf einem 2.4.x-Compiler festgelegt ist, wie dem 2.4.10-Stabil-Release, das zum Zeitpunkt dieses Schreibens aktuell ist, die vollständige Base64-API für die Dauer des Support-Zyklus.
Kleine Wunder
Ein paar Details, die die Klasse interessanter machen, sobald man sie kennt:
Base64.encode(bytes)ohne Instanz funktioniert, weilBase64.Defaultauf dem Companion-Objekt definiert ist; das Companion ist das Standard-Schema, also sind der Zucker und die benannte Form buchstäblich dasselbe Objekt.- Die
encodeToAppendable-Funktion ist Builder-Style: Sie gibt den Ziel-Appendable zurück, also ist das dokumentierte Muster, den Rückgabewert zu ignorieren und Ihren Builder weiterzunutzen. - Padding füllt nie eine ganze Gruppe: eine base64-Zeichenfolge endet mit null, einem oder zwei
=-Zeichen, und das Zählen der Pads sagt Ihnen exakt, wie viele Bytes die Originaldaten in der letzten Dreiergruppe übrig hatten. Pemist das neueste der vier Presets, hinzugekommen in 2.2; der 64-Zeichen-Umbruch ist eine PKI-Konvention, die älter ist als die RFC-2045-Regel, neben der sie steht.- Auf der JVM delegiert die Standardbibliothek bewusst nicht an
java.util.Base64; die zwei Implementierungen sind getrennt, was das Verhalten auf allen Plattformen identisch hält, auf Kosten einer auskommentierten Optimierung, die das Kotlin-Team für eine Zukunft im Baum gehalten hat, in der die Java-API es zulässt. - Dasselbe 2.2-Release, das
Base64stabilisierte, stabilisierte auchHexFormat, die Hex-Formatierungs-Klasse inkotlin.text, die seit Kotlin 1.9 experimentell war, sodass Textkodierungen auf Byte-Ebene jetzt einen festen Platz in der Standardbibliothek haben.
Zusammenfassung und wohin als Nächstes
Base64 in Kotlin zu erzeugen ist eine kurze Liste bewusster Entscheidungen: das Preset nach Kanal wählen, das Padding bewusst entscheiden, den Zeichensatz explizit halten, wenn die Eingabe Text ist, und den 4/3-Aufschlag respektieren, wenn die Nutzlast groß ist. Alles andere, Dateien, Puffer, Appendables, Ströme, ist eine dünne Hülle um dieselben vier Instanzen. Die andere Richtung, diesen gepackten Text zurück in Bytes zu nehmen, hat seine eigenen Strenge-Regeln, seine eigenen Fehlerarten und seine eigenen Fallen, und der verwandte Artikel auf der Schwestersite behandelt Base64-Dekodierung in Kotlin im Detail.
Zuletzt aktualisiert: 2026-09-08
Verwandter Artikel: Base64-Dekodierung in Kotlin: Ein vollständiger Leitfaden