Einen String kodieren und dekodieren
Base64.encode nimmt ein ByteArray entgegen und liefert einen String. encodeToByteArray() und decodeToString() kümmern sich in beide Richtungen um UTF-8:
import kotlin.io.encoding.Base64
val enc = Base64.encode("héllo wörld".encodeToByteArray())
println(enc) // aMOpbGxvIHfDtnJsZA==
println(Base64.decode(enc).decodeToString()) // héllo wörld
Base64 selbst ist die Standardinstanz (Base64.Default). Daneben gibt es Base64.UrlSafe und Base64.Mime.
Unter Kotlin vor 2.2 braucht die API @OptIn(ExperimentalEncodingApi::class). Hängen Sie an einer alten Version fest, funktioniert java.util.Base64 genauso wie unter Base64 in Java:
java.util.Base64.getEncoder().encodeToString("hi".toByteArray()) // aGk=
Typische Einsatzzwecke sind HTTP-Header und Data-URIs:
val token = Base64.encode("user:pass".encodeToByteArray())
println("Authorization: Basic $token") // Authorization: Basic dXNlcjpwYXNz
val png = byteArrayOf(0x89.toByte(), 0x50, 0x4E, 0x47)
println("data:image/png;base64," + Base64.encode(png)) // data:image/png;base64,iVBORw==
URL-sicheres Base64 und Padding-Optionen
Base64.UrlSafe tauscht +/ gegen -_. Das Padding steuert withPadding, das eine neue konfigurierte Instanz liefert:
val d = "<<???>>".encodeToByteArray()
println(Base64.UrlSafe.encode(d)) // PDw_Pz8-Pg==
val urlNoPad = Base64.UrlSafe.withPadding(Base64.PaddingOption.ABSENT)
println(urlNoPad.encode(d)) // PDw_Pz8-Pg
println(urlNoPad.decode("PDw_Pz8-Pg").decodeToString()) // <<???>>
Die vier Optionen:
| Option | Kodieren | Dekodieren |
|---|---|---|
PRESENT (Standard) | schreibt = | verlangt = |
ABSENT | kein = | lehnt = ab |
PRESENT_OPTIONAL | schreibt = | akzeptiert beides |
ABSENT_OPTIONAL | kein = | akzeptiert beides |
Die Standardinstanzen sind dabei streng:
Base64.UrlSafe.decode("PDw_Pz8-Pg")
// IllegalArgumentException: The padding option is set to PRESENT,
// but the input is not properly padded
Für JWT-Teile und andere Tokens ist ABSENT_OPTIONAL oder PRESENT_OPTIONAL meist die richtige Wahl, da sich die Erzeuger beim Padding nicht einig sind. Hintergrund: Base64 vs. Base64URL und Padding erklärt.
So lesen Sie zum Beispiel die Payload eines JWT:
val jwt = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0In0.sig"
val url = Base64.UrlSafe.withPadding(Base64.PaddingOption.ABSENT_OPTIONAL)
println(url.decode(jwt.split(".")[1]).decodeToString()) // {"sub":"1234"}
MIME-Zeilenumbruch
Base64.Mime schreibt Zeilen mit 76 Zeichen, getrennt durch \r\n, und ignoriert Zeilentrenner beim Dekodieren:
Base64.Mime.encode("x".repeat(60).encodeToByteArray())
// "eHh4eHh4...eHh4" (76 chars) + "\r\n" + "eHh4"
Base64.Mime.decode("aGVs\r\nbG8K").decodeToString() // "hello\n"
Zum Vergleich fügen Sie dieselbe Eingabe in den MIME-Base64-Encoder ein.
Eine Datei kodieren und dekodieren
Kleine Dateien passen in den Speicher:
import java.io.File
println(Base64.encode(File("hello.txt").readBytes())) // aGVsbG8K
File("copy.txt").writeBytes(Base64.decode("aGVsbG8K"))
Große Dateien streamen
Auf der JVM umhüllen encodingWith und decodingWith Streams. In Kotlin 2.4 sind sie noch experimentell und brauchen daher ein Opt-in und eigene Imports:
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi
import kotlin.io.encoding.decodingWith
import kotlin.io.encoding.encodingWith
import java.io.File
@OptIn(ExperimentalEncodingApi::class)
fun encodeFile(src: File, dst: File) {
dst.outputStream().encodingWith(Base64).use { out ->
src.inputStream().use { it.copyTo(out) }
}
}
@OptIn(ExperimentalEncodingApi::class)
fun decodeFile(src: File, dst: File) {
src.inputStream().decodingWith(Base64).use { inp ->
dst.outputStream().use { inp.copyTo(it) }
}
}
Erst das Schließen des kodierenden Streams, das der use-Block für Sie übernimmt, schreibt die letzte Gruppe und das Padding. Die gestreamte Ausgabe einer 300-KB-Testdatei war identisch mit Base64.encode(readBytes()), und der Roundtrip lieferte dieselben Bytes zurück. Bevorzugen Sie einen nicht experimentellen Weg, erledigt java.util.Base64.getEncoder().wrap(outputStream) dieselbe Aufgabe.
Validierung und Exception-Meldungen
Der Kotlin-Decoder ist strenger als der von Java. Er lehnt Padding-Bits ungleich null ab, die Java und die meisten anderen Bibliotheken akzeptieren. Jeder Fehler ist eine IllegalArgumentException:
Eingabe für Base64.decode | Meldung |
|---|---|
PDw_Pz8-Pg== | Invalid symbol '_'(137) at index 3 |
"aGVs\nbG8K" | Invalid symbol '\n'(12) at index 4 |
aGk=!! | Symbol '!'(41) at index 3 is prohibited after the pad character |
aGk== | Symbol '='(75) at index 3 is prohibited after the pad character |
aGl= | The pad bits must be zeros |
a | Input should have at least 2 symbols for Base64 decoding, startIndex: 0, endIndex: 1 |
Die Zahl in Klammern ist der Zeichencode in Oktal (137 ist _, 12 ist ein Zeilenumbruch). Eine Prüffunktion passt in eine Zeile:
fun isBase64(s: String) = runCatching { Base64.decode(s) }.isSuccess
Wo genau ein bestimmter String bricht, zeigt der Base64-Validator; die Anleitung zu Base64-Fehlern listet die üblichen Ursachen auf.
Fallstricke
- Eingaben ohne Padding mit der Standardinstanz. JWT-Segmente und viele Tokens haben kein
=. Konfigurieren SiePRESENT_OPTIONALoderABSENT_OPTIONAL, statt Strings von Hand aufzufüllen. - Android-Flag
DEFAULT.android.util.Base64.encodeToString(bytes, Base64.DEFAULT)fügt alle 76 Zeichen und am Ende einen Zeilenumbruch ein. In einem HTTP-Header macht das die Anfrage kaputt. Verwenden SieNO_WRAP, für TokensNO_WRAP or URL_SAFE or NO_PADDING. - Zeichensatz von
String.toByteArray(). In Kotlin ist der Standard UTF-8, anders als bei JavasgetBytes()auf älteren JDKs;"héllo".toByteArray()ist also sicher. - Binärdaten in einen String dekodieren.
decodeToString()auf Bild-Bytes ersetzt ungültige Sequenzen durchU+FFFDund beschädigt die Daten. Behalten Sie dasByteArray.
Versionshinweise
- Kotlin 1.8.20:
kotlin.io.encoding.Base64als experimentell hinzugefügt. - Kotlin 2.0.20:
withPaddingundPaddingOption. - Kotlin 2.2: Die Kern-API wurde stabil; die JVM-Stream-Hilfsfunktionen bleiben experimentell.
Häufige Fragen
Brauche ich noch @OptIn(ExperimentalEncodingApi::class)?
Für Base64.encode und Base64.decode ab Kotlin 2.2 nicht mehr. Die Stream-Hilfsfunktionen encodingWith und decodingWith verlangen das Opt-in in Kotlin 2.4 weiterhin.
Wie dekodiere ich in Kotlin Base64 ohne Padding?
Verwenden Sie eine konfigurierte Instanz: Base64.UrlSafe.withPadding(Base64.PaddingOption.ABSENT) für Eingaben ohne Padding oder PRESENT_OPTIONAL, um beides zu akzeptieren. Die Standardinstanzen verlangen Padding.
Soll ich android.util.Base64 oder kotlin.io.encoding.Base64 verwenden?
In neuem Code funktioniert die Kotlin-API auf jeder Android-Version, weil sie zur Standardbibliothek gehört. android.util.Base64 fügt Zeilenumbrüche ein, sofern Sie nicht Base64.NO_WRAP übergeben. Das ist eine häufige Ursache für kaputte Header.