Werk je met Base64-indeling? Dan is deze site perfect voor jou! Gebruik onze handige online tool om je gegevens te coderen of te decoderen.

Base64-decodering in C# (CSharp): een complete gids

Je herkent het in een oogopslag: een rivier van letters en cijfers, hier en daar een + of /, en misschien een of twee = die aan het einde ophangen. Ergens tussen een API-antwoord, een e-mailbijlage, een configbestand en een JWT in, heeft iemand binaire data in tekst verpakt, en nu is het aan jou om het open te maken. Dit is de decoderingskant van Base64 in C#, en het eerste goede nieuws is dat je niets nodig hebt behalve de framework. De decoder woont al meer dan twintig jaar in de System-namespace, en elke moderne .NET-runtime levert hem nog steeds mee, met meer opties en betere prestaties dan het origineel.

Even een herhaling, want de startpagina van deze site legt het formaat volledig uit: vier tekens uit een alfabet van 64 symbolen dragen drie bytes aan data, en één of twee =-tekens aan de staart markeren de resterende bytes. Decoderen draait die ruil om, dus het resultaat is ongeveer driekwart van de grootte van de input. Met de vorm van het probleem in het achterhoofd, laten we een paar pakketten openen.

De decoderfamilie: ken je opties

Vóórdat we bij het eerste voorbeeld komen, hier de hele familie decoderings-API's die je ter beschikking staat, en de situatie waarvoor elke is gemaakt. Alles wat hier op de lijst staat is onderdeel van de .NET-runtime zelf, behalve de URL-safe klasse op oudere frameworks, die er via een klein NuGet-pakket bijkomt:

API Beschikbaar sinds Waar het voor is
Convert.FromBase64String(string) .NET Framework 1.1 (2003) Het klassieke. Eén string erin, een verse byte[] eruit. Slaat gewone witruimte over, gooit een uitzondering bij alles andere.
Convert.FromBase64CharArray(char[], int, int) .NET Framework 1.1 (2003) Dezelfde decodage, maar gelezen uit een stuk van een karakterbuffer die je al bezit.
Convert.TryFromBase64String, Convert.TryFromBase64Chars .NET Core 2.1 (2018) Een booleaanse waarde in plaats van uitzonderingen, schrijven naar een span die je aanlevert. De vriendelijke wachtpost voor onvertrouwde input.
System.Buffers.Text.Base64 .NET Core 2.1 (2018) De strikte span-API: statuscodes in plaats van uitzonderingen, decoderen op dezelfde plek, en IsValid-voorafchecks.
System.Buffers.Text.Base64Url .NET 9 (2024) Het URL-safe alfabet (- en _ in plaats van + en /), met of zonder padding. Op .NET Framework 4.6.2+ en .NET Standard 2.0: het Microsoft.Bcl.Memory NuGet-pakket.
FromBase64Transform + CryptoStream .NET Framework 1.1 (2003) Streamende decodage: bestand naar bestand, netwerk naar schijf, stuk voor stuk, zonder de hele payload te laden.

Als je project op een .NET-versie vanaf 2018 doelt, staan de eerste vier rijen er al in de framework. Base64Url heeft .NET 9 of nieuwer nodig, of het Microsoft.Bcl.Memory-pakket op alles ouder. En een vooruitblik: de .NET 11-bibliotheken, bij het schrijven in preview met een algemene release verwacht in het najaar 2026, voegen verdere Base64-gemak-API's en overloads toe aan de bestaande types, dus de familie blijft groeien. Niets anders in dit artikel vereist een pakket.

Het werkpaard: Convert.FromBase64String

Negentig procent van het decoderingsleven in C# is een enkele aanroep. Geef die een string, en die geeft je precies de bytes terug die erin waren verpakt:

using System;
using System.Text;
string packed = "TWFu";
byte[] bytes = Convert.FromBase64String(packed);
string text = Encoding.UTF8.GetString(bytes);
Console.WriteLine(text);
// Man

Drie details zijn de moeite waard om te onthouden. Allereerst is de retourwaarde bytes, geen tekst: het is een byte[], de decoder is van begin tot eind byte-georiënteerd, en dat is precies wat je wilt, want de payload kan een zin zijn, een PNG, een certificaat of een hash, en geen van die mag als speciaal worden behandeld. De sprong van bytes terug naar leesbare tekst is een aparte, bewuste stap via Encoding, en die stap is waar de charset-beslissingen leven (daarover meer hieronder). Ten tweede allocereert de decoder bij elke aanroep een verse array, afgepast op de gedecodeerde lengte, zodat die je nooit een buffer met losse capaciteit geeft. Ten derde is de afspraak klein en eerlijk: een lege string decodeert naar een lege array, een null-referentie gooit een ArgumentNullException, en alles wat geen geldige Base64 is gooit een FormatException. Alles andere is een uitwerking van die drie regels.

Wat het vergeeft en wat het weigert

Hier heeft de C#-decoder een persoonlijkheid, en een karaktervolle nog wel. Hij is gul over precies één ding - witruimte - en genadeloos over alles andere. De decoder slaat exact vier tekens over, waar ze ook in de string verschijnen: de spatie (U+0020), de tab (U+0009), de line feed (U+000A) en de carriage return (U+000D). Dat beleid is een bewuste knik naar e-mail, waar Base64-payloads aankomen verpakt in regels van 76 tekens, en het betekent dat een MIME-omwikkelde bijlage decodeert met nul voorafgaande verwerking. Alles buiten het alfabet van 64 symbolen, alles dat de lengteregels breekt, of alles met padding op de verkeerde plek kost een uitzondering. Zie dezelfde decoder in actie op een paar verschillende inputs:

Input Resultaat
"TWFu" Decodeert naar Man (3 bytes).
"TWF\nu" (een nieuwe regel in het midden) Decodeert naar Man. Witruimte is voor de decoder onzichtbaar.
"TWFu\u00A0" (een non-breaking space aan het einde) FormatException. Alleen de vier witruimtekens hierboven worden overgeslagen; NBSP is dat er niet één.
"TWE" (lengte 3, geen veelvoud van 4) FormatException. De payload-lengte, witruimte niet meegerekend, moet een veelvoud van 4 zijn.
"TWFu=" (extra padding na de data) FormatException. Maximaal twee padtekens, en alleen helemaal aan het einde.
"-_88" (URL-safe alfabet) FormatException. De standaarddecoder kent alleen de 64 tekens van het standaardalfabet.
null ArgumentNullException: Value cannot be null. (Parameter 's')

Nog een quirk om te onthouden: elke formatmisdadigheid krijgt dezelfde, eenzame foutmelding, The input is not a valid Base-64 string as it contains a non-base 64 character, more than two padding characters, or an illegal character among the padding characters. De melding somt alle drie de mogelijke oorzaken op en zegt niet welke je hebt geraakt, en die zegt ook niet waar. Als je een failende payload debugt: tel de tekens, check het alfabet, en check de padding, in die volgorde.

Decoderen zonder uitzonderingen: de Try-API's

Uitzonderingsgestuurde besturingsflow is een legitiem patroon, maar voor hoogvolume- of onvertrouwde input is de Try-familie de nettere optie. Ze is toegevoegd in .NET Core 2.1 en komt in twee vormen: de ene leest uit een string, de andere uit een karakterspan. Beide schrijven naar een buffer die je aanlevert en rapporteren hoe groot ze die hebben gevuld:

using System;
using System.Text;
string payload = "TWFu"; // elke payload, geldig of niet
Span<byte> buffer = stackalloc byte[4096];
if (Convert.TryFromBase64String(payload, buffer, out int written))
{
  string text = Encoding.UTF8.GetString(buffer[..written]);
  Console.WriteLine(text);
}
else
{
  Console.WriteLine("Not a valid Base64 payload.");
}

Twee gedragingen maken de Try-varianten aanvoelen als een ander soort dier. Ongeldige input geeft false terug in plaats van te gooien, dus een stroom misvormde payloads kost je een tak in plaats van een uitzondering. Eén kanttekening: een null-input valt niet onder de afspraak - die gooit een ArgumentNullException - dus de Try-bewaker dekt kapotte payloads, en een eventueel ontbrekende waarde heeft eerst toch haar eigen null-check nodig. De zustermethode Convert.TryFromBase64Chars doet hetzelfde werk vanaf een ReadOnlySpan<char>, wat handig is wanneer de payload in een grotere karakterbuffer woont en je eerst niet een substring af wilt snijden. Maak de outputbuffer ruim: de gedecodeerde lengte is op zijn hoogst driekwart van de (niet-witruimte) inputlengte, en de written-out-parameter vertelt je precies hoeveel eruit is gekomen.

Span-gebaseerd decoderen met System.Buffers.Text.Base64

Wanneer je allocaties telt, of wanneer je wilt dat de decoder zijn falingen beschrijft in plaats van ze te gooien, is de System.Buffers.Text.Base64-klasse het gereedschap. Het is sinds .NET Core 2.1 een statische klasse in de standaardlibrary, en het werkt met spans in plaats van beheerde arrays. Har decodeermethode geeft een OperationStatus-waarde terug met vier waarden: Done (succes), DestinationTooSmall (je buffer was te klein), NeedMoreData (de input is nog geen veelvoud van 4, blijf lezen), en InvalidData (dit is geen Base64). De laatste booleaanse parameter, isFinalBlock, is wat die twee van elkaar onderscheidt: die vertelt de decoder of er nog meer input aankomt. Hier is de one-shot-vorm, afgepast met de eigen helper van de klasse:

using System.Buffers;
using System.Buffers.Text;
using System.Text;
string payload = "TWFu";
byte[] input = Encoding.ASCII.GetBytes(payload);
byte[] output = new byte[Base64.GetMaxDecodedFromUtf8Length(input.Length)];
OperationStatus status = Base64.DecodeFromUtf8(input, output,
  out int consumed, out int written, isFinalBlock: true);
if (status == OperationStatus.Done)
{
  Console.WriteLine(Encoding.UTF8.GetString(output.AsSpan(0, written)));
  // Man
}

Nog twee leden van deze klasse verdienen een alinea. De eerste is IsValid, die een payload valideert zonder hem te decoderen. Die komt in byte-span- en karakterspan-vormen, en één overload rapporteert de gedecodeerde lengte naast het oordeel, zodat je een buffer kunt afmeten met één enkele check:

using System.Buffers.Text;
string payload = "TWFu";
if (Base64.IsValid(payload, out int decodedLength))
{
  Console.WriteLine("Valid, decodes to " + decodedLength + " bytes.");
  // Valid, decodes to 3 bytes.
}
else
{
  Console.WriteLine("Rejecting payload before allocating anything.");
}

De tweede is DecodeFromUtf8InPlace, voor de situatie waarin de Base64-tekst al in een buffer van jou ligt en je niets tegen overschrijven hebt. Decoderen krimpt de data, dus het resultaat wordt naar het begin van dezelfde buffer geschreven en de methode rapporteert hoe lang het is:

using System.Buffers;
using System.Buffers.Text;
using System.Text;
byte[] data = Encoding.ASCII.GetBytes("TWFu");
OperationStatus status = Base64.DecodeFromUtf8InPlace(data, out int written);
if (status == OperationStatus.Done)
{
  Console.WriteLine(Encoding.ASCII.GetString(data, 0, written));
  // Man, nu in de eerste drie bytes van dezelfde buffer
}

Eén gedrag om in je zak te stoppen: deze klasse slaat ook de vier gangbare witruimtekens over (spatie, tab, line feed, carriage return), dus een op regels afgebroken payload decodeert even goed. Zij is streng waar het ertoe doet: een payload waarvan de niet-witruimtelengte geen veelvoud van vier is, is InvalidData als het de laatste blok is, en tekens buiten het standaardalfabet worden ronduit geweigerd. Deze klasse doet nergens een stille opruiming.

URL-safe Base64: de Base64Url-klasse

Er bestaat een tweede alfabet voor dezelfde 64 waarden, en je zult het in C#-webwerk voortdurend ontmoeten. In het standaardalfabet zijn waarden 62 en 63 + en /, twee tekens die in URLs problemen veroorzaken: een + in een query string wordt routinematig gedecodeerd als spatie, en / en = hebben elk percent-encoding nodig. RFC 4648, sectie 5, lost dit op door - en _ in de plaats te zetten, die in geen enkele URL-context een speciale betekenis hebben, en maakt de hangende =-padding optioneel. Het resultaat heet base64url, en het is het alfabet van JWTs, API-tokens, upload-identifiers van bestanden en een grote hoeveelheid URLs (de 11-karakters video-identifiers van YouTube zijn base64url zonder padding).

Sinds .NET 9 levert de standaardlibrary een speciale klasse voor het mee: System.Buffers.Text.Base64Url. Het is de URL-safe tweeling van de Base64-klasse, met eigen decodeer-, valideer- en lengte-helpers:

using System.Buffers.Text;
using System.Text;
string token = "-__8";
byte[] bytes = Base64Url.DecodeFromChars(token);
Console.WriteLine(BitConverter.ToString(bytes));
// FB-FF-FC

Merk op wat de klassieke API met dat voorbeeld niet had gedaan. Dezelfde drie bytes encoderen als +//8 in het standaardalfabet, en Convert.FromBase64String("+//8") werkt, maar Convert.FromBase64String("-__8") gooit een uitzondering, want de URL-safe tekens zitten buiten haar alfabet. En base64url-payloads komen vaak zonder padding, wat de klassieke decoder ook weigert, omdat die op de volledige groep van vier staat. De Base64Url-klasse behandelt beide varianten van het probleem natief: zij decodeert TWE (drie tekens, geen padding) naar de twee bytes Ma, en decodeert TWE= even goed.

Als je project op een oudere runtime draait, zijn er twee praktische paden. Op .NET Framework 4.6.2 en hoger voeg je het Microsoft.Bcl.Memory NuGet-pakket toe, dat Microsoft specifiek publiceert om Base64Url terug te poorten (samen met een paar andere moderne types):

dotnet add package Microsoft.Bcl.Memory

Of, zonder enig pakket, normaliseer je de payload voordat je die aan de klassieke decoder geeft: zet de URL-safe tekens terug naar hun standaard-tweelinge, en vul het ontbrekende padding aan. Deze kleine helper is de meest gangbare zelfgemaakte base64url-decoder in C#-code, en het loont de moeite om die te kennen, want die werkt op elke runtime sinds .NET Framework 1.1:

using System;
using System.Text;
string segment = "TWE";
segment = segment.Replace('-', '+').Replace('_', '/');
segment += new string('=', (4 - segment.Length % 4) % 4);
byte[] bytes = Convert.FromBase64String(segment);
Console.WriteLine(Encoding.ASCII.GetString(bytes));
// Ma

De formule (4 - length % 4) % 4 is de hele paddingrekening: zij voegt nul, één of twee =-tekens toe zodat de lengte op een veelvoud van vier belandt, en de buitenste modulo houdt al gepadde input tegen extra tekens bij te dragen.

Van bytes naar woorden: tekst, Unicode en charsets

Decoderen levert je bytes op, en bytes zijn een volstrekt neutrale zaak. Ze worden pas "tekst" wanneer je een charset kiest om ze daarmee te lezen, en die keuze staat aan jou, want Base64 draagt geen informatie over welk charset de oorspronkelijke auteur gebruikte. In de praktijk betekent dat: ga uit van UTF-8, tenzij je een reden hebt om niet, en wees daar in de code expliciet over, want een expliciete Encoding.UTF8-aanroep is het verschil tussen een programma dat toevallig correct is en een dat correct is door ontwerp:

using System;
using System.Text;
string original = "h\u00e9llo \u4e16\u754c";
byte[] utf8 = Encoding.UTF8.GetBytes(original);
string packed = Convert.ToBase64String(utf8);
byte[] decoded = Convert.FromBase64String(packed);
string restored = Encoding.UTF8.GetString(decoded);
Console.WriteLine(restored == original);
// True: h\u00e9llo \u4e16\u754c komt perfect terug

De subtiele val is wat er gebeurt wanneer de bytes geen geldige UTF-8 zijn, omdat de payload in werkelijkheid Latin-1 was, of binair, of gewoon gecorrumpeerd. Standaard vervangt de UTF-8-decoder van .NET elke misvormde sequentie met het Unicode-replacement-teken (U+FFFD) en loopt verder. Geen uitzondering, geen waarschuwing: de data is simpelweg weg, veranderd in vraagtekens in je database. Als je moet weten wanneer dat gebeurt, construeer je de encoding met een strikte terugval, die stille vervanging omzet naar een luidruchtige DecoderFallbackException:

using System.Text;
byte[] bytes = Convert.FromBase64String("//4="); // de bytes FF FE, geen geldige UTF-8
Encoding strictUtf8 = Encoding.GetEncoding(
  "utf-8",
  new EncoderExceptionFallback(),
  new DecoderExceptionFallback());
string text = strictUtf8.GetString(bytes);
// Gooit een DecoderFallbackException, want FF FE is geen UTF-8-sequentie

Voor payloads waar je liever overleeft dan faalt, zijn de vervangings-terugvallen de zachtere optie, en je kiest de vervangings-tekenreeks zelf:

using System.Text;
byte[] bytes = Convert.FromBase64String("//4="); // de bytes FF FE, geen geldige UTF-8
Encoding forgivingUtf8 = Encoding.GetEncoding(
  "utf-8",
  EncoderFallback.ReplacementFallback,
  new DecoderReplacementFallback("[bad]"));
string text = forgivingUtf8.GetString(bytes);
Console.WriteLine(text);
// [bad][bad] in plaats van de stille U+FFFD-vervanging

Nog een C#-specifieke geschiedenisles: Encoding.Default betekent op verschillende runtimes verschillende dingen. Op .NET Framework onder Windows is het de ANSI-codepagina van het systeem (vaak Windows-1252), terwijl het op .NET (Core) UTF-8 zonder BOM is. Code die een payload via Encoding.Default rondsturt, kan daarom op een machine uit 2010 en een uit 2025 andere bytes produceren, en Base64 encodeert met plezier welke reeks je hem ook aanreikt. Als je ooit een gedecodeerde string ziet vol mojibake met accentletters, is Encoding.Default de eerste plaats om te kijken.

Bestanden en binaire payloads

Bestanden zijn het meest voor de hand liggende decoderingsdoel, want er is helemaal geen charset-vraagstuk: de bytes die je decodeert, zijn het bestand, byte voor byte, nullen en al. Het patroon is twee aanroepen en een bestand, en het duikt op in alles van afbeeldingsuploads tot backup-tools:

using System.IO;
string b64 = File.ReadAllText("payload.b64");
byte[] original = Convert.FromBase64String(b64);
File.WriteAllBytes("restored.bin", original);
Console.WriteLine("Restored " + original.Length + " bytes.");

Twee praktische notities. Als het bestand witruimte of regelafbrekingen kan bevatten (wat het, als tekstbestand, bijna zeker doet), dan doet de klassieke decoder dat gratis, zoals je zojuist zag. En als de payload groot is, ga dan helemaal niet via een string: sla de stap bestand-naar-string over en decodeer rechtstreeks vanuit de stream, wat de volgende sectie is. Voor een gedecodeerde payload die tekst is en waarvan je toevallig de charset kent, is het bestandsvoorbeeld de hele oplossing, en past de Encoding.UTF8.GetString-stap uit de charset-sectie precies tussen het decoderen en het gebruik.

Decoderen uit een stream: FromBase64Transform

De Convert-methodes zijn ontworpen voor payloads die in een string passen, en de officiële documentatie zegt dat met zoveel woorden: gebruik voor streamende data de transform-classes. FromBase64Transform maakt sinds .NET Framework 1.1 (2003) deel uit van System.Security.Cryptography, en het pluggt aan op CryptoStream, de allround pipe van de framework om data te transformeren terwijl die stroomt. De hele bestand-naar-bestand-decodering is een setup van vier regels:

using System.IO;
using System.Security.Cryptography;
using FileStream source = File.OpenRead("payload.b64");
using FromBase64Transform transform =
  new FromBase64Transform(FromBase64TransformMode.IgnoreWhiteSpaces);
using CryptoStream reader = new CryptoStream(source, transform, CryptoStreamMode.Read);
using FileStream target = File.Create("payload.bin");
reader.CopyTo(target);
Console.WriteLine("Done, " + target.Length + " bytes written.");

De constructor neemt een mode, en de twee modes zijn de moeite waard om bij naam te kennen. IgnoreWhiteSpaces (de standaard, overeenkomend met het witruimtebeleid van de klassieke decoder) slaat de vier gangbare witruimtekens over terwijl de stream stroomt, wat je wilt voor e-mail-omwikkelde of door nieuwe regels gezette payloads. DoNotIgnoreWhiteSpaces is strikt: het eerste niet-alfabetkarakter dat het tegenkomt gooit een FormatException, wat je wilt wanneer een verdwaalde spatie in de payload een bug moet zijn, geen schouderophalen. Onder de motorkap verwerkt de transform de input in groepen van vier tekens en geeft de drie bytes terug die elke groep produceert, waarbij TransformFinalBlock de staart afhandelt. Je roept die methodes zelden zelf aan, want CryptoStream doet dat voor je, maar het feit van de groep van vier is belangrijk: als je de transform ooit handmatig voedt, voed die dan in veelvouden van vier, anders zal de laatste partiële groep in de finale blok zitten.

JWTs: drie segmenten, één punt

Een JSON Web Token is de drukst-gebruikte base64url-payload in C#-webontwikkeling, en zijn vorm is misleidend simpel: drie segmenten gescheiden door punten. Het eerste is de geëncodeerde header, het tweede de geëncodeerde payload (ook bekend als claims), en het derde de signatuur. Elk van de eerste twee is base64url van een UTF-8 JSON-document, zonder padding, volgens de JWS-specificatie. Opsplitsen en decoderen is twee regels C#:

using System;
using System.Buffers.Text;
using System.Text;
using System.Text.Json;
string jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0MiIsIm5hbWUiOiJBZGEifQ.c2lnbmF0dXJl";
string[] parts = jwt.Split('.');
string headerJson = Encoding.UTF8.GetString(Base64Url.DecodeFromChars(parts[0]));
string payloadJson = Encoding.UTF8.GetString(Base64Url.DecodeFromChars(parts[1]));
using JsonDocument doc = JsonDocument.Parse(payloadJson);
Console.WriteLine(doc.RootElement.GetProperty("name").GetString());
// Ada

Op runtimes vóór .NET 9 gaat hetzelfde werk via de normalisatie-helper uit de URL-safe-sectie: zet - en _ terug naar + en /, pad het segment op tot een veelvoud van vier, en decodeer met Convert.FromBase64String. Beide aanpakken geven je hetzelfde JSON; kies de die past bij je doelplatform.

Eén grens om scherp te houden: een JWT decoderen is geen JWT verifiëren. De decodage hierboven leest met plezier de claims van een token met een rommelsignatuur, want de signatuur is een aparte cryptografische check over de eerste twee segmenten. Voor productiewerk met tokens: parse helemaal niet handmatig, het System.IdentityModel.Tokens.Jwt-pakket (uit de Microsoft.IdentityModel-familie) handelt het parsen, de validatie en de vervaldatum in één aanroep af, en zijn base64url-behandeling is exact het alfabet dat deze sectie beschrijft. Decodeer handmatig voor debuggen en kleine tools; verifieer met de library voor alles waartoe een gebruiker kan komen.

Data-URIs en ingebedde afbeeldingen

Er is een hele klasse C#-code waarvan het werk is om een data:-URI te ontvangen, want HTML, CSS en een grote hoeveelheid web-API's gebruiken ze om binaire content inline in te bedden. Het schema, gestandaardiseerd door RFC 2397, is data:[mediatype][;base64],payload: alles vóór de eerste komma is metadata (de MIME-type en de ;base64-flag), alles erna is de payload. Wanneer de ;base64-flag aanwezig is, is de payload een Base64-string, en opsplitsen bij de komma is de hele parse:

using System;
using System.Text;
string dataUri = "data:image/png;base64,iVBORw0KGgo=";
int comma = dataUri.IndexOf(',');
string mediaType = dataUri[..comma];        // data:image/png;base64
string b64 = dataUri[(comma + 1)..];        // iVBORw0KGgo=
byte[] imageBytes = Convert.FromBase64String(b64);
Console.WriteLine(imageBytes.Length);
// 8: de PNG-signatuurbytes 89 50 4E 47 0D 0A 1A 0A

Het iVBORw0KGgo=-prefix in het voorbeeld is de Base64-vorm van het acht-byte PNG-magic-getal, en het is een handige vingerafdruk: elke data-URI voor een echte PNG begint zo, dus het is een snelle plausibiliteitscheck wanneer je onvertroude HTML parst. Twee praktische notities voor C#-ontwikkelaars. Eerst begrijpt de Uri-klasse data-URIs op .NET natief: new Uri("data:text/plain;base64,TWFu") parst zonder problemen en rapporteert Scheme == "data", dus als je code op URIs routeert, zullen data-URIs in de pipeline verschijnen en moet je beslissen hoe je ze behandelt. Ten tweede, onthoud wat een data-URI écht is: een volledige kopie van het bestand, opgeblazen met een derde, zittend in je document. Dat is prima voor een favicon van 4 KB en pijnlijk voor een logo van 4 MB, dus als jij ze genereert (het encoderingsartikel behandelt die kant), bepaal dan de grootte van de afbeelding voordat je hem encodeert.

HTTP: Basic auth en API-uitwisselingen

Base64 is verweven met HTTP op minimaal één plek die je bij elk API-werk zult raken: het Basic-authenticatieschema. De client stuurt Authorization: Basic opgevolgd door de Base64-codering van username:password, aan elkaar gelijmd met een dubbele punt. Aan de serverkant is decoderen van een binnenkomende header daarom: het Basic -prefix eraf halen, decoderen, en opsplitsen bij het eerste dubbele punt:

using System;
using System.Text;
string header = "Basic YWRhOnMzY3JldA==";
string encoded = header["Basic ".Length..].Trim();
string credentials = Encoding.UTF8.GetString(Convert.FromBase64String(encoded));
int colon = credentials.IndexOf(':');
string user = credentials[..colon];
string password = credentials[(colon + 1)..];
Console.WriteLine(user);      // ada
Console.WriteLine(password);  // s3cret

De UTF-8-stap telt meer dan het lijkt: RFC 7617 pint de charset eigenlijk niet vast, laat de standaard ongedefinieerd voor achterwaartse compatibiliteit en staat alleen een adviserende UTF-8-aanwijzing toe, maar die aanwijzing is wat elke moderne server verwacht, dus een gebruikersnaam met een accentletter produceert een andere (en correcte) bytestring dan dezelfde gebruikersnaam gelezen als Latin-1. De decoderingskant van Basic auth is de eenvoudige kant van dit patroon; in ASP.NET Core zul je hem meestal tegenkomen via de authenticatie-handlers in plaats van rauwe headers, maar dezelfde decodeerlogica is wat ze daaronder uitvoeren, en het is precies het soort code dat je nodig hebt wanneer je integratietests schrijft die een API-server naspelen. De spiegelbeweging, de header aan de clientkant bouwen, is aan de encoderingskant een éénregelaar, en die krijgt een volledig voorbeeld in het encoderingsartikel.

E-mail: MIME en op regels afgebroken payloads

E-mail is waar Base64 zijn reputatie verdiende, en het is nog steeds de bron van veel van de payloads die C#-diensten ontvangen. SMTP was oorspronkelijk een 7-bit-protocol, dus binaire bijlagen kunnen niet rauw reizen: de MIME-specificatie (RFC 2045) encodeert ze als Base64 met een Content-Transfer-Encoding: base64-header, breekt de output af op 76 tekens, en scheidt de regels met carriage-return-line-feed-paren. Het lichaam van een echte bijlage ziet er daarom uit als een kolom van regels van 76 tekens, en het goede nieuws voor C# is dat de klassieke decoder al weet hoe ze dat leest: omdat ze witruimte overal in de string overslaat, kun je haar het hele omwikkelde lichaam geven, nieuwe regels en al, en ze decodeert het alsof de regelafbrekingen er nooit waren:

using System;
using System.Text;
string attachmentBody = "TWFu\r\nTWFu\r\nTWFu";
byte[] bytes = Convert.FromBase64String(attachmentBody);
Console.WriteLine(Encoding.ASCII.GetString(bytes));
// ManManMan

Voor payloads die via een stream in plaats van via een string aankomen, is de FromBase64Transform met zijn witruimte-negeernde mode hetzelfde verhaal in streamende kleding. En wanneer je meer moet doen dan het lichaam decoderen, wanneer je de MIME-structuur moet aflopen, headers moet parsen, geneste multipart-secties moet afhandelen, of elke bijlage uit een echt .eml-bestand moet extraheren, is het ecosysteemantwoord in C# het MimeKit-pakket: het is de standaard MIME-library voor .NET, het behandelt de Base64- en quoted-printable-content-transfer-encoderingen intern, en het is het gereedschap om naar te grijpen van het moment dat "alleen het lichaam decoderen" stopt met je probleem te beschrijven. De eigen MailMessage-klasse van de framework decodeert eenvoudige bijlagen voor je, maar haar MIME-ondersteuning is bewust bescheiden volgens moderne maatstaven.

PEM-certificaten

PEM is het geharnaste formaat van de TLS-wereld: een Base64-lichaam tussen de -----BEGIN CERTIFICATE------ en -----END CERTIFICATE------markeringen, afgebroken op 64 tekens, zoals gespecificeerd door RFC 7468. C#-ontwikkelaars ontmoeten het als de certificaatbestanden achter elke HTTPS-endpoint, en het decoderingsverhaal hier is beter dan je zou verwachten, want sinds .NET 6 parst de framework PEM voor je, Base64-lichaam en al:

using System.IO;
using System.Security.Cryptography.X509Certificates;
string pem = File.ReadAllText("server.pem");
X509Certificate2 certificate = X509Certificate2.CreateFromPem(pem);
Console.WriteLine(certificate.Subject);
// CN=server.example.com

Nergens handmatig Base64 in dat: CreateFromPem vindt de markeringen, ontpakt het lichaam, decodeert het, en geeft je een werkend certificaat terug. (De familie heeft zustervormen voor privésleutels en voor de gecombineerde certificaat-plus-sleutelvorm, mocht je infrastructuur je die toespelen.) Als je op een oudere runtime zit, of als je de rauwe DER-bytes nodig hebt die in het harnas zitten, is de handmatige versie het afstrippen en decoderen in twee stappen, en het loont de moeite om te kennen, want hetzelfde patroon werkt voor alles wat in PEM is geharnast:

using System;
using System.Text;
string pem = File.ReadAllText("server.pem");
string body = pem
  .Replace("-----BEGIN CERTIFICATE-----", "")
  .Replace("-----END CERTIFICATE-----", "")
  .Replace("\r", "")
  .Replace("\n", "");
byte[] der = Convert.FromBase64String(body);
Console.WriteLine(der.Length);
// De lengte van het DER-certificaat in het harnas

De valkuilen in deze hoek zijn allemaal witruimte: PEM-bestanden dragen CRLF-regeleinden mee van de meeste certificaattools, dus strip zowel \r als \n weg vóór je decodeert, niet alleen de line feeds. En verwar het certificaatlichaam niet met een privésleutellichaam, die andere markeringen en andere inhoud heeft; een decoder zal je daar niet uit redden.

Configuratie, omgevingsvariabelen en databases

De derde thuis van Base64 in C#-applicaties is opslag: configbestanden, omgevingsvariabelen en databasekolommen. Het patroon is overal hetzelfde. Een binaire of geheime waarde wordt bij binnenkomst in een string geëncodeerd, en bij vertrek weer naar bytes gedecodeerd. Omgevingsvariabelen zijn het meest zichtbare voorbeeld, want ze kunnen alleen tekst bevatten:

using System;
using System.Text;
string? encoded = Environment.GetEnvironmentVariable("API_KEY_B64");
if (encoded == null)
{
  throw new InvalidOperationException("Set the API_KEY_B64 environment variable first.");
}
byte[] keyBytes = Convert.FromBase64String(encoded);
string apiKey = Encoding.UTF8.GetString(keyBytes);
Console.WriteLine(apiKey.Length + " characters of API key, ready to use.");

In een database verschijnt hetzelfde idee meestal als een byte[]-eigenschap die je in een tekstkolom wilt opslaan voor draagbaarheid, en Entity Framework Core heeft hiervoor een ingebouwd mechanisme: een value converter die je encodeer- en decodeerfuncties transparant uitvoert bij elke lees- en schrijfbewerking:

using Microsoft.EntityFrameworkCore;
modelBuilder.Entity<Avatar>()
  .Property(a => a.ImageData)
  .HasConversion(
    v => Convert.ToBase64String(v),
    v => Convert.FromBase64String(v));

Die ene converter is de hele database-integratie: ImageData blijft een byte[] in je C#-code, en de database ziet een Base64-string. Bij deze sectie horen twee waarschuwingen. Eerst houdt een kolom van een gegeven breedte als geëncodeerde tekst ongeveer een derde minder data aan dan als rauw binair, door de belasting van 4 tekens per 3 bytes, dus dimensioneer de kolom op de geëncodeerde lengte als de breedte vast is. Ten tweede, en deze is de beveiligingsnotitie: Base64 in een configbestand is een handigheid om een waarde op één regel te houden, geen bescherming voor de waarde. Iedereen die het configbestand kan lezen, kan de sleutel met één commando decoderen, en daarom horen echte geheimen in een geheimenkluis, en is de Base64 daar gewoon het transportformaat.

Als de payload groot is

Base64-decoderen heeft een prettige eigenschap die encoderen niet heeft: de output is altijd kleiner dan de input, ruwweg driekwart ervan. Een textpayload van 10 megabyte decodeert naar ongeveer 7,5 megabyte aan bytes, dus een decode kan je geheugen nooit opblazen zoals een encode wel kan. De rekensom, als je vooraf een buffer wilt afmeten, komt neer op een van twee aanroepen: Base64.GetMaxDecodedFromUtf8Length voor de strikte span-klasse, of de platte deling, length / 4 * 3 voor de klassieke API, plus een correctie voor witruimte als de input is afgebroken. (De helper geeft de maximale mogelijke gedecodeerde lengte terug: de echte lengte is die alleen gelijk wanneer de laatste groep geen padding heeft, en is één of twee bytes minder wanneer ze eindigt in één of twee padtekens.)

Wanneer de payload écht groot is, is de juiste zet echter niet een grotere buffer - het is helemaal geen buffer: sla de string helemaal over en laat FromBase64Transform de decode van bron naar bestemming streamen, zoals getoond in de streams-sectie. De enige regel om te respecteren is de uitlijning op groepen van vier: een Base64-stream kan alleen worden gesneden op veelvouden van vier tekens (nadat witruimte is meegerekend), dus als je de transform ooit handmatig voedt, lees dan in chunks die veelvouden van vier zijn en laat TransformFinalBlock de rest afvoeren. Voor alles korter dan honderden megabytes is de one-shot-decode snel genoeg dat dit een optimalisatie is, geen noodzaak, maar de streamende vorm is ook de die zich goed gedraagt onder geheugenlimites, en dat zijn precies de omgevingen waarin grote payloads graag leven.

Een decoder in je terminal

Er is in elke taal een bevredigend moment, waarin een consoleprogramma van 15 regels een commandoregeltool wordt, en de C#-Base64-decoder is een goede om dat mee te doen, want lezen van standaardinput maakt het direct inzetbaar in shell-pipes. Hier is de hele tool: die leest de Base64-payload uit de pipe (of uit een argument), decodeert die, en schrijft de rauwe bytes naar een bestand:

using System;
using System.IO;
using System.Text;
string input = args.Length > 0 ? File.ReadAllText(args[0]) : Console.In.ReadToEnd();
byte[] bytes = Convert.FromBase64String(input.Trim());
File.WriteAllBytes("output.bin", bytes);
Console.Error.WriteLine("Wrote " + bytes.Length + " bytes to output.bin.");

Bouw die één keer, en die zit ernaast aan de eigen base64-utility van de shell voor de dagen dat je specifiek de decoder van de .NET-runtime wilt: pipe een bestand erdoorheen, keten die aan andere tools, en de strikte C#-validatieregels (tolerant voor witruimte, strikt voor het alfabet, strikt voor padding) worden onderdeel van je pipeline. De Trim() doet daar stil werk, en vangt de hangende nieuwe regel die teksteditors graag toevoegen, hoewel de decoder die eerlijk gezegd toch had genegeerd. Voor de URL-safe payloads die steeds vaker in API-logs verschijnen, is hetzelfde skelet met de Base64Url-decode uit de URL-safe-sectie de hele verandering.

Snelheid: wat mag je verwachten

Base64 in moderne .NET is snel, en het wordt sneller. De runtime-implementaties van zowel de Convert-methodes als de System.Buffers.Text-classes zijn geoptimaliseerd met SIMD-vectorinstructies waar de hardware ze ondersteunt, en ze verwerken vele karakters per cyclus. In de praktijk betekent dat dat payloads van meerdere megabytes decoderen in enkele cijfers tot lage dubbele cijfers milliseconden op een gangbare desktopmachine, wat snel genoeg is dat Base64-decoderen effectief gratis is in elke applicatie die je zult schrijven. Het praktische prestatieadvies gaat daarom over de vorm van je code, niet over de decoder zelf. Gebruik op hete codepaden bij voorkeur de Try-methodes of de span-methodes die een status teruggeven, waar misvormde input mogelijk is en uitzonderingen duur zouden zijn. Hergebruik buffers met de in-place- en span-API's wanneer je duizenden kleine payloads in een lus decodeert, in plaats van bij elke aanroep een verse array te alloceren. En decodeer dezelfde payload nooit twee keer: één keer is de kosten, en een tweede decode van een veld dat je al gedecodeerd hebt is pure verspilling die in profielen opduikt als een mysterieuze tweede Base64-piek.

Beveiliging: wat Base64 níét doet

Het belangrijkste beveiligingsfeit over Base64 is precies het een dat beginners het vaakst missen: het is encoderen, niet versleutelen. Een Base64-string is door iedereen, met elk gereedschap, in een fractie van een seconde leesbaar, en C# maakt het lezen ervan een éénregelaar, zoals dit hele artikel heeft aangetoond. Base64 heeft geen sleutel, geen algoritme-parameter, en geen zwakte om te exploiteren, want het probeerde nooit iets te verbergen: het is een transportformaat, een manier om binair te laten overleven in uitsluitend tekstkanalen. Behandel het daar volgens. Stop nooit een wachtwoord, een token of een geheim in een configbestand dat door Base64 "wordt beschermd", want de bescherming is precies één Convert.FromBase64String-aanroep diep. Als de waarde geheim moet zijn, heeft het echte bescherming nodig (een geheimenbeheerder, een versleutelde opslag, op z'n minst een besturingssysteem-toegangscontrole), en is de Base64 gewoon de vorm die het draagt tijdens het reizen.

De tweede beveiligingsnotitie gaat over je eigen decodeerpad. Elke payload die je decodeert is onvertrouwde input totdat is bewezen dat het anders is, en de twee falingsmodi waartegen je moet ontwerpen zijn de luidruchtige (ongeldige input, waartoe de klassieke API een FormatException teruggeeft die je moet vangen en omzetten in een 400, niet een 500) en de stille (geldig Base64 dat decodeert naar bytes die niet zijn wat je verwachtte: geen UTF-8, niet het bestandstype waar je om vroeg, of langer dan je had ingepland). Valideer vóórdat je vertrouwt: check de lengte met IsValid of de Try-familie vóórdat je algoceert, check de gedecodeerde bytes tegen een verwachte signatuur (de PNG-magic, de PKCS-header) vóórdat je ze aan een afbeeldings- of certificaatparser geeft, en meet je buffers af vanaf de geëncodeerde lengte vóórdat je decodeert, niet erna. Base64 decodeert alles dat goed gevormd is; beslissen wat goed gevormd betekent voor jouw applicatie is jouw job.

Valkuilen om te kennen voordat ze bijten

Dit zijn de C#-specifieke valkuilen die doorlopend weer opduiken in echte code, en elke één heeft een concrete oorzaak in de werking van de framework:

  • Binair via een string. Een C# string is een reeks UTF-16-code-eenheden, en gedecodeerd Base64 is dat niet. Het moment dat je gedecodeerde bytes in een stringvariabele propt (een Console.WriteLine van een gedecodeerde PNG, een string-concat met binair, een JSON-library die "tekst" serialiseert), zal iets stroomafwaarts het kapotmaken. Houd gedecodeerd binair in byte[] tot het een plek bereikt die écht bytes wil.
  • De Encoding.Default-splitsing. Code die gedecodeerde bytes leest met Encoding.Default produceert andere tekst op .NET Framework (de Windows-ANSI-codepagina) dan op .NET (UTF-8). Dezelfde payload, twee verschillende outputs, geen uitzondering. Stel je tekenset expliciet in.
  • JWT-segmenten en de klassieke decoder. Een rauw JWT-segment voeden aan Convert.FromBase64String faalt op twee manieren tegelijk: de -/_-tekens zitten buiten het standaardalfabet, en het ontbrekende padding breekt de lengteregel. Normaliseer eerst, of gebruik Base64Url.
  • Witruimte die je ziet en witruimte die je niet ziet. De decoder slaat spatie, tab, line feed en carriage return over, en slaat niets anders over. Een non-breaking space, een Unicode-lijnscheiding, of een vertical tab in een payload (ze overleven allemaal copy-paste van sommige webpagina's) is een FormatException, geen schouderophalen.
  • Eén foutmelding voor elke misdrijf. De FormatException van de klassieke decoder zegt niet welke regel scheurde of waar. Debug door eerst de lengte te checken, dan het alfabet, dan de padding, in die volgorde, of schakel over op TryFromBase64String en IsValid voor een booleaans antwoord.
  • Stille UTF-8-vervanging. Encoding.UTF8.GetString zet misvormde bytesequenties om naar U+FFFD zonder klacht. Als de payload misschien geen geldige UTF-8 is, gebruik dan de strikte terugval uit de charset-sectie, anders onderzoek je ontbrekende data weken nadat het gebeurde.
  • Stream snijden op de verkeerde plek. Een Base64-stream kan alleen worden gesneden op veelvouden van vier tekens. Maak van een streamende decode chunks op een andere grens en de laatste partiële groep belandt in TransformFinalBlock, waar die óf thuishoort óf je uitlijningrekening kapotmaakt.
  • PEM-regeleinden. Certificaatbestanden dragen CRLF. Strip zowel \r als \n weg wanneer je het harnas handmatig ontpakt, anders is de eerste regel van je "gedecodeerde" DER een carriage return die de kleren van een databyte draagt.
  • Dubbele codering. Als een payload al Base64 was toen die bij jou aankwam (een config die een Base64-string had geëncodeerd, een API die de output van een andere encoder encodeerde), geeft één decode je méér Base64, niet je data. De rondreis sluit pas na evenveel decodes als er encodes waren, en de encoderkant van die bug is het onderwerp van het encoderingsartikel.

Een korte geschiedenis van Base64 in C#

Het Base64-verhaal in C# is ook een verhaal over het opgroeien van het .NET-platform, en het is langer dan de meeste mensen verwachten:

  • .NET Framework 1.1, april 2003. Convert.FromBase64String en zijn zustermethodes arriveren, en ze dragen het ontwerp dat de API nog steeds definieert: strikt over het alfabet, gul over de vier witruimtekens, nonchalant over zijn fouten. Grootste deel van de volgende twee decennia is deze ene methode "de" Base64-decoder in C#.
  • .NET 2.0, 2005. De Base64FormattingOptions-enum komt bij Convert, en brengt de MIME-stijl regelafbrekingen naar de encoderingskant (en de overeenkomstige witruimte-tolerantie naar de decoderingskant, waar ze al stil werkt).
  • .NET Core 2.1, 2018. Het span-tijdperk. Convert krijgt de Try-methodes en een span-gebaseerde encode, en de nieuwe System.Buffers.Text.Base64-klasse arriveert met zijn OperationStatus-contract, decoderen op dezelfde plek en IsValid, gebouwd voor de nul-allocatiewereld van de geheugengerichte herschrijving.
  • .NET 5, 2020. De hex-zusters (Convert.ToHexString en maten) verschijnen, hetzelfde ontwerppatroon als Base64 toegepast op een alfabet van 16 symbolen, een teken dat het conversie-klasse-patroon een huisstijl was geworden.
  • .NET 6, 2021. X509Certificate2.CreateFromPem maakt PEM een eersteklas input, en een hele klasse handmatig harnas-afstripcode wordt optioneel op moderne runtimes.
  • .NET 9, november 2024. System.Buffers.Text.Base64Url landt eindelijk in de framework na jaren aan verzoeken van de community, en het Microsoft.Bcl.Memory-pakket poort het terug naar .NET Framework 4.6.2 en hoger voor de legacy-codebases die nog steeds alles draaien.
  • .NET 11, bij het schrijven in preview. De volgende release, verwacht in het najaar 2026, voegt verdere Base64-gemak-API's en overloads toe aan de bestaande types, en zet de langzame mars naar een ergonomischer oppervlak voort.

Het loont de moeite om in het achterhoofd te houden: de codering zelf is veel ouder dan al dit. Het eerste gestandaardiseerde gebruik van wat we nu MIME Base64 noemen was het Privacy-Enhanced Mail-protocol in 1987 (RFC 989), MIME standaardiseerde in 1993 de op 76 tekens afgebroken vorm, en RFC 4648 in 2006 gaf het formaat zijn moderne, alfabetbewuste specificatie, inclusief de URL-safe variatie. C# erfde het allemaal: elke regelafbrek- en padding-eigenaardigheid die je tegenkomt in een 30 jaar oude e-mailindeling is een eigenaardigheid waarvoor de C#-decoder was ontworpen.

Verrassende C#-feitjes

  • De kleinste rooktest. "TWFu" decodeert naar Man. Drie bytes, geen padding, geen excuses. Het is de hello world van Base64-debuggen in C#, en het oefent het hele gelukkige pad af in vier tekens.
  • Een decoder met een postgeschiedenis. De witruimte-tolerantie is geen ongeluk van de implementatie - het is een ontwerpbeslissing geërfd van MIME: een volledig e-maillichaam van regels van 76 tekens, met al zijn CRLF-paren, is een geldig enkel argument voor Convert.FromBase64String. De decoder is gebouwd om het formaat te eten dat e-mail dertig jaar lang gebruikt.
  • Eén fout, drie oorzaken. De klassieke FormatException-melding somt alle drie de falingsmodi op die zij zou kunnen rapporteren (slecht karakter, te veel padding, verkeerd geplaatste padding) en zegt niet welke er is afgaan. Het is de enige foutmelding in het API-oppervlak die werkt als een meerkeuzevraag.
  • Een namespace die een beetje liegt. System.Buffers.Text klinkt alsof het over tekstverwerking gaat, maar het is écht de thuisbasis van binair-naar-tekst-conversie in het algemeen: de Utf8Parser en Utf8Formatter die getallen en datums rechtstreeks naar UTF-8 parsen wonen recht in de buurt van de Base64-classes.
  • Padding is optioneel aan één kant van de familie. De Base64Url-klasse decodeert AQIDBA (zes tekens, geen padding) en AQIDBA== (dezelfde bytes met padding) naar dezelfde vier bytes, terwijl de klassieke decoder alleen de gepadde vorm accepteert. Twee decoders, twee afspraken, één runtime.
  • Strings die niet zouden mogen bestaan. Een C#-string mag wettelijk NUL-bytes bevatten, dus Encoding.UTF8.GetString van gedecodeerd binair kan een "string" produceren vol met controltekens die de console, je CSV-schrijver en de helft van de JSON-libraries op aarde elk anders zullen behandelen. Het typesysteem staat het toe; het ecosysteem grotendeels niet.
  • Een 1.1-relikwie in goede staat. Convert.FromBase64CharArray heeft sinds april 2003 dezelfde drie-parameter-ondertekening, overleefd de generics-revolutie, de Span-revolutie en de URL-safe-revolutie zonder dat er een enkele overload is bijgekomen. Het char-array-tijdperk van C# is niet weg; het rust gewoon even.
  • Elf tekens, acht bytes. De video-identifiers van YouTube zijn base64url zonder padding: 11 tekens die decoderen naar 8 bytes. Base64Url.GetMaxDecodedLength(11) levert de 8 op, en de decode is een éénregelaar, wat een fijne manier is om de dag te eindigen als je het soort persoon bent dat dat soort dingen schrijft.

De andere richting

Dat was de decoderingskant, en dat is waar het grootste deel van de pijn zit, want decoderen is waar je de data van anderen tegenkomt: hun paddingkeuzes, hun regelafbrekingen, hun alfabetten, hun tokens. De tegenovergestelde richting, je eigen bytes pakken en in Base64 verpakken, is een rustiger probleem met zijn eigen set beslissingen die te nemen zijn, en zijn eigen set valkuilen. Base64-encoderen in C#, van de 76-karaktersvraag tot URL-safe tokens, is diep behandeld in het bijbehorende artikel hieronder gelinkt, en het is een kort, bevredigend leesstuk zodra je weet waarop je moet letten.

Laatst bijgewerkt: 2026-10-06

Gerelateerd artikel: Base64-codering in C# (CSharp): een complete gids