Charger un fichier
  1. base64hub
  2. Base64 dans le code
  3. Kotlin
Code

Base64 en Kotlin

Kotlin fournit un objet multiplateforme kotlin.io.encoding.Base64, stable depuis Kotlin 2.2. Sur la JVM, vous pouvez aussi utiliser java.util.Base64. Les sorties ci-dessous proviennent de Kotlin 2.4.21 sur Java 21.

Encoder et décoder une chaîne

Base64.encode prend un ByteArray et renvoie une String. encodeToByteArray() et decodeToString() gèrent l’UTF-8 dans les deux sens :

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 est lui-même l’instance standard (Base64.Default). Il existe aussi Base64.UrlSafe et Base64.Mime.

Avant Kotlin 2.2, l’API demande @OptIn(ExperimentalEncodingApi::class). Si vous êtes coincé sur une ancienne version, java.util.Base64 fonctionne comme dans Base64 en Java :

java.util.Base64.getEncoder().encodeToString("hi".toByteArray())  // aGk=

Usages typiques : en-têtes HTTP et data URI.

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==

Base64 URL-safe et options de remplissage

Base64.UrlSafe remplace +/ par -_. Le remplissage se règle avec withPadding, qui renvoie une nouvelle instance configurée :

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())  // <<???>>

Les quatre options :

OptionEncodageDécodage
PRESENT (défaut)écrit =exige =
ABSENTpas de =rejette =
PRESENT_OPTIONALécrit =accepte les deux
ABSENT_OPTIONALpas de =accepte les deux

Les instances par défaut sont strictes sur ce point :

Base64.UrlSafe.decode("PDw_Pz8-Pg")
// IllegalArgumentException: The padding option is set to PRESENT,
// but the input is not properly padded

Pour les parties d’un JWT et les autres jetons, ABSENT_OPTIONAL ou PRESENT_OPTIONAL est en général le bon choix, car les producteurs ne s’accordent pas sur le remplissage. Pour le contexte : Base64 ou Base64URL et le remplissage expliqué.

Par exemple, pour lire le payload d’un JWT :

val jwt = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0In0.sig"
val url = Base64.UrlSafe.withPadding(Base64.PaddingOption.ABSENT_OPTIONAL)
println(url.decode(jwt.split(".")[1]).decodeToString())   // {"sub":"1234"}

Découpage des lignes MIME

Base64.Mime écrit des lignes de 76 caractères séparées par \r\n et ignore les séparateurs de ligne au décodage :

Base64.Mime.encode("x".repeat(60).encodeToByteArray())
// "eHh4eHh4...eHh4" (76 chars) + "\r\n" + "eHh4"

Base64.Mime.decode("aGVs\r\nbG8K").decodeToString()  // "hello\n"

Pour comparer, collez la même entrée dans l’encodeur Base64 MIME.

Encoder et décoder un fichier

Les petits fichiers tiennent en mémoire :

import java.io.File

println(Base64.encode(File("hello.txt").readBytes()))   // aGVsbG8K
File("copy.txt").writeBytes(Base64.decode("aGVsbG8K"))

Gros fichiers en flux

Sur la JVM, encodingWith et decodingWith enveloppent des flux. Elles sont toujours expérimentales en Kotlin 2.4 : il leur faut un opt-in et leurs propres 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) }
    }
}

La fermeture du flux d’encodage, dont se charge le bloc use, écrit le dernier groupe et le remplissage. Pour un fichier de test de 300 Ko, la sortie en flux était identique à Base64.encode(readBytes()), et l’aller-retour a restitué les mêmes octets. Si vous préférez une voie non expérimentale, java.util.Base64.getEncoder().wrap(outputStream) fait le même travail.

Validation et messages d’exception

Le décodeur Kotlin est plus strict que celui de Java. Il rejette les bits de remplissage non nuls, que Java et la plupart des autres bibliothèques acceptent. Chaque échec est une IllegalArgumentException :

Entrée de Base64.decodeMessage
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
aInput should have at least 2 symbols for Base64 decoding, startIndex: 0, endIndex: 1

Le nombre entre parenthèses est le code du caractère en octal (137 pour _, 12 pour un saut de ligne). Une fonction de validation tient en une ligne :

fun isBase64(s: String) = runCatching { Base64.decode(s) }.isSuccess

Pour voir où une chaîne précise casse, utilisez le validateur Base64 ; le guide des erreurs Base64 liste les causes habituelles.

Pièges

  • Une entrée sans remplissage avec l’instance par défaut. Les segments de JWT et de nombreux jetons n’ont pas de =. Configurez PRESENT_OPTIONAL ou ABSENT_OPTIONAL au lieu de compléter les chaînes à la main.
  • L’option Android DEFAULT. android.util.Base64.encodeToString(bytes, Base64.DEFAULT) insère un saut de ligne tous les 76 caractères et à la fin. Dans un en-tête HTTP, cela casse la requête. Utilisez NO_WRAP, ou NO_WRAP or URL_SAFE or NO_PADDING pour les jetons.
  • Le jeu de caractères de String.toByteArray(). En Kotlin, c’est UTF-8 par défaut, contrairement à getBytes() de Java sur les anciens JDK : "héllo".toByteArray() ne pose donc pas de problème.
  • Décoder du binaire en chaîne. decodeToString() sur les octets d’une image remplace les séquences invalides par U+FFFD et corrompt les données. Gardez le ByteArray.

Notes de version

  • Kotlin 1.8.20 : ajout de kotlin.io.encoding.Base64 en version expérimentale.
  • Kotlin 2.0.20 : withPadding et PaddingOption.
  • Kotlin 2.2 : l’API de base devient stable ; les fonctions de flux pour la JVM restent expérimentales.

FAQ

Faut-il encore @OptIn(ExperimentalEncodingApi::class) ?

Pas pour Base64.encode et Base64.decode à partir de Kotlin 2.2. Les fonctions de flux encodingWith et decodingWith exigent toujours l’opt-in en Kotlin 2.4.

Comment décoder du Base64 sans remplissage en Kotlin ?

Utilisez une instance configurée : Base64.UrlSafe.withPadding(Base64.PaddingOption.ABSENT) pour une entrée sans remplissage, ou PRESENT_OPTIONAL pour accepter les deux. Les instances par défaut exigent le remplissage.

Faut-il utiliser android.util.Base64 ou kotlin.io.encoding.Base64 ?

Dans du nouveau code, l’API Kotlin fonctionne sur toutes les versions d’Android puisqu’elle fait partie de la bibliothèque standard. android.util.Base64 ajoute des sauts de ligne si vous ne passez pas Base64.NO_WRAP, une source fréquente d’en-têtes cassés.

Continuer la lecture