Codificar y decodificar una cadena
Internamente, las cadenas de Dart son UTF-16, así que conviértelas a bytes UTF-8 con utf8.encode antes de codificar:
import 'dart:convert';
void main() {
final enc = base64Encode(utf8.encode('héllo wörld'));
print(enc); // aMOpbGxvIHfDtnJsZA==
print(utf8.decode(base64Decode(enc))); // héllo wörld
}
base64Encode(bytes) es una forma abreviada de base64.encode(bytes), y base64Decode(s) de base64.decode(s). Si los bytes decodificados no son UTF-8, utf8.decode lanza FormatException: Invalid UTF-8 byte; pasa allowMalformed: true para obtener caracteres de reemplazo en su lugar.
Las mismas dos llamadas sirven para montar una cabecera HTTP Basic Auth, por ejemplo para package:http:
final headers = {'Authorization': 'Basic ${base64Encode(utf8.encode('user:pass'))}'};
// Authorization: Basic dXNlcjpwYXNz
El generador de cabeceras Basic Auth muestra el mismo valor para cualquier credencial.
Base64 URL-safe y normalize()
base64Url es un segundo códec con el alfabeto -_. Los dos codificadores siempre escriben el relleno, así que quítalo si tu formato no lo quiere:
final d = utf8.encode('<<???>>');
print(base64Url.encode(d)); // PDw_Pz8-Pg==
print(base64Url.encode(d).replaceAll('=', '')); // PDw_Pz8-Pg
Al decodificar, Dart es más amable que la mayoría de los lenguajes. Los dos decodificadores aceptan ambos alfabetos, pero exigen un relleno correcto. normalize lo arregla y además convierte el alfabeto:
base64Url.normalize('PDw_Pz8-Pg'); // PDw_Pz8-Pg==
base64.normalize('PDw_Pz8-Pg'); // PDw/Pz8+Pg==
utf8.decode(base64Url.decode(base64Url.normalize('PDw_Pz8-Pg'))); // <<???>>
utf8.decode(base64Decode('PDw_Pz8-Pg==')); // <<???>>
normalize también lanza FormatException si la entrada no es Base64 válido, así que sirve además como validador. Los segmentos de un JWT son Base64URL sin relleno; consulta Base64 frente a Base64URL y el relleno explicado, o pega uno en el decodificador JWT.
Con normalize, leer el payload de un JWT ocupa una línea:
const jwt = 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0In0.sig';
final payload = utf8.decode(base64Url.decode(base64Url.normalize(jwt.split('.')[1])));
print(payload); // {"sub":"1234"}
print(jsonDecode(payload)['sub']); // 1234
Esto solo lee los claims; no verifica la firma, así que nunca confíes en ellos para autorizar nada en el cliente.
Codificar y decodificar un archivo
import 'dart:io';
final bytes = await File('hello.txt').readAsBytes();
print(base64Encode(bytes)); // aGVsbG8K
await File('copy.txt').writeAsBytes(base64Decode('aGVsbG8K'));
Archivos grandes en flujo
base64.encoder y base64.decoder son transformadores de flujos, así que un archivo se puede convertir bloque a bloque:
// encode: bytes -> Base64 text -> ASCII bytes on disk
await File('big.bin')
.openRead()
.transform(base64.encoder)
.transform(ascii.encoder)
.pipe(File('big.b64').openWrite());
// decode: ASCII bytes -> String chunks -> bytes
await File('big.b64')
.openRead()
.transform(ascii.decoder)
.transform(base64.decoder)
.pipe(File('big.out').openWrite());
Los transformadores guardan los bytes sobrantes entre bloques, así que el tamaño de los bloques no importa. En las dos direcciones, un archivo de prueba de 300 KB salió byte a byte igual que con las funciones en memoria.
Flutter: imágenes y data URI
Para mostrar una imagen Base64:
Image.memory(base64Decode(b64))
Para obtener Base64 de un asset o de un archivo elegido por el usuario, lee los bytes y llama a base64Encode. Con los data URI, UriData se encarga del prefijo:
UriData.parse('data:text/plain;base64,aGk=').contentAsBytes(); // [104, 105]
UriData.fromBytes(pngBytes, mimeType: 'image/png').toString(); // data:image/png;base64,...
Si pasas un data URI directamente a base64Decode, falla con Invalid character (at character 5), que es el : que va detrás de data. Para ver qué contiene una cadena desconocida, Base64 a imagen la previsualiza en el navegador.
Validación y mensajes de FormatException
Todos los errores de decodificación son FormatException. toString() incluye el mensaje, la entrada y un acento circunflejo bajo el problema; e.message y e.offset dan esas partes por separado. Con base64Decode:
| Entrada | Mensaje |
|---|---|
aGk | Invalid length, must be multiple of four (at character 4) |
a | Invalid length, must be multiple of four (at character 2) |
aGk=!! | Invalid padding character (at character 5) |
aGk== | Invalid padding character (at character 5) |
aGl= | Invalid encoding before padding (at character 4) |
"aGVs\nbG8K" | Invalid character (at character 5) |
Dart es estricto en dos puntos en los que muchos lenguajes son tolerantes: rechaza los saltos de línea (quítalos en las entradas MIME o PEM) y rechaza los bits de relleno distintos de cero (aGl=). Una comprobación de validez:
bool isBase64(String s) {
try {
base64Decode(s);
return true;
} on FormatException {
return false;
}
}
Si una cadena falla y el mensaje no basta, el validador Base64 marca el problema; errores habituales de Base64 explica las causas.
Trampas
- Codificar
codeUnits.base64Encode('é'.codeUnits)devuelve6Q==(Latin-1, no UTF-8), y'✓'.codeUnitslanzaArgumentError: Invalid argument (Not a byte value at index 0: 0x2713). Pasa siempre porutf8.encode. - Saltos de línea de otros sistemas. La salida MIME del correo o de
base64en Linux contiene\n; elimina antes los espacios en blanco cons.replaceAll(RegExp(r'\s'), ''). - Suponer que
base64Encodeda salida URL-safe. Escribe+y/. Usabase64Url.encodepara todo lo que vaya en una URL. - Sin cortes de línea MIME. Los códecs nunca insertan saltos de línea. Si los necesitas, divide tú mismo en líneas de 76 caracteres.
Preguntas frecuentes
¿Cómo muestro una imagen Base64 en Flutter?
Decodifícala a bytes y usa Image.memory(base64Decode(b64)). Si la cadena es un data URI, quita antes todo lo que haya hasta la coma, o usa UriData.parse(uri).contentAsBytes().
¿Por qué base64Decode dice «Invalid length, must be multiple of four»?
La entrada no tiene relleno, algo típico en los JWT y los tokens URL-safe. Llama primero a base64.normalize(s) o base64Url.normalize(s), que añade los = que faltan.
¿base64Decode de Dart acepta entradas URL-safe?
Sí. El decodificador acepta tanto +/ como -_, así que base64Decode('PDw_Pz8-Pg==') funciona. Solo se diferencian los codificadores: base64 escribe +/ y base64Url escribe -_.