Codificar e decodificar uma string
Internamente, as strings do Dart são UTF-16, então converta para bytes UTF-8 com 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) é um atalho para base64.encode(bytes), e base64Decode(s) para base64.decode(s). Se os bytes decodificados não forem UTF-8, o utf8.decode lança FormatException: Invalid UTF-8 byte; passe allowMalformed: true para receber caracteres de substituição no lugar.
As mesmas duas chamadas montam um header HTTP Basic Auth, por exemplo para o package:http:
final headers = {'Authorization': 'Basic ${base64Encode(utf8.encode('user:pass'))}'};
// Authorization: Basic dXNlcjpwYXNz
O gerador de header Basic Auth mostra o mesmo valor para quaisquer credenciais.
Base64 URL-safe e normalize()
O base64Url é um segundo codec, com o alfabeto -_. Os dois codificadores sempre escrevem o preenchimento, então remova-o se o seu formato não o quiser:
final d = utf8.encode('<<???>>');
print(base64Url.encode(d)); // PDw_Pz8-Pg==
print(base64Url.encode(d).replaceAll('=', '')); // PDw_Pz8-Pg
Na decodificação, o Dart é mais amigável que a maioria das linguagens. Os dois decodificadores aceitam os dois alfabetos, mas exigem o preenchimento correto. O normalize resolve isso e ainda converte o 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==')); // <<???>>
O normalize também lança FormatException se a entrada não for Base64 válido, então serve como validador. Os segmentos de um JWT são Base64URL sem preenchimento; veja Base64 vs Base64URL e preenchimento explicado, ou cole um token no decodificador de JWT.
Com o normalize, ler o payload de um JWT leva uma linha:
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
Isso só lê as claims; não verifica a assinatura, então nunca confie nelas para autorização no cliente.
Codificar e decodificar um arquivo
import 'dart:io';
final bytes = await File('hello.txt').readAsBytes();
print(base64Encode(bytes)); // aGVsbG8K
await File('copy.txt').writeAsBytes(base64Decode('aGVsbG8K'));
Streaming de arquivos grandes
O base64.encoder e o base64.decoder são transformadores de stream, então um arquivo pode ser convertido bloco a bloco:
// 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());
Os transformadores guardam os bytes que sobram entre um bloco e outro, então o tamanho dos blocos não importa. Nas duas direções, um arquivo de teste de 300 KB saiu idêntico byte a byte ao resultado das funções em memória.
Flutter: imagens e data URIs
Para mostrar uma imagem Base64:
Image.memory(base64Decode(b64))
Para gerar Base64 a partir de um asset ou de um arquivo escolhido pelo usuário, leia os bytes e chame base64Encode. Para data URIs, o UriData cuida do prefixo:
UriData.parse('data:text/plain;base64,aGk=').contentAsBytes(); // [104, 105]
UriData.fromBytes(pngBytes, mimeType: 'image/png').toString(); // data:image/png;base64,...
Passar um data URI direto para o base64Decode falha com Invalid character (at character 5), o : depois de data. Para ver o que uma string desconhecida contém, o Base64 para imagem mostra uma prévia no navegador.
Validação e mensagens de FormatException
Todos os erros de decodificação são FormatException. O toString() inclui a mensagem, a entrada e um acento circunflexo sob o problema; e.message e e.offset dão as partes separadamente. Com base64Decode:
| Entrada | Mensagem |
|---|---|
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) |
O Dart é rígido em dois pontos em que muitas linguagens são tolerantes: rejeita quebras de linha (remova-as em entradas MIME ou PEM) e rejeita bits de preenchimento diferentes de zero (aGl=). Uma verificação de validade:
bool isBase64(String s) {
try {
base64Decode(s);
return true;
} on FormatException {
return false;
}
}
Se uma string falha e a mensagem não diz o suficiente, o validador de Base64 marca o problema; erros comuns de Base64 explica as causas.
Armadilhas
- Codificar
codeUnits.base64Encode('é'.codeUnits)retorna6Q==(Latin-1, não UTF-8), e'✓'.codeUnitslançaArgumentError: Invalid argument (Not a byte value at index 0: 0x2713). Passe sempre peloutf8.encode. - Quebras de linha vindas de outros sistemas. Saída MIME de e-mail ou do
base64do Linux contém\n; remova os espaços em branco antes coms.replaceAll(RegExp(r'\s'), ''). - Achar que o
base64Encodegera saída URL-safe. Ele escreve+e/. Usebase64Url.encodepara tudo o que vai numa URL. - Sem quebra de linha MIME. Os codecs nunca inserem quebras de linha. Se precisar, divida em linhas de 76 caracteres você mesmo.
Perguntas frequentes
Como mostrar uma imagem Base64 no Flutter?
Decodifique para bytes e use Image.memory(base64Decode(b64)). Se a string for um data URI, remova antes tudo até a vírgula ou use UriData.parse(uri).contentAsBytes().
Por que o base64Decode diz 'Invalid length, must be multiple of four'?
A entrada não tem preenchimento, o que é típico de JWTs e tokens URL-safe. Chame antes base64.normalize(s) ou base64Url.normalize(s), que acrescenta os = que faltam.
O base64Decode do Dart aceita entrada URL-safe?
Sim. O decodificador aceita tanto +/ quanto -_, então base64Decode('PDw_Pz8-Pg==') funciona. Só os codificadores diferem: base64 escreve +/, base64Url escreve -_.