Codificar e decodificar uma string
O Base64.encode recebe um ByteArray e retorna uma String. O encodeToByteArray() e o decodeToString() cuidam do UTF-8 nas duas direções:
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
O próprio Base64 é a instância padrão (Base64.Default). Também existem Base64.UrlSafe e Base64.Mime.
Em Kotlin anterior ao 2.2, a API precisa de @OptIn(ExperimentalEncodingApi::class). Se você está preso a uma versão antiga, o java.util.Base64 funciona igual ao de Base64 em Java:
java.util.Base64.getEncoder().encodeToString("hi".toByteArray()) // aGk=
Os usos típicos são headers HTTP e 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==
Base64 URL-safe e opções de preenchimento
O Base64.UrlSafe troca +/ por -_. O preenchimento é controlado pelo withPadding, que retorna uma nova instância configurada:
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()) // <<???>>
As quatro opções:
| Opção | Codificação | Decodificação |
|---|---|---|
PRESENT (padrão) | escreve = | exige = |
ABSENT | sem = | rejeita = |
PRESENT_OPTIONAL | escreve = | aceita as duas formas |
ABSENT_OPTIONAL | sem = | aceita as duas formas |
As instâncias padrão são rígidas nisso:
Base64.UrlSafe.decode("PDw_Pz8-Pg")
// IllegalArgumentException: The padding option is set to PRESENT,
// but the input is not properly padded
Para partes de JWT e outros tokens, ABSENT_OPTIONAL ou PRESENT_OPTIONAL costuma ser a escolha certa, já que os geradores não concordam sobre o preenchimento. Contexto: Base64 vs Base64URL e preenchimento explicado.
Por exemplo, para ler o payload de um JWT:
val jwt = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0In0.sig"
val url = Base64.UrlSafe.withPadding(Base64.PaddingOption.ABSENT_OPTIONAL)
println(url.decode(jwt.split(".")[1]).decodeToString()) // {"sub":"1234"}
Quebra de linha MIME
O Base64.Mime escreve linhas de 76 caracteres separadas por \r\n e ignora os separadores de linha ao decodificar:
Base64.Mime.encode("x".repeat(60).encodeToByteArray())
// "eHh4eHh4...eHh4" (76 chars) + "\r\n" + "eHh4"
Base64.Mime.decode("aGVs\r\nbG8K").decodeToString() // "hello\n"
Para comparar, cole a mesma entrada no codificador Base64 MIME.
Codificar e decodificar um arquivo
Arquivos pequenos cabem na memória:
import java.io.File
println(Base64.encode(File("hello.txt").readBytes())) // aGVsbG8K
File("copy.txt").writeBytes(Base64.decode("aGVsbG8K"))
Streaming de arquivos grandes
Na JVM, o encodingWith e o decodingWith envolvem streams. Eles ainda são experimentais no Kotlin 2.4, então exigem opt-in e imports próprios:
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) }
}
}
Ao fechar o stream de codificação, o que o bloco use faz por você, o último grupo e o preenchimento são escritos. A saída em streaming de um arquivo de teste de 300 KB foi idêntica à de Base64.encode(readBytes()), e a ida e volta devolveu os mesmos bytes. Se preferir um caminho não experimental, o java.util.Base64.getEncoder().wrap(outputStream) faz o mesmo trabalho.
Validação e mensagens de exceção
O decodificador do Kotlin é mais rígido que o do Java. Ele rejeita bits de preenchimento diferentes de zero, que o Java e a maioria das outras bibliotecas aceitam. Toda falha é uma IllegalArgumentException:
Entrada para Base64.decode | Mensagem |
|---|---|
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 |
O número entre parênteses é o código do caractere em octal (137 é _, 12 é uma quebra de linha). Um helper de validação cabe numa linha:
fun isBase64(s: String) = runCatching { Base64.decode(s) }.isSuccess
Para ver onde uma string específica quebra, use o validador de Base64; o guia de erros de Base64 lista as causas habituais.
Armadilhas
- Entrada sem preenchimento com a instância padrão. Segmentos de JWT e muitos tokens não têm
=. ConfigurePRESENT_OPTIONALouABSENT_OPTIONALem vez de completar strings à mão. - Flag
DEFAULTdo Android.android.util.Base64.encodeToString(bytes, Base64.DEFAULT)insere uma quebra de linha a cada 76 caracteres e no final. Num header HTTP, isso quebra a requisição. UseNO_WRAP, ouNO_WRAP or URL_SAFE or NO_PADDINGpara tokens. - Charset do
String.toByteArray(). No Kotlin o padrão é UTF-8, ao contrário dogetBytes()do Java em JDKs antigos, então"héllo".toByteArray()é seguro. - Decodificar binário para string.
decodeToString()em bytes de imagem substitui sequências inválidas porU+FFFDe corrompe os dados. Mantenha oByteArray.
Notas de versão
- Kotlin 1.8.20:
kotlin.io.encoding.Base64adicionado como experimental. - Kotlin 2.0.20:
withPaddingePaddingOption. - Kotlin 2.2: a API principal se tornou estável; os helpers de stream da JVM continuam experimentais.
Perguntas frequentes
Ainda preciso de @OptIn(ExperimentalEncodingApi::class)?
Não para Base64.encode e Base64.decode no Kotlin 2.2 ou mais novo. Os helpers de stream encodingWith e decodingWith ainda exigem o opt-in no Kotlin 2.4.
Como decodificar Base64 sem preenchimento em Kotlin?
Use uma instância configurada: Base64.UrlSafe.withPadding(Base64.PaddingOption.ABSENT) para entrada sem preenchimento, ou PRESENT_OPTIONAL para aceitar as duas formas. As instâncias padrão exigem preenchimento.
Devo usar android.util.Base64 ou kotlin.io.encoding.Base64?
Em código novo, a API do Kotlin funciona em qualquer versão do Android porque faz parte da biblioteca padrão. O android.util.Base64 acrescenta quebras de linha a menos que você passe Base64.NO_WRAP, uma fonte frequente de headers quebrados.