Codificación Base64 en Dart: una guía completa
Tienes bytes y necesitas una cadena. El payload puede ser un archivo, una credencial de autenticación, un token de configuración, o un bulto binario viajando dentro de un documento JSON, y el canal solo acepta texto. El Base64 es la operación que lo resuelve: cada tres bytes de entrada se convierten en cuatro caracteres de un alfabeto de 64 caracteres, así que la salida siempre es un múltiplo limpio de cuatro y siempre es segura en mundos solo de texto. El precio es fijo, 33 por ciento más de caracteres, y el formato añade uno o dos caracteres de padding = al final cuando el último bloque sale corto. Esta guía es la receta de Dart para hacer esa operación correctamente.
No hay nada que instalar. El Base64 viene incluido en dart:convert desde Dart 1.13 en 2015, y la API ha estado estable desde entonces; los dos alfabetos, el estándar y el URL-safe, llevan más de una década disponibles. La página de inicio repasa el formato a fondo; aquí está la parte de codificación del trabajo: la superficie completa de la API, la disciplina de bytes primero que evita el bug más común, las decisiones de padding y alfabeto, y los trabajos de verdad: JWTs, data URIs, subidas de archivos, cabeceras HTTP, MIME, configuración, streams y línea de comandos. La decodificación, la dirección inversa, tiene su propia guía, enlazada al final.
Un solo import, dos alfabetos, una regla de padding
Toda la superficie pública para la codificación vive en dart:convert:
| Entrada | Alfabeto | Cuándo usarla |
|---|---|---|
base64Encode(bytes) |
estándar: A-Z a-z 0-9 + /, con padding |
APIs, MIME, autenticación Basic, la mayoría de consumidores |
base64UrlEncode(bytes) |
URL-safe: A-Z a-z 0-9 - _, aún con padding |
URLs, nombres de archivo, JWTs, IDs de objetos |
base64.encode(bytes) |
estándar, idéntico a la llamada de nivel superior | Transformaciones de streams y pipelines de codecs |
Base64Encoder().convert(bytes) |
estándar | Quieres una instancia de codificador con nombre |
Dos reglas cubren las cuatro filas. Primero, la entrada debe ser una lista de valores de byte, enteros de 0 a 255; cualquier otra cosa, incluidos negativos o 256 en adelante, lanza un ArgumentError que nombra el índice malo. Segundo, la salida siempre tiene padding: no hay flag, constructor ni opción que produzca salida sin padding, porque el padding del formato es una propiedad de los datos, y las especificaciones que no lo quieren lo quitan como un paso aparte y documentado. El ejemplo más pequeño posible, de principio a fin:
import 'dart:convert';
void main() {
final text = 'Dart is open source';
final bytes = utf8.encode(text);
final encoded = base64Encode(bytes);
print(encoded); // RGFydCBpcyBvcGVuIHNvdXJjZQ==
}
Bytes primero: el orden que te salva
El bug más común de base64 en Dart no tiene nada que ver con base64. Tiene que ver con el orden de las operaciones. Una String de Dart es una secuencia de unidades de código UTF-16, y llamar a base64Encode(text.codeUnits) empaqueta esas unidades de 16 bits, no los bytes que el receptor espera. Con ASCII puro los dos coinciden por casualidad, y por eso el bug se esconde hasta que llega el primer carácter acentuado, emoji o texto CJK. Entonces el codificador se niega al trabajo, porque una unidad de código como 0x4e16 no es un 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);
}
}
El ArgumentError señala el índice exacto que causa el fallo, así que el error es ruidoso en vez de silencioso. La disciplina a mantener: decide qué son los bytes antes de hablar con base64. El texto pasa por una codificación con nombre, utf8.encode para datos modernos, y la List<int> resultante es lo que se empaqueta. Los bytes de un archivo o un socket de red ya llegan como Uint8List, que es la forma correcta para el codificador sin ninguna conversión.
Padding: el trabajo del codificador
El Base64 mapea grupos de tres bytes a cuatro caracteres, así que un payload cuya longitud no es múltiplo de tres deja un grupo parcial al final. El formato marca esa falta con caracteres =: un byte de entrada se convierte en cuatro caracteres más dos de padding, dos bytes en cuatro caracteres más uno de padding, tres bytes en exactamente cuatro caracteres. El codificador de Dart hace esto por ti, incondicionalmente:
import 'dart:convert';
void main() {
print(base64Encode([0x41])); // QQ==
print(base64Encode([0x41, 0x42])); // QUI=
print(base64Encode([0x41, 0x42, 0x43])); // QUJD
}
Ese comportamiento incondicional es una ventaja: la salida es siempre una cadena base64 legal y autoexplicativa. Cuando una especificación pide la variante sin padding, y los JWT son la razón habitual, quitarlo es tu paso explícito y visible, no una configuración de la librería:
base64UrlEncode(bytes).replaceAll('=', '')
Coloca el paso de quitar donde esté la frontera de la especificación, ponle nombre y documéntalo. El lado del decodificador de esta operación, incluido cómo se repara una entrada dañada o sin padding, se cubre en la guía de decodificación.
Base64 URL-safe
El alfabeto estándar contiene +, / y =, y esos tres caracteres chocan con la sintaxis de URLs: separadores de consulta, separadores de ruta y delimitadores de parámetros. El alfabeto URL-safe, estandarizado como base64url en el RFC 4648, cambia + por - y / por _, así que la salida puede vivir en un segmento de ruta, un valor de consulta o un nombre de archivo sin escapes. Aquí tienes la diferencia en bytes que ejercitan los dos caracteres cambiados:
import 'dart:convert';
void main() {
final tricky = [0xfb, 0xff, 0xfe, 0xf9];
print(base64Encode(tricky)); // +//++Q==
print(base64UrlEncode(tricky)); // -__--Q==
}
Elige según el consumidor, no según el gusto. Si el valor va a vivir en una URL, un JWT o un nombre de archivo, codifica con base64UrlEncode y quita el padding si la especificación es sin padding. Si el valor va a ser un cuerpo MIME, una cabecera de autenticación Basic, o un campo en un contrato de API que dice "base64", usa el alfabeto estándar, porque base64 sin cualificar significa el estándar. Los dos alfabetos no son intercambiables a los ojos de los consumidores estrictos: un servidor que espere base64 estándar puede rechazar un payload que contenga - con un 400 y nada más útil.
Charsets: ¿qué bytes estás empaquetando?
Cuando la entrada es texto, el paso de codificación decide qué bytes verá el base64, y el consumidor supone un charset al otro lado. Si tu supuesto y el del consumidor difieren, la salida es base64 perfectamente válido de los bytes equivocados, el peor tipo de bug, porque nada lanza error. Para cualquier intercambio moderno, UTF-8 es el valor por defecto; las otras codificaciones de un solo byte existen para datos heredados:
| Codificación | Cuándo usarla | Codifica con |
|---|---|---|
utf8 |
Texto moderno, JSON, lo que sea en la web | utf8.encode(text) |
latin1 |
Datos occidentales heredados de un solo byte | latin1.encode(text) |
ascii |
Texto plano 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=
}
La misma palabra, bytes distintos, base64 distinto. Fíjate en las longitudes: UTF-8 necesita seis bytes para Héllo porque la tilde es una secuencia de dos bytes, mientras que Latin-1 la cabe en cinco bytes. Si el consumidor decodifica con la codificación que no usaste, obtiene mojibake, y parecerá que los datos se corrompieron en tránsito, cuando en realidad se corrompieron en la intención.
JWTs: escribiendo el token
Un JSON Web Token es tres partes base64url unidas por puntos: cabecera, payload, firma. El RFC 7515 fija dos detalles: el alfabeto es URL-safe y el padding se omite, porque el token está diseñado para vivir en URLs y cabeceras. La firma del algoritmo HS256 es el HMAC-SHA256 de header.payload, que es base64url sin padding. Hacerlo a mano con el paquete crypto requiere unas pocas líneas, y es más transparente de lo 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);
}
La firma se calcula sobre los bytes exactos que se empaquetaron, así que mientras firmes la misma cadena que emites, la verificación del otro lado es un repaso de los mismos pasos. Tres avisos. El paquete jwt antiguo de pub.dev es de 2014 y es anterior a la null safety; la respuesta que funciona en el ecosistema es hacer lo que se muestra aquí con crypto. Nunca emitas un token con alg: none, y nunca dejes que un cliente elija el algoritmo. Y recuerda que el payload es legible por cualquiera, así que incluye solo lo que el token debe probar.
Data URIs: enviando archivos dentro del texto
Un data URI, definido por el RFC 2397, es una URL cuyo payload son los datos en sí. El contenido binario dentro de un data URI se codifica en base64, y por eso el formato aparece en todas partes donde los documentos de texto necesitan incrustar imágenes, fuentes o adjuntos: atributos HTML, CSS, JSON, archivos de configuración. Dart puede construir los URIs de forma nativa, sin necesidad de ningún paquete de URIs:
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 en base64 por defecto (tiene la opción percentEncoded: true para la otra forma), que es la codificación correcta para binarios. Uri.dataFromString codifica por porcentaje por defecto, porque el texto corto es más corto así, y acepta un flag base64: true cuando quieres la forma empaquetada en bytes. La trampa práctica es la escala: el payload viaja dentro del documento, con un sobrecoste del 33 por ciento, así que los data URIs son para assets pequeños, iconos y miniaturas, no para enviar megabytes a través de CSS.
Archivos: empaquetar bytes para canales de texto
El trabajo de cada día: un archivo que debe viajar a través de JSON, un archivo de configuración, o cualquier transporte solo de texto. El patrón es leer bytes, codificar, incrustar:
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');
}
El número que debes tener en la cabeza es el crecimiento: un archivo de 2,000 bytes se convierte en 2,668 caracteres base64, y un poco más cuando se suman las claves JSON. Dos trampas. Primero, comprueba que tu entrada no esté ya codificada: codificar en base64 una cadena que ya es base64 es el clásico bug de doble codificación, y se "decodifica" con éxito en otro muro de base64. Segundo, si el canal puede llevar binarios, para eso existe multipart/form-data, lleva binarios: es un cuarto más pequeño, y el impuesto base64 es puro desperdicio.
HTTP y APIs: cabeceras y payloads
El trabajo de codificación más familiar en HTTP es la cabecera Authorization: Basic: la palabra Basic, un espacio, y el base64 del alfabeto estándar 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();
}
Con el paquete http, a un dart pub add http de distancia, la cabecera es solo una cadena en la petición. La trampa es el marco de seguridad: el base64 aquí es ofuscación, no protección. Cualquiera puede revertirlo en un paso, y por eso la autenticación Basic solo debe vivir en conexiones TLS, donde es el transporte, no la codificación, quien protege. Para los campos de payload de APIs, sigue el contrato: si dice base64, es el alfabeto estándar con padding, y la variante URL-safe es otra cosa que los consumidores estrictos rechazarán.
Correo y MIME: envolver en 76
El MIME, el sistema que permite al correo llevar binarios, usa base64 como codificación de transferencia de contenido, y el RFC 2045 especifica que las líneas codificadas no deben exceder 76 caracteres, con CRLF entre ellas. El límite es una convención MIME - 76 más CRLF caben cómodamente en una pantalla de 80 columnas - y todo codificador conforme envuelve. El codificador de Dart produce una sola cadena ininterrumpida, así que envolver es un corto paso de posprocesado:
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));
}
Envuelve la cadena terminada, padding incluido, y deja que la última línea sea tan larga como sea, hasta 76. Lo único que no debes hacer es quitar el padding antes de envolver con la esperanza de ahorrar un carácter: los rellenos son parte del contenido codificado, y un consumidor que reensamble las líneas rechazará el resultado sin ellos.
Configuración: secretos en una sola línea
Los tokens, claves y credenciales que contienen comillas, saltos de línea u otros caracteres incómodos a veces se codifican en base64 para encajar limpiamente en una línea de configuración o una variable de CI. Primero la formulación honesta: esto es ofuscación, no cifrado, y cualquier cosa que llegue a un repositorio o a un log es pública. Usa el patrón por orden, nunca por secreto. Codificar el valor es una sola llamada:
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=
}
El valor vive luego en un archivo .env, un secreto de CI o un define de tiempo de compilación, y vuelve como texto plano después de una decodificación. Si el secreto debe protegerse en tránsito o en reposo, acude a un gestor de secretos o a una librería de cifrado; el trabajo del base64 aquí es mantener simple el manejo de texto del pipeline, nada más.
Streams: codificación a través de los límites de los bloques
Cuando los bytes llegan en bloques, una lectura de red, un archivo procesado por bloques, el codificador lo maneja sin que tengas que alinear nada. El codec lleva el grupo parcial más allá de los límites entre bloques, así que los tamaños de bloque no necesitan ser múltiplos de tres:
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
}
Dos bloques de tamaño incómodo, 777 y 99,223 bytes, producen una cadena correcta de 133,336 caracteres, porque el codificador aparcó los bits sobrantes de cada grupo incompleto hasta que llega el siguiente bloque, y emite el padding solo al final. Si prefieres sinks, base64.encoder.startChunkedConversion te da la misma máquina de estados como ByteConversionSink (le das bloques de bytes, él emite cadenas), que es el ajuste natural para escribir salidas grandes a un archivo o un socket sin unir nunca una gran cadena.
Big data: caudal y memoria
La matemática del tamaño es exacta y merece la pena recordarla: la longitud de la salida es la longitud de la entrada dividida por tres, redondeada hacia arriba, por cuatro. Uno, dos o tres bytes cuestan cuatro caracteres; a partir de ahí es un sobrecoste plano del 33 por ciento. La fórmula, para cuando necesites reservar buffers o reportar progreso:
import 'dart:convert';
int encodedLength(int n) => (n + 2) ~/ 3 * 4;
void main() {
print(encodedLength(100000)); // 133336
}
La velocidad no es la limitación; el codificador es un solo paso de búsqueda en tabla que maneja megabytes en milisegundos. Las limitaciones son el impuesto de tamaño en sí, cobrado en el cable y en la memoria, y el hecho de que la forma codificada es una cadena. Ten ambas en cuenta a escala: para payloads que pueden crecer mucho, codifica por stream como se mostró arriba en vez de acumular una gran lista y una gran cadena, y para transferencias repetidas de los mismos datos, pregunta si el canal tiene un modo binario, porque el 33 por ciento es un recargo permanente que ningún algoritmo puede reembolsar.
El codificador de línea de comandos
La VM convierte el codificador en una CLI limpia. Esta herramienta lee un argumento de archivo o la entrada estándar e imprime la codificación del alfabeto estándar:
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;
}
Guárdalo como bin/encode.dart y ejecuta dart run bin/encode.dart photo.jpg > photo.b64, o pásalo por tubo con cat config | dart run bin/encode.dart. El compañero, un decodificador que lee y aplana, es el primer ejemplo de la guía de decodificación, y juntos los dos scripts son un pequeño pero genuinamente útil conjunto de herramientas para mover binarios a través de canales de texto.
Trampas que muerden en el camino de salida
- La trampa de codeUnits.
base64Encode(text.codeUnits)empaqueta unidades UTF-16, no bytes; funciona con ASCII y lanzaArgumentErroren la primera unidad de código sobre 255. Codifica siempre el texto primero con una codificación con nombre. - Alfabeto equivocado. Dar salida URL-safe a un consumidor que espera el alfabeto estándar es un 400 a la espera de producirse. Decide el alfabeto desde la especificación, codifica una vez y no conviertas después.
- Supuestos sobre el padding. Dart siempre pone padding. Si la especificación quiere sin padding, quítalo con
replaceAll('=', '')como paso explícito en la frontera, y dilo en el contrato. - Deriva de charset. Codificar bytes Latin-1 para un consumidor que decodifica UTF-8 produce base64 válido de los datos equivocados. Nada lanza error; el texto simplemente es incorrecto.
- Doble codificación. Codificar en base64 un valor que ya es base64, un token copiado de otra configuración, es el clásico bug de "se decodifica a otro muro de base64".
- Ilusión de privacidad. El Base64 es un formato, no un cifrador. Si el modelo de amenazas incluye a un lector, la respuesta es el cifrado, no la codificación.
- Pacquetes desactualizados. El veterano paquete
jwtde pub.dev es anterior a la null safety; para trabajo con JWT,cryptomás las pocas líneas de arriba es el camino mantenido.
Cuándo recurrir a otra cosa
- Subidas de archivos por HTTP. Usa
multipart/form-data; lleva bytes crudos, así que te saltas el impuesto del 33 por ciento por completo. - Payloads grandes o repetitivos. Comprime primero, codifica después: el base64 de texto comprimido con gzip es dramáticamente más pequeño que el base64 del texto, y el lado que descomprime ya conoce el formato.
- Texto corto dentro de URLs. La codificación por porcentaje es más corta para un puñado de caracteres y mantiene el valor legible para humanos; los data URIs incluso lo hacen por ti por defecto.
- Salida de depuración y logs. El hexadecimal es un 50 por ciento más largo que el base64 (el doble del tamaño bruto, el base64 solo cuatro tercios), pero es mucho más fácil de escanear, hacer diff y pasar a un compañero; para fragmentos binarios en logs normalmente gana.
Buenas prácticas, la lista del codificador
- Codifica bytes, nunca unidades de código; el texto pasa primero por una codificación con nombre.
- Elige el alfabeto desde la especificación del consumidor antes de escribir la llamada.
- Quita el padding solo donde la especificación dice sin padding, como paso visible en la frontera.
- Declara el charset explícitamente en el contrato; no supongas nada sobre el otro lado.
- Pasa por stream cualquier cosa que pueda crecer mucho.
- Trata el base64 como un formato para canales solo de texto, nunca como una protección para datos sensibles.
Una breve historia de dos alfabetos
El formato que acabas de usar es más antiguo que todas las versiones de Dart, y las opciones de alfabeto que tienes disponibles se estandarizaron décadas antes de que llegara Dart. La versión corta:
- 1993, RFC 1521: el MIME introduce base64 como codificación de transferencia de contenido para el correo, con el alfabeto estándar de 64 caracteres y el límite de línea de 76 caracteres en el que envuelve este artículo. El trabajo del formato, llevar binarios a través de canales de texto, tiene su origen aquí.
- 1996, RFC 2045: la obsolescencia del MIME que convirtió las reglas de padding y de longitud de línea de base64 en el estándar duradero.
- 2006, RFC 4648: la codificación se saca del MIME y se estandariza por su cuenta, añadiendo el alfabeto URL-safe y el consejo de que los decodificadores deben rechazar entradas inválidas. La elección de dos alfabetos que tienes en Dart viene de este documento.
- 2015, RFC 7515: las Firmas JSON Web especifican base64url sin padding, la convención detrás de cada JWT.
- noviembre de 2015, Dart 1.13: base64 llega a
dart:convert; la variante URL-safe sigue en Dart 1.16 la primavera siguiente, y las llamadas de nivel superiorbase64Encodeybase64UrlEncodeque usaste arriba llegan en Dart 2.0 en 2018. - Hoy, Dart 3.13: los dos alfabetos, siempre con padding, a un import de distancia, la misma máquina estricta y simple desde 2015.
El sobrecoste del 33 por ciento tampoco ha cambiado desde 1993. Es una propiedad de la matemática, cuatro símbolos por tres bytes, y cada implementación que vayas a usar, en cada lenguaje, lo paga de forma idéntica.
Datos curiosos del banco de codificación
- El codificador no se puede apagar: no hay flag para salida sin padding en el SDK, y por eso "quitar el padding" es siempre tu código, en tu frontera, a plena vista.
- Un byte se convierte en cuatro caracteres:
base64Encode([65])esQQ==. La cadena base64 más corta posible tiene cuatro caracteres, y solo los dos primeros llevan información; los dos últimos son padding. - Los dos codificadores de Dart ponen padding, incluso
base64UrlEncode. El "sin padding" del base64url es una convención del consumidor del RFC 7515, no una propiedad del alfabeto. - El alfabeto estándar fue diseñado para ser imprimible en 7 bits, y se ha mantenido como el predeterminado desde entonces; el hecho de que
+y/acabaran ganando sustitutos URL-safe es una señal de lo central que se volvió, no un defecto. - Los mismos 20 bytes UTF-8 de
Héllo Wörld 世界se empaquetan enSMOpbGxvIFfDtnJsZCDkuJbnlYw=, mientras que las 14 unidades de código de la cadena harían volar al codificador en el índice 12. Mismos caracteres, dos salidas completamente distintas, una de ellas un error. - Los archivos PEM, los bloques
-----BEGIN CERTIFICATE-----de cada certificado TLS, son base64 envuelto en 64 caracteres con cabeceras, y el formato data de 1987, seis años antes de que el MIME publicara base64 para el correo.
Ya tienes todo el lado de la codificación: la superficie de la API, la disciplina de bytes primero, las decisiones de padding y alfabeto, y los patrones funcionales para JWTs, data URIs, archivos, HTTP, MIME, configuración, streams y la shell. La dirección inversa, desmontar una de estas cadenas, con toda la estrictez del decodificador, su sorpresa del escape por porcentaje y sus herramientas de reparación, se cubre en la guía de decodificación Base64, enlazada justo debajo.
Última actualización: 2026-09-08
Artículo relacionado: Decodificación Base64 en Dart: una guía completa