Você tem que lidar com o formato Base64? Então esse site é perfeito para você! Use o nosso ferramenta online super útil para codificar ou decodificar os seus dados.

Codificação Base64 em Dart: um guia completo

Você tem bytes, e precisa de uma string. O payload pode ser um arquivo, uma credencial de autenticação, um token de configuração ou um blob binário viajando dentro de um documento JSON, e o canal só aceita texto. O Base64 é a troca que resolve isso: cada três bytes de entrada viram quatro caracteres de um alfabeto de 64 caracteres, então a saída sempre é um múltiplo limpo de quatro e sempre segura em mundos só de texto. O preço é fixo em 33 por cento a mais de caracteres, e o formato acrescenta um ou dois caracteres de padding = no final quando o último bloco fica curto. Este guia é a receita em Dart para fazer essa troca corretamente.

Nada para instalar. O Base64 vem no dart:convert desde o Dart 1.13 em 2015, e a API está estável desde então; os dois alfabetos, padrão e URL-safe, estão disponíveis há mais de uma década. A página inicial passeia pelo formato em profundidade; aqui está o lado da codificação do trabalho: a superfície de API completa, a disciplina de bytes primeiro que evita o bug mais comum, as escolhas de padding e alfabeto, e os trabalhos do mundo real: JWTs, data URIs, upload de arquivos, cabeçalhos HTTP, MIME, configuração, streams e linha de comando. A decodificação, a direção inversa, tem o seu próprio guia, linkado no final.

Um import, dois alfabetos, uma regra de padding

A superfície pública inteira para codificação mora no dart:convert:

Entrada Alfabeto Quando usar
base64Encode(bytes) padrão: A-Z a-z 0-9 + /, com padding APIs, MIME, Basic auth, a maioria dos consumidores
base64UrlEncode(bytes) URL-safe: A-Z a-z 0-9 - _, ainda com padding URLs, nomes de arquivo, JWTs, IDs de objeto
base64.encode(bytes) padrão, idêntico à chamada de nível superior transformações de stream e pipelines de codec
Base64Encoder().convert(bytes) padrão você quer uma instância de codificador com nome

Duas regras cobrem todas as quatro linhas. Primeiro, a entrada tem que ser uma lista de valores de bytes, inteiros de 0 a 255; qualquer outra coisa, incluindo negativos ou 256 para cima, lança um ArgumentError que nomeia o índice ruim. Segundo, a saída sempre vem com padding: não existe flag, construtor ou opção que produza saída sem padding, porque o padding do formato é uma propriedade dos dados, e as especificações que querem ele fora o removem como uma etapa separada e documentada. O menor exemplo possível, do início ao fim:

import 'dart:convert';
void main() {
  final text = 'Dart is open source';
  final bytes = utf8.encode(text);
  final encoded = base64Encode(bytes);
  print(encoded); // RGFydCBpcyBvcGVuIHNvdXJjZQ==
}

Bytes primeiro: a ordem que te salva

O bug mais comum de base64 em Dart não é sobre base64 de jeito nenhum. É sobre a ordem das operações. Uma String em Dart é uma sequência de unidades de código UTF-16, e chamar base64Encode(text.codeUnits) empacota essas unidades de 16 bits, não os bytes que o receptor espera. Para ASCII puro as duas coisas coincidem por acaso, que é por que o bug se esconde até o primeiro caractere acentuado, emoji ou texto CJK chegar. Aí o codificador recusa o trabalho, porque uma unidade de código como 0x4e16 não é um valor de byte:

import 'dart:convert';
void main() {
  final message = 'Héllo Wörld 世界';
  print(utf8.encode(message).length); // 20
  print(message.codeUnits.length); // 14
  print(base64Encode(utf8.encode(message)));
  try {
    base64Encode(message.codeUnits);
  } on ArgumentError catch (e) {
    print(e);
  }
}

O ArgumentError aponta o índice ofensivo exato, então a falha é barulhenta em vez de silenciosa. A disciplina para manter: decidir o que são os bytes antes de falar com o base64. Texto passa por uma codificação nomeada, utf8.encode para dados modernos, e o List<int> resultante é o que se empacota. Bytes de um arquivo ou de um socket de rede já chegam como Uint8List, que é a forma certa para o codificador sem qualquer conversão.

Padding: o trabalho do codificador

O Base64 mapeia grupos de três bytes para quatro caracteres, então um payload cujo comprimento não é múltiplo de três deixa um grupo parcial no final. O formato marca esse déficit com caracteres =: um byte de entrada vira quatro caracteres mais dois pads, dois bytes viram quatro caracteres mais um pad, três bytes viram exatamente quatro caracteres. O codificador do Dart faz isso por você, incondicionalmente:

import 'dart:convert';
void main() {
  print(base64Encode([0x41])); // QQ==
  print(base64Encode([0x41, 0x42])); // QUI=
  print(base64Encode([0x41, 0x42, 0x43])); // QUJD
}

Esse comportamento incondicional é um recurso: a saída sempre é uma string base64 legal e autoexplicativa. Quando uma especificação pede a variante sem padding, e JWTs são o motivo usual, o corte é o seu passo explícito e visível, não uma configuração de biblioteca:

base64UrlEncode(bytes).replaceAll('=', '')

Coloque o corte onde está a fronteira da especificação, nomeie ele e documente. O lado da decodificação dessa troca, incluindo como entrada danificada ou cortada é reparada, é coberto no guia de decodificação.

Base64 URL-safe

O alfabeto padrão contém +, / e =, e esses três caracteres colidem com a sintaxe de URL: separadores de query, separadores de caminho e delimitadores de parâmetro. O alfabeto URL-safe, padronizado como base64url na RFC 4648, troca + por - e / por _, então a saída pode morar num segmento de caminho, num valor de query ou num nome de arquivo sem escape. Aqui está a diferença em bytes que exercitam os dois caracteres trocados:

import 'dart:convert';
void main() {
  final tricky = [0xfb, 0xff, 0xfe, 0xf9];
  print(base64Encode(tricky)); // +//++Q==
  print(base64UrlEncode(tricky)); // -__--Q==
}

Escolha pelo consumidor, não pelo gosto. Se o valor vai viver numa URL, num JWT ou num nome de arquivo, codifique com base64UrlEncode e remova o padding se a especificação for sem padding. Se o valor vai ser um corpo MIME, um cabeçalho de Basic auth ou um campo de um contrato de API que diz "base64", use o alfabeto padrão, porque base64 sem qualificativo significa o padrão. Os dois alfabetos não são intercambiáveis aos olhos de consumidores estritos: um servidor esperando base64 padrão pode rejeitar um payload com - com um 400 e nada mais útil.

Charsets: quais bytes você está empacotando?

Quando a entrada é texto, a etapa de codificação decide quais bytes o base64 vai ver, e o consumidor assume um charset do outro lado. Se a sua suposição e a do consumidor divergirem, a saída é um base64 perfeitamente válido dos bytes errados, o pior tipo de bug, porque nada lança erro. Para qualquer troca moderna, UTF-8 é o padrão; as outras codificações de um byte existem para dados legados:

Codificação Quando usar Codifique com
utf8 texto moderno, JSON, qualquer coisa na web utf8.encode(text)
latin1 dados ocidentais legados de um byte latin1.encode(text)
ascii texto puro de 7 bits ascii.encode(text)
import 'dart:convert';
void main() {
  final modern = base64Encode(utf8.encode('Héllo'));
  final legacy = base64Encode(latin1.encode('Héllo'));
  print(modern); // SMOpbGxv
  print(legacy); // SOlsbG8=
}

Palavra igual, bytes diferentes, base64 diferente. Repare nos comprimentos: o UTF-8 precisa de seis bytes para Héllo porque o acento é uma sequência de dois bytes, enquanto o Latin-1 cabe em cinco. Se o consumidor decodificar com a codificação que você não usou, ele ganha mojibake, e vai parecer que os dados foram corrompidos no trânsito quando na verdade foram corrompidos na intenção.

JWTs: escrevendo o token

Um JSON Web Token é três partes base64url unidas por pontos: cabeçalho, payload, assinatura. A RFC 7515 fixa dois detalhes: o alfabeto é URL-safe, e o padding é omitido, porque o token é desenhado para morar em URLs e cabeçalhos. A assinatura para o algoritmo HS256 é o HMAC-SHA256 de header.payload, ele mesmo em base64url sem padding. Montar na mão com o pacote crypto leva poucas linhas, e é mais transparente do que parece:

import 'dart:convert';
import 'package:crypto/crypto.dart';
String base64UrlNoPadding(List<int> bytes) {
  return base64UrlEncode(bytes).replaceAll('=', '');
}
String createJwt(Map<String, dynamic> header, Map<String, dynamic> payload,
    List<int> secretKey) {
  final signingInput =
      '${base64UrlNoPadding(utf8.encode(jsonEncode(header)))}.'
      '${base64UrlNoPadding(utf8.encode(jsonEncode(payload)))}';
  final mac = Hmac(sha256, secretKey).convert(utf8.encode(signingInput));
  final signature = base64UrlNoPadding(mac.bytes);
  return '$signingInput.$signature';
}
void main() {
  final token = createJwt(
    {'alg': 'HS256', 'typ': 'JWT'},
    {'sub': 'user-42', 'exp': 1893456000},
    utf8.encode('a-32-byte-secret-key-0123456789'),
  );
  print(token);
}

A assinatura é calculada sobre os bytes exatos que foram empacotados, então desde que você assine a mesma string que emite, a verificação do outro lado é a repetição dos mesmos passos. Três avisos. O pacote jwt antigo no pub.dev é de 2014 e antecede a null safety; a resposta que funciona no ecossistema é fazer o que está mostrado aqui com o crypto. Nunca emita um token com alg: none, e nunca deixe um cliente escolher o algoritmo. E lembre-se de que o payload é legível por qualquer um, então inclua só o que o token precisa provar.

Data URIs: embarcando arquivos dentro de texto

Uma data URI, definida pela RFC 2397, é uma URL cujo payload é o próprio dado. Conteúdo binário dentro de uma data URI é codificado em base64, que é por que o formato aparece em todo lugar onde documentos de texto precisam embutir imagens, fontes ou anexos: atributos HTML, CSS, JSON, arquivos de configuração. O Dart consegue montar as URIs nativamente, sem nenhum pacote de URI:

import 'dart:convert';
import 'dart:io';
Future<void> main() async {
  final png = await File('icon.png').readAsBytes();
  final imageUri = Uri.dataFromBytes(png, mimeType: 'image/png');
  print(imageUri); // data:image/png;base64,iVBOR...
  final note = Uri.dataFromString('Hello, Dart!');
  print(note); // data:,Hello,%20Dart!
}

Uri.dataFromBytes codifica em base64 por padrão (tem um opt-in percentEncoded: true para a outra forma), que é a codificação certa para binário. Uri.dataFromString faz percent-encoding por padrão, porque texto curto fica mais curto assim, e aceita a flag base64: true quando você quer a forma com bytes empacotados. O percalço prático é a escala: o payload viaja junto dentro do documento, com sobrecarga de 33 por cento, então data URIs são para assets pequenos, ícones e miniaturas, não para embarcar megabytes por CSS.

Arquivos: empacotando bytes para canais de texto

O trabalho do dia a dia: um arquivo que precisa viajar por JSON, um arquivo de configuração ou qualquer transporte só de texto. O padrão é ler bytes, codificar, embutir:

import 'dart:convert';
import 'dart:io';
Future<void> main() async {
  final image = await File('photo.jpg').readAsBytes();
  final encoded = base64Encode(image);
  final upload = jsonEncode({
    'name': 'photo.jpg',
    'size': image.length,
    'data': encoded,
  });
  print('payload ${upload.length} chars for ${image.length} bytes');
}

O número para guardar na cabeça é o crescimento: um arquivo de 2.000 bytes vira 2.668 caracteres base64, e um pouco mais quando as chaves do JSON entram. Dois percalços. Primeiro, confira que a sua entrada não está já codificada: codificar em base64 uma string que já é base64 é o bug clássico de codificação dupla, e ele decodifica "com sucesso" para outro muro de base64. Segundo, se o canal pode carregar binário, que é para isso que o multipart/form-data existe, carregue binário: ele é um quarto menor, e o imposto do base64 é puro desperdício.

HTTP e APIs: cabeçalhos e payloads

O trabalho de codificação mais familiar do HTTP é o cabeçalho Authorization: Basic: a palavra Basic, um espaço, e o base64 de alfabeto padrão de username:password:

import 'dart:convert';
import 'package:http/http.dart' as http;
Future<void> main() async {
  final credentials = base64Encode(utf8.encode('octocat:secret'));
  final client = http.Client();
  final response = await client.get(
    Uri.parse('https://httpbin.org/basic-auth/octocat/secret'),
    headers: {'Authorization': 'Basic $credentials'},
  );
  print(response.statusCode);
  client.close();
}

Com o pacote http, a um dart pub add http de distância, o cabeçalho é só uma string na requisição. O percalço é o enquadramento de segurança: o base64 aqui é ofuscação, não proteção. Qualquer pessoa consegue reverter em um passo, que é exatamente por que Basic auth só cabe em conexões TLS, onde é o transporte, não a codificação, que faz a proteção. Para campos de payload de API, siga o contrato: se diz base64, é o alfabeto padrão com padding, e a variante URL-safe é outra coisa que consumidores estritos vão rejeitar.

E-mail e MIME: quebrando em 76

O MIME, o sistema que deixa o e-mail carregar binário, usa base64 como uma codificação de transferência de conteúdo, e a RFC 2045 especifica que as linhas codificadas não podem passar de 76 caracteres, com CRLF entre elas. O limite é uma convenção MIME - 76 mais CRLF cabe com folga num display de 80 colunas - e todo codificador conforme quebra a linha. O codificador do Dart produz uma string sem quebras, então a quebra de linha é uma etapa curta de pós-processamento:

import 'dart:convert';
String wrapForMime(String base64Text, [int lineLength = 76]) {
  final buffer = StringBuffer();
  for (var i = 0; i < base64Text.length; i += lineLength) {
    final end = i + lineLength > base64Text.length
        ? base64Text.length
        : i + lineLength;
    buffer
      ..write(base64Text.substring(i, end))
      ..write('\r\n');
  }
  return buffer.toString();
}
void main() {
  final encoded = base64Encode(utf8.encode('Hello from an email attachment'));
  print(wrapForMime(encoded));
}

Quebre a string pronta, padding incluído, e deixe a linha final ter o comprimento que tiver, até 76. A única coisa a não fazer é remover o padding antes de quebrar na esperança de salvar um caractere: os pads fazem parte do conteúdo codificado, e um consumidor que remontar as linhas vai rejeitar o resultado sem eles.

Configuração: tornando segredos uma linha só

Tokens, chaves e credenciais que contêm aspas, quebras de linha ou outros caracteres incômodos às vezes são codificados em base64 para caberem numa linha de configuração ou numa variável de CI limpinha. O enquadramento honesto primeiro: isso é ofuscação, não criptografia, e qualquer coisa que chegar a um repositório ou a um log é pública. Use o padrão para organização, nunca para sigilo. Codificar o valor é uma chamada:

import 'dart:convert';
String forEnvFile(String secret) {
  return base64Encode(utf8.encode(secret));
}
void main() {
  final line = 'API_TOKEN_B64=${forEnvFile('sk-live-abc123')}';
  print(line); // API_TOKEN_B64=c2stbGl2ZS1hYmMxMjM=
}

O valor então mora num arquivo .env, num segredo de CI ou num define de compilação, e volta como texto simples depois de uma decodificação. Se o segredo precisa ser protegido no trânsito ou em repouso, vá a um gerenciador de segredos ou a uma biblioteca de criptografia; o trabalho do base64 aqui é manter o tratamento de texto do pipeline simples, nada mais.

Streams: codificando além das fronteiras dos blocos

Quando os bytes chegam em blocos, uma leitura de rede, um arquivo processado em pedaços, o codificador dá conta sem você alinhar nada. O codec carrega o grupo parcial pelas fronteiras dos blocos, então o tamanho dos blocos não precisa ser múltiplo de três:

import 'dart:convert';
import 'dart:typed_data';
Future<void> main() async {
  final data = Uint8List(100000);
  for (var i = 0; i < data.length; i += 31) {
    data[i] = i % 256;
  }
  final chunks = <List<int>>[data.sublist(0, 777), data.sublist(777)];
  final encoded = await Stream.fromIterable(chunks)
      .transform(base64.encoder)
      .join();
  print('in: ${data.length}, out: ${encoded.length}'); // in: 100000, out: 133336
}

Dois blocos de tamanho incômodo, 777 e 99.223 bytes, produzem uma única string correta de 133.336 caracteres, porque o codificador estaciona os bits sobrando de cada grupo incompleto até o próximo bloco chegar, e emite o padding só no final. Se você prefere sinks, o base64.encoder.startChunkedConversion te dá a mesma máquina de estados como um ByteConversionSink (você alimenta com blocos de bytes, ele emite strings), que é o encaixe natural para escrever saídas grandes num arquivo ou num socket sem nunca juntar uma string gigante.

Big data: vazão e memória

A matemática do tamanho é exata e vale a pena guardar: o comprimento da saída é o comprimento da entrada dividido por três, arredondado para cima, vezes quatro. Um, dois ou três bytes custam quatro caracteres; a partir daí é uma sobrecarga plana de 33 por cento. A fórmula, para quando você precisar reservar buffers ou reportar progresso:

import 'dart:convert';
int encodedLength(int n) => (n + 2) ~/ 3 * 4;
void main() {
  print(encodedLength(100000)); // 133336
}

Velocidade não é a restrição; o codificador é uma passada única de tabela de busca que lida com megabytes em milissegundos. As restrições são o imposto de tamanho em si, cobrado no fio e na memória, e o fato de a forma codificada ser uma string. Guarde as duas na cabeça em escala: para payloads que podem crescer muito, codifique em stream como mostrado acima em vez de acumular uma lista gigante e uma string gigante, e para transferências repetidas dos mesmos dados, pergunte se o canal tem um modo binário, porque 33 por cento é um acréscimo permanente que nenhum algoritmo reembolsa.

O codificador de linha de comando

A VM faz uma CLI limpa a partir do codificador. Esta ferramenta lê um argumento de arquivo ou a entrada padrão e imprime a codificação de alfabeto padrão:

import 'dart:convert';
import 'dart:io';
Future<void> main(List<String> args) async {
  final bytes = await _read(args);
  stdout.writeln(base64Encode(bytes));
}
Future<List<int>> _read(List<String> args) async {
  if (args.isNotEmpty) {
    return File(args[0]).readAsBytes();
  }
  final all = <int>[];
  await for (final chunk in stdin) {
    all.addAll(chunk);
  }
  return all;
}

Salve como bin/encode.dart e rode dart run bin/encode.dart photo.jpg > photo.b64, ou use pipe com cat config | dart run bin/encode.dart. O companheiro, um decodificador que lê e achata, é o primeiro exemplo no guia de decodificação, e juntos os dois scripts formam um toolset pequeno, mas genuinamente útil, para mover binário por canais de texto.

Percalços que mordem na saída

  • A armadilha do codeUnits. base64Encode(text.codeUnits) empacota unidades UTF-16, não bytes; funciona para ASCII e lança ArgumentError na primeira unidade de código acima de 255. Sempre codifique texto com uma codificação nomeada primeiro.
  • Alfabeto errado. Alimentar um consumidor que espera o alfabeto padrão com saída URL-safe é um 400 esperando acontecer. Decida o alfabeto pela especificação, codifique uma vez e não converta depois.
  • Suposições de padding. O Dart sempre dá padding. Se a especificação quer sem padding, remova com replaceAll('=', '') como etapa explícita na fronteira, e diga isso no contrato.
  • Deriva de charset. Codificar bytes Latin-1 para um consumidor que decodifica UTF-8 produz um base64 válido dos dados errados. Nada lança erro; o texto é só errado.
  • Codificação dupla. Codificar em base64 um valor que já é base64, um token copiado de outra configuração, é o bug clássico de "decodifica para outro muro de base64".
  • Ilusão de privacidade. Base64 é um formato, não um cifrador. Se o modelo de ameaça envolve um leitor, a resposta é criptografia, não codificação.
  • Pacotes desatualizados. O pacote jwt de longa data no pub.dev antecede a null safety; para trabalho com JWT, o crypto mais as poucas linhas acima é o caminho mantido.

Quando procurar outra coisa

  • Upload de arquivos por HTTP. Use multipart/form-data; ele carrega bytes crus, então você pula o imposto de 33 por cento inteiro.
  • Payloads grandes ou repetitivos. Comprima primeiro, codifique depois: base64 de texto comprimido é dramaticamente menor que base64 do texto, e o lado que descomprime já conhece o formato.
  • Texto curto dentro de URLs. Percent-encoding é mais curto para uma mão cheia de caracteres e mantém o valor legível por humanos; data URIs até fazem isso por você por padrão.
  • Saída de debug e logs. Hex é 50 por cento mais longo que base64 (o dobro do tamanho bruto, base64 só quatro terços), mas muito mais fácil de varrer, diffar e entregar a um colega; para trechos binários em logs ele costuma vencer.

Boas práticas, a lista do codificador

  • Codifique bytes, nunca unidades de código; texto passa por uma codificação nomeada primeiro.
  • Escolha o alfabeto pela especificação do consumidor antes de escrever a chamada.
  • Remova padding só onde a especificação diz sem padding, como passo visível na fronteira.
  • Deixe o charset explícito no contrato; não assuma nada sobre o outro lado.
  • Use stream para qualquer coisa que possa crescer muito.
  • Trate o base64 como um formato para canais só de texto, nunca como uma proteção para dados sensíveis.

Uma breve história de dois alfabetos

O formato que você acabou de usar é mais antigo que qualquer release do Dart, e as escolhas de alfabeto que você tem disponíveis foram padronizadas décadas antes do Dart chegar. A versão curta:

  • 1993, RFC 1521: o MIME introduz o base64 como codificação de transferência de conteúdo para e-mail, com o alfabeto padrão de 64 caracteres e o limite de linha de 76 caracteres no qual este artigo quebra. O trabalho do formato, carregar binário por canais de texto, é desta época.
  • 1996, RFC 2045: a RFC que tornou o MIME obsoleto e estabeleceu as regras de padding e de comprimento de linha do base64 como padrão durável.
  • 2006, RFC 4648: a codificação é puxada do MIME e padronizada sozinha, adicionando o alfabeto URL-safe e o conselho de que decodificadores devem rejeitar entrada inválida. A escolha de dois alfabetos que você tem no Dart vem deste documento.
  • 2015, RFC 7515: as JSON Web Signatures especificam base64url sem padding, a convenção por trás de todo JWT.
  • Novembro de 2015, Dart 1.13: o base64 chega no dart:convert; a variante URL-safe segue no Dart 1.16 na primavera seguinte, e as chamadas de nível superior base64Encode e base64UrlEncode que você usou acima aterrissam no Dart 2.0 em 2018.
  • Hoje, Dart 3.13: os dois alfabetos, sempre com padding, a um import de distância, a mesma máquina estrita e simples desde 2015.

A sobrecarga de 33 por cento também não mudou desde 1993. É uma propriedade da matemática, quatro símbolos para três bytes, e toda implementação que você já usou, em qualquer linguagem, paga a taxa de forma idêntica.

Fatos curiosos da bancada de codificação

  • O codificador não pode ser desligado: não existe flag para saída sem padding no SDK, que é por que "remover os pads" é sempre o seu código, na sua fronteira, em plena vista.
  • Um byte vira quatro caracteres: base64Encode([65]) é QQ==. A string base64 mais curta possível tem quatro caracteres, e só os dois primeiros carregam informação; os dois últimos são padding.
  • Os dois codificadores do Dart dão padding, até o base64UrlEncode. O "sem padding" do base64url é uma convenção do consumidor da RFC 7515, não uma propriedade do alfabeto.
  • O alfabeto padrão foi desenhado para ser imprimível em 7 bits, e ele segue sendo o padrão desde então; o fato de + e / terem acabado ganhando substitutos URL-safe é um sinal de quão central ele se tornou, não um defeito.
  • Os mesmos 20 bytes UTF-8 de Héllo Wörld 世界 empacotam para SMOpbGxvIFfDtnJsZCDkuJbnlYw=, enquanto as 14 unidades de código da string derrubariam o codificador no índice 12. Mesmos caracteres, duas saídas completamente diferentes, uma delas um erro.
  • Arquivos PEM, os blocos -----BEGIN CERTIFICATE----- de todo certificado TLS, são base64 quebrado a cada 64 caracteres com cabeçalhos, e o formato é de 1987, seis anos antes do MIME publicar o base64 para e-mail.

Agora você tem o lado inteiro da codificação: a superfície de API, a disciplina de bytes primeiro, as decisões de padding e alfabeto, e os padrões funcionais para JWTs, data URIs, arquivos, HTTP, MIME, configuração, streams e o shell. A direção inversa, desmontar uma dessas strings, com toda a estriteza do decodificador, a surpresa do percent-escape e as ferramentas de reparo, é coberta no guia de decodificação Base64, linkado logo abaixo.

Última atualização: 2026-09-08

Artigo relacionado: Decodificação Base64 em Dart: um guia completo