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 :
| Option | Encodage | Décodage |
|---|---|---|
PRESENT (défaut) | écrit = | exige = |
ABSENT | pas de = | rejette = |
PRESENT_OPTIONAL | écrit = | accepte les deux |
ABSENT_OPTIONAL | pas 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.decode | Message |
|---|---|
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 |
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
=. ConfigurezPRESENT_OPTIONALouABSENT_OPTIONALau 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. UtilisezNO_WRAP, ouNO_WRAP or URL_SAFE or NO_PADDINGpour 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 parU+FFFDet corrompt les données. Gardez leByteArray.
Notes de version
- Kotlin 1.8.20 : ajout de
kotlin.io.encoding.Base64en version expérimentale. - Kotlin 2.0.20 :
withPaddingetPaddingOption. - 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.