Base64-decodering in Dart: een complete gids
Het verschijnt in een API-antwoord, in een URL, of geplakt in een supportticket: een lange opeenvolging van letters en cijfers met hier en daar een +, /, - of _, en misschien een paar =-tekens die aan het einde ophangen. Iemand noemt het Base64, en jij hebt nodig wat erin zit. Deze gids is het Dart-recept om het terug te krijgen. Even snel oriënteren, want de startpagina doorloopt het format in detail: Base64 schrijft elke drie invoerbytes om naar vier tekens uit een alfabet van 64 tekens, en haakt er één of twee =-pads aan het einde vast als het laatste blok te kort is. Decoderen is de krimpende richting van die ruil: vier tekens gaan erin, drie bytes komen eruit, dus het resultaat heeft altijd ongeveer een kwart minder ruimte nodig dan de invoer.
Het goede nieuws: er is niets te installeren. Base64 is sinds Dart 1.13 in 2015 opgenomen in de dart:convert-bibliotheek, en de API is stabiel sinds Dart 2.0 in 2018. Eén import geeft je een snelle, strikte decoder die zowel het standaardalfabet als het URL-veilige alfabet leest.
Eén eerlijke grens: dit is de decoderkant van het verhaal. Je leert wat de decoder accepteert en weigert, hoe padding werkt, hoe je bytes weer naar tekst zet zonder mojibake, en hoe je Base64 tegenkomt in JWTs, data-URIs, bestanden, streams, e-mail, configuratie en de opdrachtregel. De andere richting, bytes in een tekenreeks verpakken, heeft zijn eigen gids, verlinkt aan het einde van deze.
Vier deuren naar één strikte machine
Hier is het hele openbare oppervlak dat je zult gebruiken, en het zit allemaal in dart:convert:
| Ingang | Wat het is | Grijp ernaar wanneer |
|---|---|---|
base64Decode(source) |
Topniveau-functie, decodeert naar een Uint8List |
Alledaags decoderen, vrijwel altijd deze |
base64.decode(source) |
De decodeermethode van de codec, identiek gedrag | Je wilt de codec voor fuse of streamtransformaties |
base64Url.decode(source) |
De decodeermethode van de URL-veilige codec | De invoer is gedocumenteerd als URL-veilig (de machine is dezelfde) |
base64Url.normalize(source) |
Valideert en repareert een tekenreeks, geeft hem terug met padding | De invoer kan zonder padding komen, alfabetten mengen of percent-escapes gebruiken |
Twee dingen vallen op. Ten eerste leiden alle vier de wegen naar dezelfde decoder: één strikte statiemachine met één opzoektafel. Ten tweede is de laatste rij helemaal geen decoder. Het is een reparatiestation, en het bewijst zijn waarde de eerste keer dat een JWT zonder padding of een half opgeschoonde configuratiewaarde opduikt.
Jouw eerste decode
Negentig procent van het decoderen past in vijf regels. Hier is het kleinste voorbeeld dat de hele vorm van het werk toont:
import 'dart:convert';
void main() {
final bytes = base64Decode('TWFu');
final text = utf8.decode(bytes);
print(text); // Man
}
Drie zinnen over wat zojuist gebeurde. Ten eerste reikt de ingangsfunctie bytes aan, geen tekst: base64Decode geeft een Uint8List terug, en dat is opzettelijk, want de payload kan een zin, een JPEG of een hash zijn, en geen van die mag je op dezelfde manier behandelen voordat je weet wat je hebt. Ten tweede is de sprong van bytes naar tekst een aparte, expliciete stap met een expliciete codering, en die stap is waar "café" in mojibake verandert als je slordig bent. Ten derde is de lege tekenreeks een eersteklaswaarde: base64Decode('') geeft je een lijst met lengte nul, zonder uitzondering en zonder gedoe.
Wat de decoder accepteert en weigert
De decoder van Dart is streng bij ontwerpprincipe. RFC 4648 zegt dat implementaties invoer met tekens buiten het alfabet moeten weigeren, en Dart volgt die interpretatie tot op de letter: geen spaties overslaan, geen regeleindes negeren, geen tweede kans. Als de invoer fout is, krijg je een FormatException die de invoer toont en naar het exacte teken wijst. Hier is het gedrag bij de klassieke problemmakers:
| Invoer | Wat er mis is | Exacte fout |
|---|---|---|
'SGVs bG8s' |
een spatie heeft zich versluipd | FormatException: Invalid character (at character 5) |
'SGVs\nbG8s' |
een regeleinde heeft zich versluipd | FormatException: Invalid character (at character 5) |
'SGVs$bG8s' |
een dollarteken zit niet in het alfabet | FormatException: Invalid character (at character 5) |
'Zm8' |
helemaal geen padding | FormatException: Invalid length, must be multiple of four (at character 4) |
'Zm8==' |
twee pads waar er één hoort | FormatException: Invalid padding character (at character 5) |
'Zm=8' |
padding in het midden van de data | FormatException: Invalid encoding before padding (at character 3) |
'Zm8=xx' |
rommel na de pads | FormatException: Invalid padding character (at character 5) |
'Zé' |
een niet-ASCII-teken | FormatException: Invalid character (at character 2) |
De positie in de melding is een vanaf één getelde tekenpositie, en de invoer staat net onder de karret gedrukt, dus het bisseren van een gecorrupte payload gaat snel. In de strengheid schuilt een aangename verrassing: de decoder accepteert beide alfabetten. Een - of _ in het midden van een standaardtekenreeks is geen probleem, en een + of / in een URL-veilige tekenreeks ook niet. Het alfabet maakt alleen uit als jij de tekst produceert, niet als je hem leest.
Padding: niet onderhandelbaar
Hier is de regel die de meeste mensen verbaast: de Dart-decoder vereist correcte padding. De invoer moet een veelvoud van vier tekens lang zijn, en de =-tekens aan het einde moeten er in exact de juiste hoeveelheid zijn. Er is geen tolerante modus, geen vlag om het te versoepelen en geen instelling om te veranderen. De redenen zijn goed: decoderen zonder padding is dubbelzinnig in hoekgevallen, en de RFC waarschuwt dat liberale decodering een sluipkanaal kan openen, dus de strikte lezing is de veilige. Wat dit in de praktijk betekent:
| Invoer | Resultaat |
|---|---|
'' |
lege Uint8List, geen fout |
'QQ==' |
1 byte: A |
'QUI=' |
2 bytes: AB |
'QUJD' |
3 bytes: ABC |
'Zm8' |
FormatException: ongeldige lengte |
'Zm8==' |
FormatException: ongeldig padding-teken |
Als de invoer afkomstig is van een systeem dat padding strippt, en JWTs zitten vol met waarden zonder padding, is de reparatiestap één aanroep van normalize. Het valideert de tekenreeks, zet URL-veilige tekens om naar het standaardalfabet en voegt de ontbrekende pads toe:
import 'dart:convert';
void main() {
final stripped = '-__--Q';
final repaired = base64Url.normalize(stripped);
print(repaired); // +//++Q==
final bytes = base64Decode(repaired);
print('decoded ${bytes.length} bytes'); // decoded 4 bytes
}
De verrassing van het percentteken
Dit is een vinding uit Dart. Als Base64 in een data-URI voorkomt, percent-coderen sommige tools de padding en schrijven %3D in plaats van =, want een losse = mag in URL-syntax ook "parameterscheider" betekenen. De meeste talen zouden eisen dat je eerst de escapes verwijdert. De decoder van Dart niet: de opzoektafel behandelt %3D als de eigen schrijfwijze van het padding-teken, dus je kunt de ruwe payload onbewerkt overhandigen:
import 'dart:convert';
void main() {
final fromDataUri = 'SGVsbG8%3D';
final bytes = base64Decode(fromDataUri);
print(utf8.decode(bytes)); // Hello
}
De escape wordt alleen geaccepteerd op de plek waar padding legaal is, en dat is de afsluitende positie. Zet %3D op een plek waar = zou worden afgewezen, dan wordt het op dezelfde manier afgewezen, en %25 faalt in plaats daarvan op de padding-check - % is het eigen padding-escape-teken van Dart, dus de decoder leest het als een ge-escape-de = en wijst de 2 af met Invalid padding character. In de praktijk betekent dit dat je een ;base64,-payload die je rechtstreeks uit de developer tools van de browser kopieert, zonder enige voorverwerking kunt decoderen, een klein maar echt handig trucje.
URL-veilige Base64
RFC 4648 definieert om één reden een tweede alfabet: het standaardalfabet heeft drie tekens, +, / en =, die botsen met URL-syntax. Het URL-veilige alfabet, in de RFC base64url genoemd, vervangt + door - en / door _, en laat vaak ook de padding weg. Dit is het alfabet van JWTs, object-IDs, deellinks en alles wat in een URL of een bestandsnaam woont.
Op de decodeerkant geeft Dart één antwoord: beide alfabetten worden door dezelfde machine gelezen. base64Decode en base64Url.decode zijn twee namen voor dezelfde decoder, dus het enige echte werk is padding, omdat URL-veilige producenten het vaak zonder meegeven. Daar is normalize precies voor:
import 'dart:convert';
void main() {
final bytes = [0xfb, 0xff, 0xfe, 0xf9];
final urlSafe = base64UrlEncode(bytes);
print(urlSafe); // -__--Q==
final repaired = base64Url.normalize(urlSafe.replaceAll('=', ''));
print(repaired); // +//++Q==
print(base64Decode(repaired).length); // 4
}
Twee valkuilen om mee te nemen. Doe geen handgemaakte vervanging van - naar + vóór je decodeert; dat is overbodig, en normalize doet de alfabetomzetting al als die nodig is. En neem niet aan dat een URL-veilige tekenreeks zonder padding aankomt: sommige producenten houden de pads aan, en de decoder accepteert beide, zolang de padding maar correct is.
Van bytes naar tekst: het tekenset-besluit
Base64-decoderen reikt je bytes aan. Als die bytes tekst zijn, moet je de codering kiezen die ze weer naar een String zet, en die keuze maak je zelf expliciet. De standaardaanname in moderne systemen is UTF-8, en utf8.decode is het werkpaard:
import 'dart:convert';
void main() {
final payload = base64Encode(utf8.encode('Héllo Wörld'));
final bytes = base64Decode(payload);
print(utf8.decode(bytes)); // Héllo Wörld
final legacy = base64Encode(latin1.encode('Héllo'));
print(latin1.decode(base64Decode(legacy))); // Héllo
}
Als de bytes geen geldige UTF-8 zijn, gooit utf8.decode een FormatException, en dat is het juiste gedrag, verreweg beter dan stille mojibake. Als je weet dat de data oude enkele-byte-tekst is, gebruik dan de bijbehorende codering:
| Codering | Gebruik het voor | Decodeer met |
|---|---|---|
utf8 |
Moderne tekst, JSON, alles op het web | utf8.decode(bytes) |
latin1 |
Oude Westerse enkele-byte-data | latin1.decode(bytes) |
ascii |
Gewone 7-bits-tekst | ascii.decode(bytes) |
Eén valkuil verdient een eigen waarschuwing: String.fromCharCodes is geen tekenset. Het leest bytes als UTF-16-code-eenheden, dus als je het de UTF-8-bytes van Héllo voert, geeft het zonder te knipperen Héllo uit. Als je dat mojibake-patroon in je output ziet, is de oplossing vrijwel altijd utf8.decode.
JWTs: het token lezen
Een JSON Web Token is drie base64url-onderdelen die met punten aan elkaar zitten: header, payload, handtekening. Base64 wordt hier gebruikt voor compactheid en URL-veiligheid, niet voor geheimhouding. Iedereen met het token kan de header en de payload lezen, en dat is zo bedoeld. Wat je verifieert is de handtekening, met het gedeelde geheim of de openbare sleutel van de uitgever. De leesbare delen decoderen kost in Dart een paar regels:
import 'dart:convert';
Map<String, dynamic> readJwtPayload(String token) {
final parts = token.split('.');
if (parts.length != 3) {
throw FormatException('Not a compact JWT');
}
final padded = base64Url.normalize(parts[1]);
final bytes = base64Decode(padded);
return jsonDecode(utf8.decode(bytes)) as Map<String, dynamic>;
}
void main() {
const token =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9'
'.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkRhcnQgRGV2IiwiaWF0IjoxNTE2MjM5MDIyfQ'
'.c2lnbmF0dXJl';
print(readJwtPayload(token)['name']); // Dart Dev
}
Let op de padding-dans: JWTs worden zonder padding opgebouwd, dus een onderdeel faalt bij een directe base64Decode als de lengte geen veelvoud van vier is. (In het voorbeeld hierboven is de header toevallig 36 tekens en decodeert hij direct; de payload is 74 tekens en doet dat niet.) De normalize-aanroep maakt de reparatie gelijkmatig, ongeacht de lengte. Nog twee waarschuwingen. Decoderen is niet verifiëren: het controleren van de handtekening en de exp-claim is een aparte, verplichte stap, meestal met het crypto-pakket voor HMAC-algoritmen. En wees wantrouwig ten opzichte van tokens die alg: none beweren; een parser die ze accepteert is een kwetsbaarheid, geen feature.
Data-URIs: bestanden in een URL-vermomming
Een data-URI, gedefinieerd in RFC 2397, is een URL waarvan de payload de data zelf is: data:image/png;base64, gevolgd door de gecodeerde bytes. Ze bestaan zodat kanalen die alleen tekst aannemen - HTML-attributen, CSS-regels, JSON-documenten - binaire data kunnen vervoeren zonder een apart bestand. Base64 is het payload-formaat van de keuze, want het alternatief, percent-codering, is voor binaire data veel langer.
En Dart kan ze natief parsen: ondersteuning voor data-URIs zit sinds 2016 in dart:core, dus er is geen URI-bibliotheek nodig:
import 'dart:convert';
void main() {
final uri = Uri.parse('data:image/png;base64,iVBORw0KGgo=');
final data = uri.data!;
print(data.mimeType); // image/png
print(data.isBase64); // true
print('decoded ${data.contentAsBytes().length} bytes');
final textUri = Uri.parse('data:text/plain;base64,SGVsbG8sIERhcnQh');
print(textUri.data!.contentAsString()); // Hello, Dart!
}
Het UriData-object reikt je de MIME-type, de isBase64-vlag, de ruwe payload-tekst en de gedecodeerde inhoud aan, als tekenreeks of als bytes. Twee valkuilen: het verklaarde MIME-type kan liegen, dus controleer in beveiligingsgevoelige code de werkelijke magic bytes. En data-URIs zijn voor kleine assets, want de hele payload reist mee in het document dat ernaar verwijst.
Bestanden: Base64 op schijf
Base64-bestanden duiken op in exportformaten, provisioning-bundels en elke overdracht die alleen tekst aankan en toch binair mee moet vervoeren. Het recept is: de tekst lezen, het plat maken, decoderen, de bytes schrijven:
import 'dart:convert';
import 'dart:io';
Future<void> main() async {
final encoded = await File('image.b64').readAsString();
final flat = encoded.replaceAll(RegExp(r'\s+'), '');
final bytes = base64Decode(flat);
await File('image.png').writeAsBytes(bytes);
print('wrote ${bytes.length} bytes');
}
Die replaceAll doet echt werk. Tekstbestanden zitten vol regeleindes, vaak MIME-omwikkeling van 76 tekens, en de strikte decoder weigert die, dus maak het eerst plat. De regex verwijdert elk witruimte-teken, en dat is precies wat je wilt voor een puur base64-bestand. Als het bestand andere annotaties kan bevatten, zoals een PEM-header, strip die dan expliciet weg vóór je decodeert, en laat de foutmeldingen van de decoder alles oppakken wat écht gecorrupt is.
HTTP en API's
Base64 in HTTP draagt twee vermommingen. Eerst API-antwoorden: een JSON-veld dat binair als tekenreeks vervoert. En dan de Authorization: Basic-header, waar credentials met het standaardalfabet en padding base64-gecodeerd zijn:
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<void> main() async {
final response = await http.get(
Uri.parse('https://httpbin.org/get?attachment=TWFuIGlzIGhlcmU%3D&name=man.txt'),
);
final payload = jsonDecode(response.body) as Map<String, dynamic>;
final args = payload['args'] as Map<String, dynamic>;
final bytes = base64Decode(args['attachment'] as String);
print('got ${bytes.length} bytes');
final credentials = utf8.decode(base64Decode('b2N0b2NhdDpzZWNyZXQ='));
print(credentials.split(':').first); // octocat
}
Het http-pakket is de standaardclient, op één dart pub add http afstand. Voor Basic-auth decodeer je het deel achter het Basic -prefix. Twee valkuilen: sommige API's sturen URL-veilige of zonder padding opgeleverde waarden waar de documentatie base64 zegt, dus als direct decoderen een uitzondering gooit, stuur de waarde dan eerst door base64Url.normalize. En onthoud dat Basic-auth obfuscatie is, geen bescherming, en dat het daarom alleen op TLS-verbindingen hoort.
E-mail en MIME: het regeleinde-probleem
E-mail is de oudste base64-klant. MIME omwikkelt base64-regels op 76 tekens - 76 plus CRLF past ruim op een 80-kolommen-weergave - en RFC 2045 vertelt decoders de regeleindes te negeren. Dat doet de decoder van Dart niet, expres: hij weigert ze. De oplossing is om vóór het decoderen plat te maken:
import 'dart:convert';
List<int> decodeMimeBody(String wrapped) {
final flat = wrapped.replaceAll(RegExp(r'\s+'), '');
return base64Decode(flat);
}
void main() {
const wrapped =
'SGVsbG8gZnJvbSBhbiBlbWFpbCBhdHRhY2htZW50LCB3cmFwcGVkIGF0IDc2IGNoYXJhY3RlcnMg'
'\r\n'
'dGhlIHdheSBNSU1FIHdhbnRzIGl0IHRvIGJlLCB3aXRoIENSTEYgYmV0d2VlbiB0aGUgbGluZXMu';
print(utf8.decode(decodeMimeBody(wrapped)));
}
De regel is simpel: witruimte strippen, niets anders. Strip niet andere tekens in de hoop nuttig te zijn; de decoder is de validator, en je wilt dat hij klagt over echte corruptie. Als je e-mail op schaal verwerkt, is het platmaken goedkoop, één doorloop van de regex, en houdt het de rest van de pipeline eerlijk.
Configuratie en omgevingsvariabelen
Tokens en credentials die in tekstgebaseerde configuratie leven, worden soms base64-gecodeerd om ze op één regel te houden en er als tokens uit te laten zien. De eerlijke framing: base64 is obfuscatie, geen versleuteling, dus dit patroon is voor netheid, nooit voor geheimhouding. Het patroon zelf is triviaal:
import 'dart:convert';
import 'package:dotenv/dotenv.dart';
Future<void> main() async {
final env = DotEnv()..load();
final encoded = env['API_TOKEN_B64'];
if (encoded == null) {
return;
}
final token = utf8.decode(base64Decode(encoded));
print('loaded a ${token.length}-char token');
}
Met het dotenv-pakket zit de waarde in een .env-bestand als API_TOKEN_B64=c2stbGl2ZS1hYmMxMjM= en komt hij na de decode terug als gewone tekst. Dezelfde vorm werkt met String.fromEnvironment voor compile-time dart-define-waarden, met één waarschuwing: dart-define-waarden zijn ingebakken in de gecompileerde binary, dus alles wat een geheim is hoort in runtime-configuratie of een secret manager, niet daar.
Streams: stuk voor stuk
Als de gecodeerde tekst in stukjes aankomt - een netwerkstream, een groot bestand dat in blokken wordt gelezen - komt de decoder ermee klaar. Zijn statiemachine draagt de gedeeltelijke groep over de chunk-grenzen heen, dus de chunks hoeven niet uitgelijnd te zijn op vier-teken-grenzen:
import 'dart:convert';
Future<void> main() async {
final incoming = Stream.fromIterable(['TWF', 'uaGVsbG8=']);
final text = await incoming
.transform(base64.decoder)
.map(utf8.decode)
.join();
print(text); // Manhello
}
De transform-aanroep gebruikt de decoder als streamtransformatie. Het eerste chunk, drie tekens, parkeert zijn bits in de staat van de decoder, en het tweede chunk voltooit de groep. Fouten verschijnen als stream-fouten met dezelfde FormatException-details, en een lege stream produceert simpelweg geen output. Als je liever sinks gebruikt, geeft base64.decoder.startChunkedConversion je een StringConversionSink die op dezelfde statiemachine is aangesloten.
Grote data: de wiskunde en het geheugen
Decoderen maakt dingen kleiner: vier tekens worden drie bytes, dus de output is altijd net iets minder dan driekwart van de invoerlengte. Dat betekent dat de outputgrootte vóór het decoderen bekend is, en het geheugen voorspelbaar wordt. Een kleine helper berekent het uit de tekenreeks alleen:
import 'dart:convert';
int decodedLength(String encoded) {
var padding = 0;
for (var i = encoded.length - 1; i >= 0 && padding < 2; i--) {
if (encoded.codeUnitAt(i) == 0x3d) {
padding++;
} else {
break;
}
}
return (encoded.length ~/ 4) * 3 - padding;
}
void main() {
print(decodedLength('QQ==')); // 1
print(decodedLength('QUI=')); // 2
print(decodedLength('QUJD')); // 3
}
De ingebouwde decoder is snel: één doorloop door een opzoektafel, zonder teken-voor-teken string-toewijzingen, dus tekenreeksen van meerdere megabytes zijn routine. Waar base64 je wél iets kost is aan de invoerkant: de gecodeerde tekst is zo'n 33 procent groter dan de data, en het is een tekenreeks, en op de VM bestaat die als UTF-16-code-eenheden, ruwweg twee keer de byte-lengte van de gecodeerde tekens. Voor payloads die groot kunnen worden, stream de decode in plaats van één grote tekenreeks bij elkaar te voegen.
Vanaf de opdrachtregel
De VM van Dart maakt van de decoder een nette CLI. Dit kleine tooltje leest een bestandargument of de standaard invoer, maakt witruimte plat, en schrijft de ruwe bytes naar de standaard output:
import 'dart:convert';
import 'dart:io';
Future<void> main(List<String> args) async {
String encoded;
if (args.isNotEmpty) {
encoded = await File(args[0]).readAsString();
} else {
encoded = await stdin
.transform(utf8.decoder)
.join();
}
final flat = encoded.replaceAll(RegExp(r'\s+'), '');
stdout.add(base64Decode(flat));
await stdout.flush();
}
Sla het op als bin/decode.dart en voer dart run bin/decode.dart image.b64 > image.png uit, of pipe het door: cat token.b64 | dart run bin/decode.dart. De stdout.add-aanroep neemt de Uint8List direct aan, zonder tussentijdse tekenreeks, en dat is precies hoe binaire data door een pipeline hoort te bewegen.
Valkuilen die Dart-developers bijten
- De padding-muur. Invoer in JWT-stijl en van URL-tools komt vaak zonder
=-tekens, en de decoder weigert die metInvalid length, must be multiple of four. Stuur niet-vertrouwde invoer eerst doorbase64Url.normalize. - De witruimte-valkuil. Tekstbestanden, e-mail en copy-paste brengen allemaal regeleindes mee, en de decoder slaat ze nooit over. Maak het plat met
replaceAll(RegExp(r'\s+'), '')vóór je decodeert. - Blind vertrouwen op het alfabet. Omdat beide alfabetten overal gedecodeerd worden, bouw geen logica op welke decoder een tekenreeks heeft geproduceerd. De tekenreeks is de overeenkomst, niet de instellingen van de producent.
- String.fromCharCodes is geen tekenset. Het leest UTF-16-code-eenheden, dus het maakt van UTF-8-tekst mojibake. Gebruik
utf8.decodeof een expliciete codering. - Twee verschillende fouttypes. Decode-problemen zijn
FormatExceptions, en de encoder gooit eenArgumentErrorvoor waarden buiten het bereik 0 tot 255. Vang ze apart af als je een grens bouwt. - Het resultaat heeft vaste lengte.
Uint8Listkan niet groeien, dusbytes.add(1)gooit eenUnsupportedError. Kopieer metList<int>.from(bytes)als je een groeiende lijst nodig hebt. - Verwijder percent-escapes niet met de hand. De decoder leest percent-ge-escape padding natief; een te vroege
replaceAll('%3D', '=')koppelt je code aan een detail waar de SDK al voor zorgt. - Decoderen van een JWT-payload is niet verifiëren. De claims lezen en ze geloven is een beveiligingsfout die op een vastberaden gebruiker wacht.
Beste praktijken, korte lijst
- Begin standaard met
base64Decode. Paknormalizeer alleen bij op grenzen waar de invoer niet vertrouwd is. - Wees expliciet over de tekenset met
utf8.decode(bytes), ook als je UTF-8 aannemt. - Houd bytes als bytes tot je weet wat ze zijn. Een
Uint8Listreist netjes naarFile.writeAsBytesen dergelijke. - Op vertrouwensgrenzen vang je
FormatExceptionaf en log je de invoerpositie die de melding geeft. - Stream alles wat groter kan worden dan een paar megabytes.
- Behandel base64 als een format, niet als bescherming. Het verbergt niets voor iemand die weet dat het base64 is.
Een korte geschiedenis van Base64 in Dart
De decoder die je zojuist leerde kennen is ouder dan Dart 3, null safety en het Flutter-tijdperk. De korte versie:
- 18 november 2015, Dart 1.13: Base64 komt aan in
dart:convertals deBASE64-constante, samen met deBase64Codec,Base64EncoderenBase64Decoder-classes. Vóór deze release had de SDK helemaal geen base64. - 28 januari 2016, Dart 1.14:
Base64Decoder.convertkrijgtstart- enend-bereiksparameters, en dezelfde release voegt data-URI-ondersteuning toe aandart:core, deUri.parse-weg waarop dit artikel steunt. - 26 april 2016, Dart 1.16: het URL-veilige alfabet komt erbij als
BASE64URLen deBase64Codec.urlSafe-constructor. - 7 augustus 2018, Dart 2.0: de constanten worden herbapt naar kleine letters
base64enbase64Url, de topniveau-aanroepbase64Decodeen vrienden komen erbij, decoderen geeft eenUint8Listterug in plaats van een groeiendeList<int>, enBase64Codec.normalizesluit zich aan bij de familie, en maakt van validatie en reparatie een stap van één aanroep. - 2021, Dart 2.12: null safety verschijnt, en het hele
dart:convert-verhaal, base64 inbegrepen, wordt null-safe. - Vandaag, Dart 3.13: de classes zijn gemarkeerd als
final, en het gedrag dat je hierboven tegenkwam komt van dezelfde strikte, percent-bewuste machine die beide alfabetten leest, en die sinds 2015 draait.
De strengheid is geen toevalligheid van de implementatie. Het is de decoder die de instructie van RFC 4648 volgt, dat implementaties tekens buiten het alfabet moeten weigeren, en laat de tolerantie in MIME-stijl aan de applicaties die dat nodig hebben, en in Dart betekent dat een platmaakstap vóór de decode.
Leuke feiten
- De decoder leest
%3Dals native padding. Geef hem de ruwe payload van een data-URI, escape en al, en hij decodeert het. Maar weinig taalruntimes kunnen dat zonder een voorverwerkingsstap. base64.decoderenbase64Url.decoderzijn letterlijk hetzelfde object: allebei zijn het de gecanonicaliseerdeconst Base64Decoder()-instantie. De "URL-veilige decoder" is de standaarddecoder in een andere vermomming.- De hele decoder past in één opzoektafel van 128 posten, een
Int8Listdie wordt gedeeld tussen de interpreter en AOT-gecompileerde code, waarbij zowel+als-naar slot 62 van het alfabet wijzen, en zowel/als_naar 63. - De base64 van Dart en de data-URI-ondersteuning kwamen twee releases uit elkaar, in 1.13 en 1.14, en het is duidelijk dat ze als een paar gepland waren: de ene leest het format, de andere leest het rechtstreeks uit een URL.
- Een lege tekenreeks decodeert naar een lege
Uint8Listzonder fout, en een lege tekenreeks encodeert naar de lege tekenreeks: base64 behandelt het ontbreken van data als een perfect geldige boodschap. - In 2018, toen Dart 2.0 zijn constanten herbaptte, werd
BASE64base64als onderdeel van een SDK-brede overgang naar constanten met kleine letters, dezelfde golf die jeascii,jsonenutf8gaf.
Je hebt nu de complete decoder in handen: wat het accepteert, wat het weigert, hoe je beschadigde invoer repareert, en hoe je het tegenkomt in JWTs, data-URIs, bestanden, streams, e-mail en de shell. De andere richting van de ruil, bytes pakken en één van de twee alfabetten produceren, compleet met de padding-beslissingen en de grootte-wiskunde, staat gedetailleerd in de Base64-coderingsgids, verlinkt onder aan deze pagina.
Laatst bijgewerkt: 2026-10-06
Gerelateerd artikel: Base64-codering in Dart: een complete gids