Devi lavorare con il formato Base64? Allora questo sito è perfetto per te! Usa il nostro praticissimo strumento online per codificare o decodificare i tuoi dati.

Decodifica Base64 in Kotlin: una guida completa

Sei davanti a un valore che si rifiuta di essere letto: SGVsbG8sIFdvcmxkIQ==. Una sequenza di lettere e cifre, qua e là un + o un / mescolati, e di solito uno o due caratteri = appesi alla fine. Questo è Base64, e questa guida serve a riportarlo a quello che era - una frase, un'immagine, un certificato, un blob binario - alla maniera Kotlin. Un ripasso di una riga prima di immergerci: Base64 impacchetta ogni tre byte in quattro caratteri tratti da un alfabeto di 64 simboli, e una corta coda di padding = segna dove finiscono i dati veri. Il tour completo del formato è sulla home page, quindi qui gli dedichiamo una sola frase, e una seconda: dato che quattro caratteri portano quello che portavano tre byte, la forma testuale è circa un terzo più lunga dei dati originali.

La buona notizia su Kotlin: non ti serve alcun pacchetto. La libreria standard fornisce la propria implementazione di Base64 da anni; è pienamente stabile da Kotlin 2.2, e gira su ogni piattaforma dove gira Kotlin, dalla JVM del portatile a un telefono Android a Node.js a una funzione edge WASI. Tutto quello che segue funziona con il Kotlin che arriva insieme al tuo progetto.

Prima la buona notizia: cosa ti serve davvero

Nessun artefatto base64 da aggiungere a Gradle, nessun pacchetto in stile NuGet, nessun modulo npm. La classe che cerchi è kotlin.io.encoding.Base64, parte della stessa libreria standard di Kotlin. Se sai scrivere println, sai decodificare Base64. In un progetto Kotlin, tre API possono fare lavoro Base64, e scegliere quella giusta è la prima decisione vera:

API Dove gira Quando usarla
kotlin.io.encoding.Base64 Tutte le piattaforme Kotlin: JVM, Android, JS, Native, Wasm La scelta di default. Stabile da Kotlin 2.2, multipiattaforma, API moderna
java.util.Base64 Solo JVM (Java 8+; su Android, API 26+) Codebase solo-JVM che già vivono nel territorio dell'interop con Java
android.util.Base64 Solo Android (API 8+) Codice Android legacy, o quando ti servono specificamente le sue costanti flag

Due note sulle versioni che vale la pena conoscere. Prima, la classe della libreria standard è apparsa per la prima volta in Kotlin 1.8.20 (aprile 2023) dietro un cancello @ExperimentalEncodingApi; Kotlin 2.0.20 ha portato la manopola withPadding e la regola rigorosa sul padding, e Kotlin 2.2.0 (giugno 2025) ha reso stabile l'API e ha aggiunto l'istanza PEM. Quindi con Kotlin 2.2 o successivo - inclusa la riga stabile attuale, 2.4.x - puoi usare tutto in questa guida con zero annotazioni. Secondo, se il tuo progetto fissa una versione di Kotlin tra 1.8 e 2.1, la stessa classe esiste ma è marcata sperimentale, e il compilatore non te la lascia usare senza un'annotazione @OptIn sulla funzione.

Una trappola di installazione che è costata più di un pomeriggio: il pacchetto kotlin nei repository Debian e Ubuntu è alla versione 1.3.31, che precede del tutto l'API Base64 della libreria standard, quindi non sa compilare un singolo esempio di questo articolo. Prendi il compilatore dai rilasci Kotlin su GitHub o da SDKMAN, e nei progetti Gradle fissa il plugin esplicitamente:

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

La tua prima decodifica: due righe e un risultato in byte

L'intera cerimonia sta in due dichiarazioni, e la classica stringa TWFu è un buon punto di partenza:

import kotlin.io.encoding.Base64
fun main() {
  val packed = "TWFu"
  val bytes = Base64.decode(packed)
  println(bytes.decodeToString())  // Man
}

Leggi piano, perché dentro ci sono nascoste tre decisioni di design. Prima, Base64.decode(...) senza alcun .Default non è un refuso: Default è l'oggetto companion della classe, quindi chiamare la funzione sulla classe stessa è un modo abbreviato di chiamarla su Base64.Default. Vedrai anche Base64.Default.decode(...) nei tutorial più vecchi, e significa esattamente la stessa cosa. Secondo, e questo conta più di quanto sembri: decode ti consegna un ByteArray, mai una String. Il payload potrebbe essere una JPEG, un certificato X.509 o una frase, e l'API si rifiuta di indovinare quale, quindi il salto dai byte al testo è un passo separato e deliberato. Terzo, in quel passo vive la decisione sul charset, ed è lì che nascono la maggior parte dei bug del tipo "il mio Base64 è tornato come spazzatura". Arriviamo lì tra poco; prima, un giro di andata e ritorno per provare che la decodifica è fedele:

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
}

Quattro schemi, quattro personalità

La classe non viene mai istanziata; scegli una delle quattro istanze pronte, e ognuna decodifica con un temperamento diverso:

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/
}
Istanza Alfabeto Come decodifica
Base64.Default A-Z a-z 0-9 + / Rigido: ogni carattere fuori dall'alfabeto lancia; il padding è richiesto
Base64.UrlSafe A-Z a-z 0-9 - _ Rigido, ma rispetto all'alfabeto URL; un + o / nell'input lancia
Base64.Mime A-Z a-z 0-9 + / Tollerante: ignora i separatori di riga e gli altri caratteri fuori dall'alfabeto, ma dopo il padding = non può seguire nulla; il padding è richiesto
Base64.Pem A-Z a-z 0-9 + / Tollerante, stesse regole di Mime; questa è la variante PEM/PKI dello stesso alfabeto

La divisione tollerante/rigido è la singola cosa più utile da interiorizzare. Default e UrlSafe trattano ogni carattere estraneo come una scena del crimine e lanciano subito. Mime e Pem sorvolano su a capo, spazi e punteggiatura sperduta - perché è esattamente quello che contengono i file veri di email e certificati - eppure non sono illimitati: nel momento in cui un carattere di dati appare dopo il padding, persino loro lanciano. Vedrai i messaggi d'errore esatti nella guida ai fallimenti più avanti in questo articolo.

Un'altra conseguenza delle personalità: uno schema non sa leggere l'output di un altro schema. Dai un token base64url a Base64.Default e il carattere - non c'è nel suo alfabeto, quindi ottieni IllegalArgumentException: Invalid symbol '-'(55) at index .... In caso di dubbio su dove sia venuta una stringa, scegli lo schema che corrisponde a chi l'ha prodotta, non quello che corrisponde al tuo umore.

Base64 URL-safe e JWT

Due caratteri dell'alfabeto standard fanno i casini nel momento in cui i dati devono viaggiare attraverso un URL. Un + in una query string viene di routine riinterpretato come uno spazio prima che qualcosa lo legga, e / è un separatore di percorso, quindi non può apparire in un segmento di URL nemmeno per sbaglio. La RFC 4648, sezione 5, risolve scambiando gli ultimi due simboli dell'alfabeto: + diventa - e / diventa _. Il nome che sentirai di più è base64url, e in Kotlin è Base64.UrlSafe.

Il più grande consumatore di base64url è il JSON Web Token. Un JWT nella sua forma compatta è tre parti base64url unite da punti: header.payload.signature. La RFC 7515 specifica queste parti come base64url senza padding, ed è una seconda differenza rispetto all'alfabeto semplice, non solo i caratteri. Ecco un token mentre viene aperto per ispezione:

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
}

Due cose da notare. Le parti del token non portano padding, ma Base64.UrlSafe di fabbrica richiede il padding, quindi la riga withPadding(PRESENT_OPTIONAL) sta facendo lavoro vero: accetta input con e senza padding. E lo split(".") più lo smembramento è solo Kotlin puro che fa quello che il formato gli chiede. Un avvertimento serio: aprire un JWT serve per guardare un token, non per fidarsi di esso. Header e payload sono dati semplici dopo la decodifica; solo una firma verificata dice che il token è genuino, e per quello ti serve una libreria JWT vera, non splitting di stringhe fatto a mano.

Modalità di padding e la manopola della severità

Il padding non è un fatto fisso del Base64 in Kotlin; è un'impostazione. Ogni istanza porta una PaddingOption, tutte e quattro le istanze preset partono su PRESENT, e withPadding ti consegna una nuova istanza con un'impostazione diversa lasciando l'originale intatta. Ecco la manopola, opzione per opzione:

Opzione Input senza padding Input con padding corretto
PRESENT (default ovunque) Lancia Decodifica
ABSENT Decodifica Lancia
PRESENT_OPTIONAL Decodifica Decodifica
ABSENT_OPTIONAL Decodifica Decodifica
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
}

Sul lato decodifica, PRESENT_OPTIONAL è la tua rete di sicurezza: è l'opzione che dice "non so se il mittente ha messo il padding, e intendo continuare a funzionare". I messaggi d'errore per le altre combinazioni sono insolitamente utili, quindi li riconoscerai all'istante quando un decoder rigido incontra l'input sbagliato: padding mancante sotto PRESENT produce The padding option is set to PRESENT, but the input is not properly padded, e padding sotto ABSENT produce The padding option is set to ABSENT, but the input has a pad character at index 7. Un comportamento merita una segnalazione perché sorprende la gente: un doppio padding come SGVsbG8== non è "in più, ma va bene". Il primo = chiude i dati, e il secondo è un carattere dove ci si aspettava dati, quindi persino i decoder più tolleranti lo rifiutano.

C'è anche una storia di versioni nascosta qui. Se hai ereditato codice scritto contro l'API sperimentale 1.8.x, ricorda che il vecchio decode accettava input con o senza padding. In Kotlin 2.0.20, Default è passato alla regola rigorosa PRESENT, quindi un input senza padding che prima funzionava ora lancia facendo l'upgrade oltre quel rilascio puntuale. La correzione è una riga: withPadding(Base64.PaddingOption.PRESENT_OPTIONAL), oppure normalizza i tuoi input prima di decodificare.

Dai byte al testo: charset e Unicode

Una volta che hai il tuo ByteArray, la domanda è cosa significa. Se il payload è testo, la risposta di default è decodeToString(), che interpreta i byte come UTF-8 e funziona su ogni piattaforma. Per il caso comune di API moderne, email e dati web, è tutto quello che ti servirà mai, emoji comprese:

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

Nel momento in cui il mittente ha usato qualcosa di diverso da UTF-8, però, la decisione sul charset spetta a te. Le conversioni di testo incorporate di Kotlin sono solo UTF-8 per scelta: decodeToString() non ha un parametro charset, e nemmeno esiste una funzione da stringa a byte che ne abbia uno. Sulla JVM scendi all'API charset della piattaforma, che è onesta ed esplicita:

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  (il byte é non è UTF-8 valido)
  println(asLatin)  // héllo
  val byName = String(Base64.decode(packed), Charset.forName("ISO-8859-1"))
  println(byName)   // héllo
}

Quel ? nella riga centrale non è un problema di font; è U+FFFD, il carattere di sostituzione Unicode, che sta al posto di un byte che non forma UTF-8 valido. Se ne vedi una fila dopo la decodifica, il tuo payload sta bene - è l'assunzione sul charset a non stare bene. Nota anche l'asimmetria che morde la gente: sul lato codifica esiste l'estensione JVM toByteArray(charset); sul lato decodifica il costruttore corrispondente è String(bytes, charset). Nessuno dei due prende un nome di charset; per quello ti serve Charset.forName("..."), che lancia UnsupportedCharsetException per un nome inventato, quindi un refuso in un valore di configurazione fallisce subito invece di scegliere in silenzio una codifica diversa.

Mentre siamo nel paese dei byte, una trappola specifica di Kotlin: un Char è un valore a 16 bit, e il toByte() su di esso mantiene in silenzio solo gli otto bit bassi. Se impacchetti byte a mano dai caratteri, "中".first().code.toByte() ti dà 45, un numero che non c'entra nulla con il carattere. La strada corretta è sempre encodeToByteArray(), che fa il vero lavoro di codifica - lo stesso carattere sono tre byte UTF-8, e la sua forma Base64 è 5Lit. Lascia che sia la libreria standard a codificare; non impacchettare mai caratteri in byte a mano.

File, sottostringhe e input grossi

I dati Base64 non sono sempre una stringa ordinata in memoria. A volte sono un file, una fetta di una risposta più grande, o troppo grossi per starci tutti insieme. Kotlin ti dà tutte e tre le porte.

I file sono il caso noioso nel modo migliore: leggi i byte, decodifichi, fatto. Entrambe le API file standard funzionano, quella che il tuo progetto già usa:

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)  // numero di byte decodificati
  val fromPath = Path("payload.b64").readBytes()
  println(Base64.decode(fromPath.decodeToString()).size)  // stesso numero
}

Le sottostringhe sono dove gli overload CharSequence si fanno valere. decode accetta qualsiasi sequenza di caratteri con indice di inizio e fine, quindi puoi dargli una fetta di un lungo corpo di risposta senza prima fare una copia della fetta:

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
}

Se già conosci la dimensione dell'output e vuoi riutilizzare un buffer, decodeIntoByteArray scrive in un array di destinazione che scegli tu e ti dice quanti byte ha scritto. Dagli un buffer troppo piccolo e lancia IndexOutOfBoundsException con la capacità richiesta nel messaggio, quindi l'errore fa anche da indizio di dimensionamento.

Per stream davvero grandi sulla JVM, c'è una terza porta: i decoder streaming. Sono ancora marcati sperimentali - da qui l'annotazione opt-in - ed esistono solo per la JVM, ma decodificano al volo invece di tenere tutto in memoria:

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

Due dettagli pratici. Le funzioni di estensione vivono a livello superiore del pacchetto, quindi le importi per nome (funziona anche l'import con la stella, ma i nomi sono più gentili). E il decoder tratta il padding come una fermata definitiva: se lo stream sottostante continua dopo la sezione Base64, la lettura dallo stream decodificato finisce al = e i byte rimasti restano disponibili nello stream originale. Questo lo rende ordinato per i formati che appendono Base64 davanti a qualcos'altro.

In trincea: API HTTP e corpi JSON

Il JSON non può portare byte grezzi - è un protocollo testuale - quindi le API che devono spostare dati binari (immagini, certificati, blob arbitrari) quasi sempre li avvolgono in Base64 dentro un campo stringa. Lo schema è: analizza il JSON, prendi il campo, decodifica. Con la libreria di serializzazione ufficiale la parte JSON è lontana due annotazioni:

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
}

Questo esempio ha bisogno del plugin e della libreria di serializzazione, da aggiungere una volta alla build:

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")
}

Senza la libreria la stessa idea funziona sulla stringa grezza, che è comoda per script veloci: estrai il campo con substringBetween e decodificalo. I punti deboli sono quelli familiari delle API: il campo potrebbe in realtà essere un data URL completo (con il prefisso data:image/png;base64,, trattato più avanti in questo articolo), il payload potrebbe essere avvolto in MIME con a capo, e il payload codificato può essere circa un terzo più grande del binario originale, quindi tieni d'occhio il budget di memoria sulle risposte grandi.

In trincea: email e input avvolti in MIME

L'email è un mondo di testo a 7 bit, e la risposta della RFC 2045 agli allegati binari è il Base64 con una piega: l'output codificato deve essere avvolto in modo che nessuna riga superi i 76 caratteri. Se hai mai ricevuto un allegato come testo, ecco perché sembra una colonna indentata di Base64. Per esattamente questo input, Base64.Mime è il decoder giusto, perché ignora i separatori di riga e gli altri caratteri fuori dall'alfabeto mentre procede:

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
}

La tolleranza è vera ma limitata. Avvolgi l'input, spargi uno o due spazi, nessun problema. Aggiungi però un carattere di dati dopo l'ultimo =, e persino Mime lancia: Symbol 'e'(145) at index 7 is prohibited after the pad character. E ricorda che Mime richiede comunque che il padding sia presente e corretto; un decoder MIME che inghiottisse anche il padding mancante chiederebbe guai. La ricetta pratica per i payload sporchi di email in ingresso è decodificare prima con Mime, e se lancia, guarda il messaggio - ti dice esattamente quale simbolo, a quale indice, ha rotto le regole.

In trincea: immagini e data URL

Una data URL è il modo del web di integrare un file direttamente in un documento: un media type, il marcatore base64, e il payload, tutto in un'unica stringa. Browser, CSS e UI integrate le adorano per gli asset piccoli - icone, avatar, grafica segnaposto - perché non c'è una seconda richiesta da fare. Il formato è così:

data:image/png;base64,iVBORw0KGgoAAAANSUhEUg==

Decodificarne una in Kotlin è un'operazione di stringa seguita da una decodifica Base64. Il prefisso non nasconde segreti; tutto dopo la virgola è il 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, ...]
}

Quel primo byte, -119 (cioè 0x89), seguito dalle lettere PNG, è il numero magico che identifica un file PNG. Controllare i primi quattro o otto byte dopo la decodifica è un modo economico per confermare che una data URL contiene davvero quello che il suo prefisso dichiara. Due avvertimenti onesti: il Base64 aggiunge circa un terzo alla dimensione, quindi una data URL è uno scambio di dimensioni che fai contro un giro di rete, e per qualsiasi cosa di grosso conviene di solito servire il file da un URL vero e lasciare che sia la cache a fare il suo lavoro.

In trincea: configurazione, variabili d'ambiente e database

Il Base64 compare nei file di configurazione e nelle variabili d'ambiente ogni volta che un valore binario deve viaggiare in un canale solo testuale: un'icona piccola integrata in un file properties, un token conservato in una variabile d'ambiente su un contenitore, un blob di byte parcheggiato in una colonna testuale perché lo schema precede un tipo binario vero. Il lato decodifica sono gli stessi due passi ovunque - leggi il testo, decodificalo:

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

Il punto debole di tutta questa zona sta in una riga: il Base64 non è cifratura. È un trucco di trasporto, non un lucchetto. Nessuno dovrebbe leggere un valore Base64 e pensare che i dati dentro siano nascosti; è lontano dalla visibilità una chiamata di funzione, ed è visibile in ogni riga di log che scrivi. Se un valore è sensibile, trattalo come sensibile da capo a fondo - un secret store, una colonna cifrata, quello che il tuo stack fornisce - e usa il Base64 solo per far viaggiare i byte attraverso il testo, non per proteggerli.

In trincea: la riga di comando

Il caso d'uso più vecchio di tutti: trasformare un blob Base64 nella riga di comando in un file. Uno strumento completo è otto righe di Kotlin, perché la libreria standard fa il lavoro pesante. Compilalo una volta con il compilatore Kotlin e sarà tuo per sempre:

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")
}

Lancialo con un argomento per un valore una tantum, oppure incanalaci un file con pipe per il lavoro a lotti: il programma legge il primo argomento se presente, altrimenti ripiega sull'input standard. Il trim() sta facendo servizio silenzioso qui, perché gli argomenti dello shell e i valori incollati amano arrivare con spazi bianchi vaganti che il decoder rigido rifiuterebbe. E se i tuoi payload sono base64url, sostituisci Base64.decode con Base64.UrlSafe.withPadding(Base64.PaddingOption.PRESENT_OPTIONAL).decode e lo strumento è pronto anche per i token.

Guida sul campo ai fallimenti di decodifica

Ogni decoder di questa guida fallisce con uno di due tipi di eccezione, e ogni messaggio è abbastanza specifico da dirti esattamente cosa è andato storto. Ecco la mappa completa, con i messaggi esatti prodotti dalla libreria standard:

Situazione Eccezione Messaggio (come prodotto)
Carattere fuori dall'alfabeto (spazio, a capo, simbolo dello schema sbagliato) IllegalArgumentException Invalid symbol ' '(40) at index 5
Carattere di dati dopo il padding IllegalArgumentException Symbol 'e'(145) at index 7 is prohibited after the pad character
Padding mancante mentre l'opzione è PRESENT IllegalArgumentException The padding option is set to PRESENT, but the input is not properly padded
Padding presente mentre l'opzione è ABSENT IllegalArgumentException The padding option is set to ABSENT, but the input has a pad character at index 7
Indice fuori dai limiti della sorgente IndexOutOfBoundsException startIndex: 0, endIndex: 100, size: 8
startIndex maggiore di endIndex IllegalArgumentException startIndex: 3 > endIndex: 2
Buffer di destinazione troppo piccolo per decodeIntoByteArray IndexOutOfBoundsException The destination array does not have enough capacity, destination offset: 0, destination size: 2, capacity needed: 8

Nota il pattern nelle prime due righe: il messaggio nomina il simbolo colpevole, il suo codice numerico tra parentesi e il suo indice. È un regalo per il debug. Quando una decodifica lancia in produzione, registra nei log i primi una dozzina di caratteri dell'input e l'indice del messaggio, e quasi sempre puoi trovare il colpevole in pochi secondi, che sia un cambio di riga incollato, un payload troncato o una stringa base64url finita per errore in un decoder standard.

Le insidie che colpiscono specificamente gli sviluppatori Kotlin

  • Dare per scontato che il payload sia testo. decode restituisce un ByteArray apposta. Chiamare decodeToString() su una JPEG perché "probabilmente è testo" ti dà un muro di caratteri di sostituzione. Decidi cosa sono i byte prima di convertirli.
  • Spazi bianchi di copy-paste. Il decoder di default è rigido, e un valore preso da un messaggio di chat o da un log arriva quasi sempre con un a capo finale o uno spazio iniziale. Fai trim prima di decodificare, oppure decodifica attraverso Mime, oppure accetta l'IllegalArgumentException e gestiscila.
  • Fare l'upgrade dall'era sperimentale. Il codice scritto per l'API sperimentale 1.8.x portava annotazioni @OptIn(ExperimentalEncodingApi::class) e contava sul padding opzionale. Da 2.0.20 in poi, lo stesso input può lanciare. La correzione è PRESENT_OPTIONAL, oppure ripulire gli input prima che arrivino al decoder.
  • Abbinare lo schema al produttore sbagliato. Una parte di JWT decodificata con Base64.Default fallisce sui suoi caratteri - e _; un payload a alfabeto standard decodificato con UrlSafe fallisce su + e /. L'eccezione nomina il simbolo esatto, ma la correzione è sapere da dove viene la stringa.
  • Il buco del charset. decodeToString() è solo UTF-8, senza overload per altre codifiche. Se il mittente ha usato Latin-1 o Windows-1252, prevedi String(bytes, charset) sulla JVM, e aspettati caratteri di sostituzione U+FFFD come sintomo quando dimentichi.
  • Il compilatore del pacchetto della distribuzione. apt install kotlin su Debian e Ubuntu fornisce 1.3.31, di prima che questa API esistesse. Se i tuoi esempi si rifiutano all'improvviso di compilare a causa di "unresolved reference", controlla quale compilatore c'è davvero nel PATH.

Buone pratiche per la decodifica

  • Prima decodifica nei byte, poi interpreta. Tieni Base64.decode e la conversione in testo come passi separati. Questo rende il charset esplicito, mantiene i payload binari binari, e rende i test banali: confronta array di byte, non stringhe.
  • Scegli l'istanza che corrisponde al produttore. JWT e dati destinati a URL significano UrlSafe; email e file PEM significano Mime o Pem; tutto il resto parte da Default. I decoder tolleranti sono per input notoriamente sporchi, non una rete di sicurezza generale.
  • Normalizza l'input non fidato una volta, a costo basso. Un trim() e, dove il formato è noto per essere pulito, una rimozione degli spazi bianchi, prima di una decodifica rigorosa catturano più fallimenti reali di qualsiasi quantità di try-catch. Un piccolo helper con ripiego PRESENT_OPTIONAL è un buon pattern per valori da sorgenti sconosciute.
  • Calcola la dimensione prima di allocare. L'output decodificato è al massimo tre quarti della lunghezza dell'input (quattro simboli portano tre byte), quindi un rapido controllo di lunghezza ti dice la dimensione di destinazione prima di decodificare, ed è esattamente quello che vuoi prima di riempire un buffer pre-allocato o di accettare una stringa di diversi megabyte.
  • Fidati del messaggio d'errore. La libreria standard riporta il simbolo, il suo codice e il suo indice. Registra nei log il vicinato di quell'indice per gli input non fidati e smetti di indovinare.
  • Non decodificare per nascondere cose, e non decodificare per dimostrare cose. Il Base64 è una codifica di trasporto. Non aggiunge segretezza né integrità; se ti serve una delle due, è compito della crittografia, non del decoder.

Come il Base64 è entrato in Kotlin

Il Base64 è più vecchio del Kotlin di qualche decennio - la specifica MIME che ha dato la regola delle righe da 76 caratteri risale al 1993, e l'alfabeto stesso a RFC di metà anni Novanta - ma la storia specifica di Kotlin è corta e recente. Il pacchetto kotlin.io.encoding è arrivato in Kotlin 1.8.20 nell'aprile 2023, portando Base64 con tre istanze - Default, UrlSafe e Mime - dietro l'annotazione @ExperimentalEncodingApi, insieme alle estensioni streaming solo-JVM che sono ancora sperimentali oggi. Per due anni, usarlo significava una riga opt-in in ogni funzione e la piccola possibilità che l'API si spostasse.

Kotlin 2.2.0, rilasciato nel giugno 2025, ha cambiato il contratto. L'intera API è diventata stabile in un unico rilascio, e l'istanza Pem si è unita alla famiglia (la variante della RFC 1421, righe da 64 caratteri, usata nel giro della PKI). La severità che fa sì che il codice con padding opzionale dell'era 1.8 richieda attenzione dopo un upgrade è arrivata un passo prima: nel 2.0.20, quando withPadding con i suoi quattro valori PaddingOption ha sostituito il vecchio comportamento fisso e il decoder ha iniziato a richiedere il padding. Il rilascio 2.2 ha anche stabilizzato la classe sorella HexFormat in kotlin.text, l'API di formattazione hex che è sperimentale da Kotlin 1.9, quindi le codifiche testuali a livello di byte hanno ora una casa stabile nella libreria standard. E in nota di manutenzione: da Kotlin 2.4.0 la libreria standard JVM esce con una finestra di supporto di 18 mesi per riga di rilascio, che è un motivo in più perché un progetto sulla riga attuale 2.4.x possa trattare questa API come un punto fisso e non mobile.

Fatti divertenti

  • L'oggetto companion fa un lavoro. Poiché Default è il companion di Base64, il nome della classe fa anche da istanza di default: Base64.decode(x) e Base64.Default.decode(x) sono la stessa chiamata. È la ragione per cui l'esempio di due righe in cima a questo articolo resta di due righe.
  • La decodifica di stringhe riceve un trucco di velocità sulla JVM. Il loop di decodifica comune lavora sui byte, ma le stringhe Kotlin sono sequenze di caratteri. L'implementazione JVM aggira la conversione reinterpretando i caratteri di una String come valori ISO-8859-1 a byte singolo prima che parta il loop condiviso - un trucco che i commenti del codice sorgente dichiarano fino a dieci volte più veloce del percorso comune, ed è per questo che decode(String) sembra istantaneo persino su payload lunghi.
  • Il nome del pacchetto è un indizio. Troverai questa API in kotlin.io.encoding, non in kotlin.text, perché il punto è che i dati sono byte - input e output hanno la forma dell'I/O, e il testo è solo quello che succede al risultato dopo.
  • I messaggi d'errore includono il codice del carattere. Symbol 'e'(145) riporta il valore del simbolo colpevole in ottale, non solo il suo glifo. Comodo quando il colpevole è uno spazio bianco: ' '(40) ti dice che era uno spazio molto prima che te lo sospetti.
  • PEM è arrivata in ritardo. Base64.Pem non faceva parte dell'API originale 1.8.20; è comparsa con la stabilizzazione 2.2. Se un post di blog del 2023 o 2024 elenca solo tre istanze, non è sbagliato - è solo due rilasci fuori data.
  • È scritto per piattaforma, non delegato. La libreria standard implementa il codec separatamente per ogni target con funzioni expect/actual. Sulla JVM c'è persino un'ottimizzazione commentata che affiderebbe il lavoro a java.util.Base64, disattivata dietro un issue aperto del compilatore, ed è per questo che il comportamento dell'implementazione Kotlin è il comportamento di riferimento su ogni piattaforma.

In chiusura

Decodificare Base64 in Kotlin si riduce a una lista corta di scelte deliberate: l'istanza che corrisponde a dove sono venuti i dati, la modalità di padding che corrisponde a come sono stati inviati, il buffer o lo stream che corrisponde a quanto sono grossi, e il charset che corrisponde a cosa significano. La libreria standard ti consegna tutte e quattro come funzioni semplici senza dipendenze, e i suoi messaggi d'errore sono abbastanza specifici da rendere un fallimento una diagnosi, non un mistero. La direzione opposta - scegliere lo schema giusto, il padding e l'a capo delle righe quando sei tu a produrre il Base64 - ha le sue decisioni e le sue insidie, e l'articolo correlato sul sito gemello copre la codifica Base64 in Kotlin in profondità.

Ultimo aggiornamento: 2026-09-08

Articolo correlato: Codifica Base64 in Kotlin: una guida completa