Base64-Dekodierung in C# (CSharp): Ein vollständiger Leitfaden
Sie erkennen es in einem Moment: ein Fluss aus Buchstaben und Ziffern, das gelegentliche + oder /, und vielleicht ein = oder zwei, die am Ende baumeln. Irgendwo zwischen einer API-Antwort, einem E-Mail-Anhang, einer Konfigurationsdatei und einem JWT hat jemand binäre Daten in Text gepackt, und jetzt ist es Ihre Aufgabe, es zu öffnen. Das ist die Dekodier-Seite von Base64 in C#, und die erste gute Nachricht ist: Sie brauchen nichts anderes als das Framework. Der Dekodierer lebt seit über zwanzig Jahren im System-Namespace, und jede moderne .NET-Laufzeitumgebung liefert ihn immer noch mit, mit mehr Optionen und besserer Leistung als das Original.
Ein kurzer Wiederholer, denn die Startseite dieser Site erklärt das Format im Ganzen: Vier Zeichen aus einem Alphabet von 64 Symbolen tragen drei Bytes Daten, und ein oder zwei =-Zeichen am Tail markieren die übrig gebliebenen Bytes. Das Dekodieren läuft diesen Tausch in umgekehrter Richtung ab, also ist das Ergebnis ungefähr drei Viertel so groß wie die Eingabe. Mit dem Grundriss des Problems im Kopf öffnen wir ein paar Pakete.
Die Dekodierer-Familie: Kennen Sie Ihre Optionen
Bevor das erste Beispiel kommt, hier ist die ganze Familie von Dekodier-APIs, auf die Sie zugreifen können, und die Situation, für die jede einzelne gebaut wurde. Alles, was hier aufgelistet ist, ist Teil der .NET-Laufzeitumgebung selbst, mit Ausnahme der URL-sicheren Klasse auf älteren Frameworks, die in einem kleinen NuGet-Paket mitfährt:
| API | Verfügbar seit | Wofür sie da ist |
|---|---|---|
Convert.FromBase64String(string) |
.NET Framework 1.1 (2003) | Der Klassiker. Ein String hinein, ein frisches byte[] heraus. Überspringt gewöhnlichen Weißraum, wirft bei allem anderen. |
Convert.FromBase64CharArray(char[], int, int) |
.NET Framework 1.1 (2003) | Das gleiche Dekodieren, liest aus einem Ausschnitt eines Zeichens-Puffers, den Sie bereits besitzen. |
Convert.TryFromBase64String, Convert.TryFromBase64Chars |
.NET Core 2.1 (2018) | Boolesch statt Ausnahmen, schreibt in einen Span, den Sie bereitstellen. Der freundliche Wächter für unzuverlässige Eingaben. |
System.Buffers.Text.Base64 |
.NET Core 2.1 (2018) | Die strenge Span-API: Statuscodes statt Ausnahmen, In-Place-Dekodierung und IsValid-Vorabchecks. |
System.Buffers.Text.Base64Url |
.NET 9 (2024) | Das URL-sichere Alphabet (- und _ statt + und /), mit oder ohne Padding. Auf .NET Framework 4.6.2+ und .NET Standard 2.0: das Microsoft.Bcl.Memory-NuGet-Paket. |
FromBase64Transform + CryptoStream |
.NET Framework 1.1 (2003) | Dekodieren per Stream: Datei zu Datei, Netzwerk zu Festplatte, Chunk für Chunk, ohne das gesamte Payload zu laden. |
Wenn Ihr Projekt eine .NET-Version ab 2018 als Ziel hat, sind die ersten vier Zeilen im Kasten. Base64Url braucht .NET 9 oder neuer, oder das Microsoft.Bcl.Memory-Paket auf allem Älteren. Und ein Hinweis nach vorn: Die .NET-11-Bibliotheken, beim Schreiben dieses Artikels im Preview mit einem allgemeinen Release Ende 2026 erwartet, fügen den vorhandenen Typen weitere Base64-Komfort-APIs und Overloads hinzu, also wächst die Familie weiter. Sonstige Pakete braucht nichts in diesem Artikel.
Das Arbeitstier: Convert.FromBase64String
Neunzig Prozent des Dekodier-Alltags in C# sind ein einziger Aufruf. Geben Sie ihm einen String, und er gibt Ihnen genau die Bytes zurück, die darin verpackt waren:
using System;
using System.Text;
string packed = "TWFu";
byte[] bytes = Convert.FromBase64String(packed);
string text = Encoding.UTF8.GetString(bytes);
Console.WriteLine(text);
// Man
Drei Details lohnen es, sie sich einzuprägen. Erstens ist der Rückgabewert Bytes, kein Text: Es ist ein byte[], der Dekodierer ist von Anfang bis Ende byte-orientiert, und genau das wollen Sie, denn das Payload könnte ein Satz sein, eine PNG, ein Zertifikat oder ein Hash, und keines davon sollte Sonderbehandlung bekommen. Der Sprung von Bytes zurück zu lesbarem Text ist ein eigener, bewusster Schritt durch Encoding, und in genau diesem Schritt leben die Zeichensatz-Entscheidungen (mehr dazu weiter unten). Zweitens allokiert der Dekodierer bei jedem Aufruf ein frisches Array, exakt auf die dekodierte Länge dimensioniert, also gibt er Ihnen nie einen Puffer mit überschüssiger Kapazität. Drittens ist der Vertrag klein und ehrlich: Ein leerer String dekodiert zu einem leeren Array, eine null-Referenz wirft ArgumentNullException, und alles, was kein gültiges Base64 ist, wirft FormatException. Alles andere ist eine Ausführung dieser drei Regeln.
Was es verzeiht und was es ablehnt
Hier zeigt der C#-Dekodierer seine Persönlichkeit, und eine eigenwillige. Bei genau einem Thema ist er großzügig - dem Weißraum - und bei allem anderen gnadenlos. Der Dekodierer überspringt genau vier Zeichen, wo immer sie im String auftauchen: das Leerzeichen (U+0020), der Tab (U+0009), der Zeilenvorschub (U+000A) und der Wagenrücklauf (U+000D). Diese Politik ist ein bewusstes Nicken in Richtung E-Mail, wo Base64-Payloads in 76-Zeichen-Zeilen eingewickelt ankommen, und das bedeutet, dass ein MIME-eingewickelter Anhang ohne jegliche Vorverarbeitung dekodiert wird. Alles, was außerhalb des 64-Symbole-Alphabets liegt, alles, was die Längenregeln verletzt, oder alles mit Padding am falschen Ort verdient eine Ausnahme. Sehen Sie denselben Dekodierer an einigen verschiedenen Eingaben in Aktion:
| Eingabe | Ergebnis |
|---|---|
"TWFu" |
Dekodiert zu Man (3 Bytes). |
"TWF\nu" (ein Zeilenumbruch in der Mitte) |
Dekodiert zu Man. Weißraum ist für den Dekodierer unsichtbar. |
"TWFu\u00A0" (ein nicht brechendes Leerzeichen am Ende) |
FormatException. Nur die vier Weißraumzeichen oben werden übersprungen; NBSP ist nicht eines davon. |
"TWE" (Länge 3, kein Vielfaches von 4) |
FormatException. Die Payload-Länge, Weißraum ignoriert, muss ein Vielfaches von 4 sein. |
"TWFu=" (zusätzliches Padding nach den Daten) |
FormatException. Höchstens zwei Padding-Zeichen, und nur ganz am Ende. |
"-_88" (URL-sicheres Alphabet) |
FormatException. Der Standard-Dekodierer kennt nur die 64 Zeichen des Standard-Alphabets. |
null |
ArgumentNullException: Der Wert darf nicht null sein. (Parameter 's') |
Noch eine Eigenart, die sich zu merken lohnt: Jede Format-Verfehlung bekommt dieselbe einzelne Fehlermeldung, Die Eingabe ist kein gültiger Base-64-String, da er ein Nicht-Base-64-Zeichen enthält, mehr als zwei Padding-Zeichen oder ein ungültiges Zeichen unter den Padding-Zeichen. Die Meldung listet alle drei möglichen Ursachen auf und sagt nicht, welche Sie getroffen haben, und sie sagt auch nicht, wo. Wenn Sie ein fehlgeschlagenes Payload debuggen, zählen Sie die Zeichen, prüfen Sie das Alphabet und prüfen Sie dann das Padding, in genau dieser Reihenfolge.
Dekodieren ohne Ausnahmen: Die Try-APIs
Ausnahme-gesteuerter Kontrollfluss ist ein legitimes Muster, aber für Hochvolumen- oder unzuverlässige Eingaben ist die Try-Familie der bessere Staatsbürger. Sie wurde in .NET Core 2.1 hinzugefügt und kommt in zwei Varianten: eine, die aus einem String liest, und eine, die aus einem Zeichens-Span liest. Beide schreiben in einen Puffer, den Sie bereitstellen, und melden, wie viel davon sie gefüllt haben:
using System;
using System.Text;
string payload = "TWFu"; // jeder Payload, gültig oder nicht
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.");
}
Zwei Verhaltensweisen lassen die Try-Varianten wie eine andere Spezies wirken. Ungültige Eingabe gibt false zurück, anstatt zu werfen, also kostet ein Strom aus fehlerhaften Payloads eine Verzweigung statt einer Ausnahme. Eine Einschränkung: Eine null-Eingabe ist nicht Teil des Vertrags - sie wirft ArgumentNullException - der Try-Wächter deckt also kaputte Payloads ab, und ein möglicherweise fehlender Wert braucht trotzdem zuerst seinen eigenen Null-Check. Die Geschwister-Methode Convert.TryFromBase64Chars erledigt dieselbe Aufgabe von einem ReadOnlySpan<char> aus, was praktisch ist, wenn das Payload in einem größeren Zeichens-Puffer lebt und Sie erst nicht einen Teilstring abschneiden möchten. Dimensionieren Sie den Ausgabe-Puffer großzügig: Die dekodierte Länge ist höchstens drei Viertel der (ohne Weißraum) Eingabe-Länge, und der written-Out-Parameter sagt Ihnen genau, wie viel herausgekommen ist.
Span-basiertes Dekodieren mit System.Buffers.Text.Base64
Wenn Sie Allokationen zählen, oder wenn Sie möchten, dass der Dekodierer seine Fehlschläge beschreibt, anstatt sie zu werfen, dann ist die System.Buffers.Text.Base64-Klasse das Werkzeug. Sie ist seit .NET Core 2.1 eine statische Klasse in der Standardbibliothek, und sie arbeitet auf Spans statt auf verwalteten Arrays. Ihre Dekodier-Methode gibt einen OperationStatus-Wert mit vier Launen zurück: Done (Erfolg), DestinationTooSmall (Ihr Puffer war zu klein), NeedMoreData (die Eingabe ist noch kein Vielfaches von 4, weiter lesen) und InvalidData (das ist kein Base64). Der letzte boolesche Parameter, isFinalBlock, ist das, was jene zwei unterscheidet: Er sagt dem Dekodierer, ob noch mehr Eingabe kommt. Hier ist die One-Shot-Form, dimensioniert mit dem eigenen Helper der 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
}
Zwei weitere Mitglieder dieser Klasse verdienen einen Absatz. Das erste ist IsValid, das ein Payload validiert, ohne es zu dekodieren. Es kommt in Byte-Span- und Zeichens-Span-Varianten, und ein Overload meldet die dekodierte Länge neben dem Urteil, damit Sie einen Puffer aus einer einzigen Prüfung heraus dimensionieren können:
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.");
}
Das zweite ist DecodeFromUtf8InPlace, für die Situation, in der der Base64-Text bereits in einem Puffer sitzt, den Sie besitzen, und Sie es nicht stört, ihn zu überschreiben. Das Dekodieren verkleinert die Daten, also wird das Ergebnis an den Anfang desselben Puffers geschrieben, und die Methode meldet, wie lang es ist:
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, jetzt in den ersten drei Bytes desselben Puffers lebend
}
Ein Verhalten, das man in der Tasche behalten sollte: Diese Klasse überspringt ebenfalls die vier gewöhnlichen Weißraumzeichen (Leerzeichen, Tab, Zeilenvorschub, Wagenrücklauf), also dekodiert ein zeilig umgebrochenes Payload genauso gut. Sie ist streng, wo es darauf ankommt: Ein Payload, dessen ohne-Weißraum-Länge kein Vielfaches von vier ist, ist InvalidData, wenn es der letzte Block ist, und Zeichen außerhalb des Standard-Alphabets werden ohne Weiteres abgelehnt. Irgendwo in dieser Klasse gibt es keine stille Aufräumaktion.
URL-sicheres Base64: Die Base64Url-Klasse
Für dieselben 64 Werte gibt es ein zweites Alphabet, und auf C#-Web-Arbeit treffen Sie ständig darauf. Im Standard-Alphabet sind die Werte 62 und 63 + und /, zwei Zeichen, die in URLs Ärger machen: Ein + in einem Query-String wird routinemäßig als Leerzeichen dekodiert, und / und = brauchen jeweils Percent-Encoding. RFC 4648, Abschnitt 5, behebt das, indem er - und _ einschiebt, die in keinem URL-Kontext eine besondere Bedeutung tragen, und macht das abschließende =-Padding optional. Das Ergebnis heißt base64url, und es ist das Alphabet von JWTs, API-Tokens, Datei-Upload-IDs und unzähligen URLs (YouTubes 11-Zeichen-Video-Identifikatoren sind base64url ohne Padding).
Seit .NET 9 liefert die Standardbibliothek eine eigene Klasse dafür: System.Buffers.Text.Base64Url. Sie ist der URL-sichere Zwilling der Base64-Klasse, mit eigenen Dekodier-, Validier- und Längen-Helpers:
using System.Buffers.Text;
using System.Text;
string token = "-__8";
byte[] bytes = Base64Url.DecodeFromChars(token);
Console.WriteLine(BitConverter.ToString(bytes));
// FB-FF-FC
Beachten Sie, was die klassische API mit diesem Beispiel nicht gemacht hätte. Dieselben drei Bytes kodieren im Standard-Alphabet zu +//8, und Convert.FromBase64String("+//8") funktioniert, aber Convert.FromBase64String("-__8") wirft, weil die URL-sicheren Zeichen außerhalb ihres Alphabets liegen. Und base64url-Payloads kommen häufig ohne Padding an, was der klassische Dekodierer ebenfalls ablehnt, weil er auf der vollen Gruppe von vier besteht. Die Base64Url-Klasse behandelt beide Varianten des Problems nativ: Sie dekodiert TWE (drei Zeichen, kein Padding) zu den zwei Bytes Ma, und TWE= dekodiert sie genauso gut.
Wenn Ihr Projekt auf einer älteren Laufzeitumgebung läuft, gibt es zwei praktische Wege. Auf .NET Framework 4.6.2 und höher fügen Sie das Microsoft.Bcl.Memory-NuGet-Paket hinzu, das Microsoft speziell veröffentlicht hat, um Base64Url zurück zu portieren (zusammen mit einigen anderen modernen Typen):
dotnet add package Microsoft.Bcl.Memory
Oder, ganz ohne Paket, normalisieren Sie das Payload, bevor Sie es dem klassischen Dekodierer übergeben: Tauschen Sie die URL-sicheren Zeichen zurück zu ihren Standard-Zwillingen, und füllen Sie das fehlende Padding auf. Dieser kleine Helper ist der häufigste handgerollte base64url-Dekodierer in C#-Code, und es lohnt sich, ihn zu kennen, weil er auf jeder Laufzeitumgebung seit .NET Framework 1.1 funktioniert:
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
Die Formel (4 - length % 4) % 4 ist die gesamte Padding-Arithmetik: Sie fügt null, ein oder zwei =-Zeichen hinzu, damit die Länge auf ein Vielfaches von vier landet, und das äußere Modulo verhindert, dass bereits gepaddete Eingabe zusätzliche bekommt.
Von Bytes zu Wörtern: Text, Unicode und Zeichensätze
Das Dekodieren liefert Ihnen Bytes, und Bytes sind eine vollkommen neutrale Sache. Sie werden erst dann zu "Text", wenn Sie sich einen Zeichensatz aussuchen, als den Sie sie lesen, und diese Wahl steht Ihnen frei, denn Base64 trägt keine Information darüber, welchen Zeichensatz der ursprüngliche Autor verwendet hat. In der Praxis bedeutet das: Gehen Sie von UTF-8 aus, außer Sie haben einen Grund, es nicht zu tun, und schreiben Sie es im Code explizit, denn ein expliziter Encoding.UTF8-Aufruf ist der Unterschied zwischen einem Programm, das zufällig korrekt ist, und einem, das by Design korrekt ist:
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 übersteht den Hin- und Rückweg perfekt
Die subtile Falle ist, was passiert, wenn die Bytes kein gültiges UTF-8 sind, weil das Payload wirklich Latin-1 war, oder binär, oder einfach nur beschädigt. Standardmäßig ersetzt der UTF-8-Dekodierer von .NET jede fehlerhafte Sequenz durch das Unicode-Ersatzzeichen (U+FFFD) und macht weiter. Keine Ausnahme, keine Warnung: Die Daten sind einfach weg, in Ihrer Datenbank in Fragezeichen verwandelt. Wenn Sie wissen müssen, wann das passiert, konstruieren Sie die Encoding mit einem strengen Fallback, der die stille Ersetzung in ein lautes DecoderFallbackException verwandelt:
using System.Text;
byte[] bytes = Convert.FromBase64String("//4="); // die Bytes FF FE, kein gültiges UTF-8
Encoding strictUtf8 = Encoding.GetEncoding(
"utf-8",
new EncoderExceptionFallback(),
new DecoderExceptionFallback());
string text = strictUtf8.GetString(bytes);
// Wirft DecoderFallbackException, weil FF FE keine UTF-8-Sequenz ist
Für Payloads, bei denen Sie lieber überleben als scheitern wollen, sind die Ersetzungs-Fallbacks die mildere Option, und Sie dürfen den Ersetzungstext selbst aussuchen:
using System.Text;
byte[] bytes = Convert.FromBase64String("//4="); // die Bytes FF FE, kein gültiges UTF-8
Encoding forgivingUtf8 = Encoding.GetEncoding(
"utf-8",
EncoderFallback.ReplacementFallback,
new DecoderReplacementFallback("[bad]"));
string text = forgivingUtf8.GetString(bytes);
Console.WriteLine(text);
// [bad][bad] statt der stillen U+FFFD-Ersetzung
Noch eine C#-spezifische Lektion aus der Geschichte: Encoding.Default bedeutet auf verschiedenen Laufzeitumgebungen verschiedene Dinge. Auf .NET Framework unter Windows ist es die ANSI-Codepage des Systems (oft Windows-1252), während es auf .NET (Core) UTF-8 ohne BOM ist. Code, der ein Payload durch Encoding.Default schickt, kann deshalb auf einem 2010er-Rechner und einem 2025er-Rechner andere Bytes produzieren, und Base64 kodiert fröhlich, welche Menge Sie ihm auch hinstellen. Wenn Sie je einen dekodierten String voller akzentuierter Mojibake sehen, ist Encoding.Default der erste Ort, an den Sie schauen.
Dateien und binäre Payloads
Dateien sind das unkomplizierteste Dekodier-Ziel, denn es gibt überhaupt keine Zeichensatz-Frage: Die Bytes, die Sie dekodieren, sind die Datei, Byte für Byte, Nullen inklusive. Das Muster ist zwei Aufrufe und eine Datei, und es taucht in allem auf, von Bild-Uploads bis zu 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.");
Zwei praktische Hinweise. Wenn die Datei Weißraum oder Zeilenumbrüche enthalten kann (was sie als Textdatei mit ziemlicher Sicherheit tut), erledigt der klassische Dekodierer das gratis, wie Sie weiter oben gesehen haben. Und wenn das Payload groß ist, gehen Sie gar nicht erst durch einen String: Überspringen Sie den Schritt von der Datei in den String und dekodieren Sie direkt aus dem Stream, was der nächste Abschnitt ist. Für ein dekodiertes Payload, das Text ist und dessen Zeichensatz Sie zufällig kennen, ist das Datei-Beispiel die ganze Lösung, und der Encoding.UTF8.GetString-Schritt aus dem Zeichensatz-Abschnitt passt genau zwischen das Dekodieren und die Verwendung.
Dekodieren aus einem Stream: FromBase64Transform
Die Convert-Methoden sind für Payloads ausgelegt, die in einen String passen, und die offizielle Dokumentation sagt genau das mit diesen Worten: Für Streaming-Daten verwenden Sie die Transform-Klassen. FromBase64Transform ist seit .NET Framework 1.1 (2003) Teil von System.Security.Cryptography, und es steckt in CryptoStream ein, dem Allzweck-Rohr des Frameworks zum Transformieren von Daten, während sie fließen. Die komplette Datei-zu-Datei-Dekodierung ist eine Vier-Zeilen-Einrichtung:
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.");
Der Konstruktor nimmt einen Modus entgegen, und die beiden Modi lohnen es, sie beim Namen zu kennen. IgnoreWhiteSpaces (der Standard, passend zur Weißraum-Politik des klassischen Dekodierers) überspringt die vier gewöhnlichen Weißraumzeichen, während der Stream fließt, was Sie für E-Mail-eingewickelte oder zeilenumbruch-gespickte Payloads wollen. DoNotIgnoreWhiteSpaces ist streng: Das erste Nicht-Alphabet-Zeichen, dem es begegnet, wirft eine FormatException, was Sie wollen, wenn ein versehentliches Leerzeichen im Payload ein Bug sein sollte und kein Achselzucken. Unter der Haube verarbeitet die Transformation die Eingabe in Gruppen von vier Zeichen und gibt die drei Bytes zurück, die jede Gruppe produziert, wobei TransformFinalBlock den Tail übernimmt. Sie rufen diese Methoden selten selbst auf, denn CryptoStream macht es für Sie, aber die Tatsache der Vierer-Gruppe ist wichtig: Wenn Sie die Transformation je manuell füttern, füttern Sie sie in Vielfachen von vier, oder die letzte teilweise Gruppe wird im Final-Block liegen bleiben.
JWTs: Drei Segmente, ein Punkt
Ein JSON Web Token ist das base64url-Payload mit dem größten Verkehrsaufkommen in der C#-Web-Entwicklung, und seine Form ist täuschend einfach: drei durch Punkte getrennte Segmente. Das erste ist der kodierte Header, das zweite das kodierte Payload (a.k.a. Claims), und das dritte die Signatur. Jedes der ersten beiden ist base64url eines UTF-8-JSON-Dokuments, ohne Padding, gemäß der JWS-Spezifikation. Aufteilen und Dekodieren sind zwei Zeilen 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
Auf Laufzeitumgebungen vor .NET 9 geht dieselbe Aufgabe durch den Normalisierungs-Helper aus dem URL-sicheren Abschnitt: Tauschen Sie - und _ zurück zu + und /, padden Sie das Segment auf ein Vielfaches von vier, und dekodieren Sie mit Convert.FromBase64String. Beide Ansätze geben Ihnen dasselbe JSON; wählen Sie den, der zu Ihrem Ziel-Framework passt.
Eine Grenze, die scharf bleiben muss: Ein JWT zu dekodieren ist nicht dasselbe wie ein JWT zu verifizieren. Das obige Dekodieren liest fröhlich die Claims eines Tokens mit Müll-Signatur, denn die Signatur ist eine eigenständige kryptographische Prüfung über die ersten beiden Segmente. Für Token-Arbeit im Produktivbetrieb: Per Hand gar nicht erst parsen: Das System.IdentityModel.Tokens.Jwt-Paket (aus der Microsoft.IdentityModel-Familie) übernimmt Parsen, Validierung und Ablauf in einem, und seine base64url-Behandlung ist genau das Alphabet, das dieser Abschnitt beschreibt. Dekodieren Sie per Hand für Debugging und kleine Werkzeuge; verifizieren Sie mit der Bibliothek für alles, was ein Nutzer erreichen kann.
Data-URIs und eingebettete Bilder
Es gibt eine ganze Klasse von C#-Code, dessen Job darin besteht, einen data:-URI zu empfangen, denn HTML, CSS und sehr viele Web-APIs verwenden sie, um binären Inhalt inline einzubetten. Das Schema, standardisiert durch RFC 2397, lautet data:[mediatype][;base64],payload: Alles vor dem ersten Komma ist Metadaten (der MIME-Typ und die ;base64-Flagge), alles danach ist das Payload. Wenn die ;base64-Flagge vorhanden ist, ist das Payload ein Base64-String, und das Aufteilen am Komma ist der gesamte 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: die PNG-Signatur-Bytes 89 50 4E 47 0D 0A 1A 0A
Das Präfix iVBORw0KGgo= im Beispiel ist die Base64-Form der acht-byte-PNG-Magic-Number, und es ist ein nützlicher Fingerabdruck: Jeder Data-URI für eine echte PNG beginnt so, also ist es ein schneller Plausibilitätscheck, wenn Sie unzuverlässiges HTML parsen. Zwei praktische Hinweise für C#-Entwickler. Erstens versteht die Uri-Klasse Data-URIs nativ in .NET: new Uri("data:text/plain;base64,TWFu") wird problemlos geparst und meldet Scheme == "data", also werden Data-URIs in der Pipeline auftauchen, wenn Ihr Code auf URIs routet, und Sie sollten entscheiden, wie Sie sie behandeln. Zweitens: Erinnern Sie sich daran, was ein Data-URI wirklich ist: eine vollständige Kopie der Datei, um ein Drittel aufgebläht, die in Ihrem Dokument sitzt. Für eine 4-kB-Favicon ist das in Ordnung und für ein 4-MB-Logo schmerzhaft, also dimensionieren Sie das Bild, bevor Sie es kodieren, wenn Sie diejenige sind, die sie erzeugt (der Kodierungs-Artikel behandelt diese Seite).
HTTP: Basic-Authentifizierung und API-Austausch
Base64 ist an mindestens einer Stelle in HTTP verwebt, die Sie bei jeder API-Arbeit berühren werden: das Basic-Authentifizierungsschema. Der Client schickt Authorization: Basic gefolgt von der Base64-Kodierung von username:password, verbunden mit einem Doppelpunkt. Auf der Server-Seite ist das Dekodieren eines einkommenden Headers also: das Basic -Präfix streichen, dekodieren und am ersten Doppelpunkt aufteilen:
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
Der UTF-8-Schritt ist wichtiger, als er aussieht: RFC 7617 festigt den Zeichensatz tatsächlich nicht, lässt den Standard undefined, um die Abwärtskompatibilität zu wahren, und erlaubt nur einen empfehlenden UTF-8-Hinweis, aber genau diesen Hinweis erwartet jeder moderne Server, also produziert ein Benutzername mit einem akzentuierten Zeichen eine andere (und korrekte) Byte-Sequenz als derselbe Benutzername, der als Latin-1 gelesen wird. Die Dekodier-Seite der Basic-Auth ist das einfache Ende dieses Musters; in ASP.NET Core treffen Sie sie gewöhnlich über die Authentifizierungs-Handler und nicht über rohe Headers, aber dieselbe Dekodier-Logik ist es, die sie unter der Haube ausführt, und es ist genau die Art von Code, die Sie brauchen, wenn Sie Integrationstests schreiben, die einen API-Server fälschen. Die Spiegelfunktion, den Header auf der Client-Seite zu bauen, ist auf der Kodierungs-Seite ein Einzeiler, und er bekommt ein vollständiges Beispiel im Kodierungs-Artikel.
E-Mail: MIME und zeilig umgebrochene Payloads
E-Mail ist der Ort, an dem Base64 seinen Ruf verdient hat, und es ist immer noch die Quelle für viele der Payloads, die C#-Dienste empfangen. SMTP war ursprünglich ein 7-Bit-Protokoll, also können binäre Anhänge nicht roh reisen: Die MIME-Spezifikation (RFC 2045) kodiert sie als Base64 mit einem Content-Transfer-Encoding: base64-Header, bricht die Ausgabe bei 76 Zeichen um und trennt die Zeilen mit Wagenrücklauf-Zeilenvorschub-Paaren. Der Körper eines echten Anhangs sieht also aus wie eine Spalte von 76-Zeichen-Zeilen, und die gute Nachricht für C# ist, dass der klassische Dekodierer das bereits zu lesen weiß: Da er Weißraum an jeder Stelle im String überspringt, können Sie ihm den gesamten eingewickelten Körper geben, Zeilenumbrüche inklusive, und er dekodiert ihn, als wären die Zeilenumbrüche nie da gewesen:
using System;
using System.Text;
string attachmentBody = "TWFu\r\nTWFu\r\nTWFu";
byte[] bytes = Convert.FromBase64String(attachmentBody);
Console.WriteLine(Encoding.ASCII.GetString(bytes));
// ManManMan
Für Payloads, die durch einen Stream ankommen und nicht durch einen String, ist FromBase64Transform mit seinem Weißraum-ignorierenden Modus dieselbe Geschichte in Streaming-Kleidung. Und wenn Sie mehr tun müssen, als den Körper zu dekodieren, wenn Sie die MIME-Struktur durchlaufen, Header parsen, verschachtelte Multipart-Abschnitte behandeln oder jeden Anhang aus einer echten .eml-Datei extrahieren müssen, ist die Ökosystem-Antwort in C# das MimeKit-Paket: Es ist die Standard-MIME-Bibliothek für .NET, es behandelt die Base64- und Quoted-Printable-Content-Transfer-Kodierungen intern, und es ist das Werkzeug, nach dem Sie greifen, in dem Moment, in dem "einfach nur den Körper dekodieren" aufhört, Ihr Problem zu beschreiben. Die eigene MailMessage-Klasse des Frameworks dekodiert einfache Anhänge für Sie, aber ihre MIME-Unterstützung ist nach modernen Maßstäben bewusst bescheiden.
PEM-Zertifikate
PEM ist das gerüstete Format der TLS-Welt: Ein Base64-Körper zwischen -----BEGIN CERTIFICATE----- und -----END CERTIFICATE------Marken, umgebrochen bei 64 Zeichen, wie von RFC 7468 spezifiziert. C#-Entwickler treffen es als die Zertifikatsdateien hinter jedem HTTPS-Endpunkt, und die Dekodier-Geschichte hier ist besser, als Sie vielleicht erwarten, denn seit .NET 6 parst das Framework PEM für Sie, Base64-Körper inklusive:
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
Überall darin kein manuelles Base64: CreateFromPem findet die Marken, wickelt den Körper aus, dekodiert ihn und gibt Ihnen ein lebendiges Zertifikat zurück. (Die Familie hat Geschwister für private Schlüssel und für die kombinierte Zertifikat-plus-Schlüssel-Form, falls Ihre Infrastruktur Sie mit genau denen konfrontiert.) Wenn Sie auf einer älteren Laufzeitumgebung sind, oder Sie die rohen DER-Bytes brauchen, die im Inneren der Rüstung sitzen, ist die manuelle Version ein zweistufiges Streichen-und-Dekodieren, und sie lohnt sich zu kennen, weil dasselbe Muster für alles PEM-gerüstete funktioniert:
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);
// Die Länge des DER-Zertifikats im Inneren der Rüstung
Die Fallstricke in dieser Ecke sind allesamt Weißraum: PEM-Dateien tragen CRLF-Zeilenenden von den meisten Zertifikat-Tools, also streichen Sie vor dem Dekodieren sowohl \r als auch \n, nicht nur die Zeilenvorschübe. Und verwechseln Sie den Zertifikatskörper nicht mit dem Körper eines privaten Schlüssels, der andere Marken und andere Inhalte hat; ein Dekodierer rettet Sie in dem einen Fall nicht.
Konfiguration, Umgebungsvariablen und Datenbanken
Das dritte Zuhause von Base64 in C#-Anwendungen ist der Speicher: Konfigurationsdateien, Umgebungsvariablen und Datenbank-Spalten. Das Muster ist überall dasselbe. Ein binärer oder geheimer Wert wird beim Hereinkommen in einen String kodiert und beim Rausgehen wieder zu Bytes dekodiert. Umgebungsvariablen sind das sichtbarste Beispiel, denn sie können nur Text halten:
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 einer Datenbank taucht dieselbe Idee gewöhnlich als byte[]-Eigenschaft auf, die Sie aus Portabilitätsgründen in einer Textspalte speichern wollen, und Entity Framework Core hat genau dafür einen eingebauten Mechanismus: einen Value-Converter, der Ihre Kodier- und Dekodier-Funktionen transparent bei jedem Lesen und Schreiben ausführt:
using Microsoft.EntityFrameworkCore;
modelBuilder.Entity<Avatar>()
.Property(a => a.ImageData)
.HasConversion(
v => Convert.ToBase64String(v),
v => Convert.FromBase64String(v));
Dieser eine Converter ist die gesamte Datenbank-Integration: ImageData bleibt in Ihrem C#-Code ein byte[], und die Datenbank sieht einen Base64-String. Zwei Vorsichtshinweise gehören zu diesem Abschnitt. Erstens hält eine Spalte gegebener Breite als kodierter Text etwa ein Drittel weniger Daten als als rohes Binär, wegen der 4-Zeichen-für-3-Bytes-Steuer, also dimensionieren Sie die Spalte auf die kodierte Länge, wenn sie eine feste Breite hat. Zweitens, und das ist der Sicherheits-Hinweis: Base64 in einer Konfigurationsdatei ist eine Bequemlichkeit, einen Wert in einer einzigen Zeile zu halten, kein Schutz für den Wert. Jeder, der die Konfigurationsdatei lesen kann, kann den Schlüssel mit einem einzigen Befehl dekodieren, und genau deshalb gehören echte Geheimnisse in einen Secret-Store, und das Base64 dort ist nur das Transport-Format.
Wenn das Payload groß ist
Das Base64-Dekodieren hat eine angenehme Eigenschaft, die das Kodieren nicht hat: Die Ausgabe ist immer kleiner als die Eingabe, ungefähr drei Viertel davon. Ein 10-Megabyte-Text-Payload dekodiert zu etwa 7,5 Megabyte Bytes, also kann ein Dekodieren Ihren Speicher nie aufblähen, so wie es ein Kodieren kann. Die Arithmetik, falls Sie einen Puffer im Voraus dimensionieren müssen, läuft auf einen von zwei Aufrufen hinaus: Base64.GetMaxDecodedFromUtf8Length für die strenge Span-Klasse, oder die einfache Division, length / 4 * 3 für die klassische API, plus ein bisschen Spielraum für Weißraum, wenn die Eingabe umgebrochen ist. (Der Helper gibt die maximal mögliche dekodierte Länge zurück: Die echte Länge ist genau dann gleich, wenn die letzte Gruppe kein Padding hat, und ein oder zwei Bytes kleiner, wenn sie mit einem oder zwei Padding-Zeichen endet.)
Wenn das Payload aber wirklich groß ist, dann ist der richtige Zug nicht ein größerer Puffer - es ist gar kein Puffer: Gehen Sie gar nicht erst durch den String und lassen Sie FromBase64Transform das Dekodieren per Stream von Quelle nach Ziel laufen, wie im Stream-Abschnitt gezeigt. Die einzige Regel, die zu respektieren ist, ist die Vierer-Gruppen-Ausrichtung: Ein Base64-Stream kann nur an Vielfachen von vier Zeichen geschnitten werden (nachdem Weißraum berücksichtigt ist), also lesen Sie, wenn Sie die Transformation je von Hand füttern, in Chunks, die Vielfache von vier sind, und lassen Sie TransformFinalBlock den Rest abfließen. Bei allem unter Hunderten von Megabytes ist das One-Shot-Dekodieren schnell genug, dass dies eine Optimierung und keine Notwendigkeit ist, aber die Streaming-Form ist auch die, die sich unter Speicherlimiten gut benimmt, und genau das sind Umgebungen, in denen große Payloads gerne leben.
Ein Dekodierer in Ihrem Terminal
In jeder Sprache gibt es einen befriedigenden Moment, in dem ein 15-zeiliges Konsolenprogramm zu einem Befehlszeilen-Tool wird, und C#-Base64-Dekodierer ist gut dafür geeignet, denn das Lesen von der Standard-Eingabe macht es zum direkten Einsatz in Shell-Pipes. Hier ist das komplette Tool: Es liest das Base64-Payload von der Pipe (oder von einem Argument), dekodiert es und schreibt die rohen Bytes in eine Datei:
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.");
Bauen Sie es einmal, und es sitzt neben dem eigenen base64-Utility der Shell für die Tage, an denen Sie gezielt den Dekodierer der .NET-Laufzeitumgebung wollen: Schicken Sie eine Datei durch, verketten Sie es mit anderen Tools, und die strengen C#-Validierungsregeln (Weißraum-tolerant, Alphabet-streng, Padding-streng) werden Teil Ihrer Pipeline. Das Trim() leistet dort leise Arbeit und fängt den abschließenden Zeilenumbruch ab, den Texteditoren gerne hinzufügen, auch wenn der Dekodierer ihn ohnehin ignoriert hätte. Für die URL-sicheren Payloads, die immer häufiger in API-Logs auftauchen, ist dasselbe Gerüst mit dem Base64Url-Dekodieren aus dem URL-sicheren Abschnitt der gesamte Wechsel.
Speed: Was Sie erwarten können
Base64 in modernem .NET ist schnell, und es wird immer schneller. Die Laufzeit-Implementierungen sowohl der Convert-Methoden als auch der System.Buffers.Text-Klassen sind dort mit SIMD-Vektoranweisungen optimiert, wo die Hardware sie unterstützt, und sie verarbeiten viele Zeichen pro Takt. In der Praxis bedeutet das, dass mehrere-Megabyte-Payloads auf einem gewöhnlichen Desktop-Rechner in einstelligen bis niedrigen zweistelligen Millisekunden dekodiert werden, was schnell genug ist, dass das Base64-Dekodieren in jeder Anwendung, die Sie schreiben werden, effektiv kostenlos ist. Der praktische Performance-Rat dreht sich deshalb um die Form Ihres Codes, nicht um den Dekodierer selbst. Bevorzugen Sie auf heißen Pfaden die Try-Methoden oder die statusrückgebenden Span-Methoden, wo fehlerhafte Eingabe möglich und Ausnahmen teuer wären. Wiederverwenden Sie Puffer mit den In-Place- und Span-APIs, wenn Sie in einer Schleife Tausende kleiner Payloads dekodieren, statt bei jedem Aufruf ein frisches Array zu allozieren. Und dekodieren Sie dasselbe Payload nie zweimal: Einmal ist der Preis, und ein zweites Dekodieren eines Felds, das Sie bereits dekodiert haben, ist reine Verschwendung, die in Profilen als zweiter mysteriöser Base64-Spike auftaucht.
Sicherheit: Was Base64 nicht tut
Die wichtigste Sicherheits-Tatsache über Base64 ist genau die, die Anfänger am häufigsten übersehen: Es ist Kodierung, keine Verschlüsselung. Ein Base64-String kann von jedermann gelesen werden, mit jedem Werkzeug, in einem Bruchteil einer Sekunde, und C# macht das Lesen daraus einen Einzeiler, wie dieser gesamte Artikel gezeigt hat. Base64 hat keinen Schlüssel, keinen Algorithmus-Parameter und keine Schwäche, die man ausnutzen könnte, denn es hat nie versucht, etwas zu verbergen: Es ist ein Transport-Format, ein Weg, Binäres in nur-Text-Kanälen überleben zu lassen. Behandeln Sie es entsprechend. Legen Sie nie ein Passwort, ein Token oder ein Geheimnis in eine Konfigurationsdatei, die durch Base64 "geschützt" ist, denn der Schutz ist genau ein Convert.FromBase64String-Aufruf tief. Wenn der Wert geheim sein muss, braucht er echten Schutz (ein Secret-Manager, ein verschlüsselter Store, mindestens eine Betriebssystem-Zugriffskontrolle), und das Base64 ist nur die Form, die es auf der Reise trägt.
Der zweite Sicherheitshinweis betrifft Ihren eigenen Dekodier-Pfad. Jedes Payload, das Sie dekodieren, ist unzuverlässige Eingabe, bis das Gegenteil bewiesen ist, und die beiden Fehlerszenarien, für die Sie designen sollten, sind das laute (ungültige Eingabe, auf die die klassische API mit einer FormatException antwortet, die Sie fangen und in eine 400 verwandeln sollten, nicht in eine 500) und das leise (gültiges Base64, das zu Bytes dekodiert, die nicht das sind, was Sie erwartet haben: kein UTF-8, nicht der Dateityp, um den Sie gebeten haben, oder länger als Ihr Budget). Validieren Sie, bevor Sie vertrauen: Prüfen Sie die Länge mit IsValid oder der Try-Familie, bevor Sie allozieren, prüfen Sie die dekodierten Bytes gegen eine erwartete Signatur (die PNG-Magic, den PKCS-Header), bevor Sie sie einem Bild- oder Zertifikats-Parser geben, und dimensionieren Sie Ihre Puffer aus der kodierten Länge, bevor Sie dekodieren, nicht danach. Base64 dekodiert alles, was wohlgeformt ist; zu entscheiden, was wohlgeformt bedeutet für Ihre Anwendung, ist Ihre Aufgabe.
Fallstricke, die Sie kennen sollten, bevor sie beißen
Das sind die C#-spezifischen Fallen, die immer wieder im echten Code auftauchen, und jede von ihnen hat eine konkrete Ursache darin, wie das Framework arbeitet:
- Binär durch einen String. Ein C#-
stringist eine Folge von UTF-16-Codeeinheiten, und dekodiertes Base64 ist es nicht. In dem Moment, in dem Sie dekoderte Bytes in eine String-Variable stopfen (einConsole.WriteLineeiner dekodierten PNG, eine String-Konkatenation mit Binär, eine JSON-Bibliothek, die "Text" serialisiert), wird etwas weiter unten es verunstalten. Halten Sie dekodiertes Binär inbyte[], bis es an einen Ort gelangt, der wirklich Bytes will. - Die Encoding.Default-Aufspaltung. Code, der dekoderte Bytes mit
Encoding.Defaultliest, produziert auf .NET Framework (die Windows-ANSI-Codepage) und auf .NET (UTF-8) anderen Text. Dasselbe Payload, zwei verschiedene Ausgaben, keine Ausnahme. Legen Sie Ihren Zeichensatz explizit fest. - JWT-Segmente und der klassische Dekodierer. Ein rohes JWT-Segment in
Convert.FromBase64Stringzu füttern scheitert auf zwei Arten gleichzeitig: Die-/_-Zeichen liegen außerhalb des Standard-Alphabets, und das fehlende Padding verletzt die Längenregel. Erst normalisieren, oderBase64Urlverwenden. - Weißraum, den Sie sehen können, und Weißraum, den Sie nicht sehen können. Der Dekodierer überspringt Leerzeichen, Tab, Zeilenvorschub und Wagenrücklauf, und überspringt nichts anderes. Ein nicht brechendes Leerzeichen, ein Unicode-Zeilen-Trenner oder ein vertikaler Tab in einem Payload (all das überlebt das Kopieren aus einigen Webseiten) ist eine
FormatException, kein Achselzucken. - Eine Fehlermeldung für alle Verbrechen. Die
FormatExceptiondes klassischen Dekodierers sagt nicht, welche Regel gebrochen wurde oder wo. Debuggen Sie, indem Sie zuerst die Länge, dann das Alphabet und dann das Padding prüfen, in genau dieser Reihenfolge, oder wechseln Sie zuTryFromBase64StringundIsValidfür eine boolesche Antwort. - Stille UTF-8-Ersetzung.
Encoding.UTF8.GetStringverwandelt fehlerhafte Byte-Sequenzen ohne Murren in U+FFFD. Wenn das Payload möglicherweise kein gültiges UTF-8 ist, verwenden Sie den strengen Fallback aus dem Zeichensatz-Abschnitt, oder Sie werden Wochen nach dem Vorfall die fehlenden Daten untersuchen. - Stream-Schnitt an der falschen Stelle. Ein Base64-Stream kann nur an Vielfachen von vier Zeichen geschnitten werden. Schnippeln Sie ein Streaming-Dekodieren an jeder anderen Grenze, und die letzte teilweise Gruppe landet in
TransformFinalBlock, wo sie entweder hingehört oder Ihre Ausrichtungsbuchhaltung kaputt macht. - PEM-Zeilenenden. Zertifikatsdateien tragen CRLF. Streichen Sie beim manuellen Auswickeln der Rüstung
\rebenso wie\n, oder die erste Zeile Ihres "dekodierten" DER ist ein Wagenrücklauf in der Kleidung eines Daten-Bytes. - Doppel-Kodierung. Wenn das Payload schon Base64 war, als es Sie erreichte (eine Konfiguration, die einen Base64-String Base64-gekodet hat, eine API, die die Ausgabe eines anderen Encoders kodiert hat), gibt ein Dekodieren Ihnen mehr Base64, nicht Ihre Daten. Der Rundweg schließt sich erst nach so vielen Dekodierungen, wie Kodierungen stattfanden, und die Kodierer-Seite dieses Bugs ist das Thema des Kodierungs-Artikels.
Eine kurze Geschichte von Base64 in C#
Die Base64-Geschichte in C# ist auch eine Geschichte davon, wie die .NET-Plattform erwachsen wurde, und sie ist länger, als die meisten erwarten:
- .NET Framework 1.1, April 2003.
Convert.FromBase64Stringund seine Geschwister kommen an, und sie tragen das Design, das die API bis heute definiert: streng beim Alphabet, großzügig bei den vier Weißraumzeichen, ungeschliffen bei seinen Fehlern. Den größten Teil der folgenden zwei Jahrzehnte ist diese eine Methode "der" Base64-Dekodierer in C#. - .NET 2.0, 2005. Die
Base64FormattingOptions-Enum gesellt sich zuConvertund bringt die MIME-artigen Zeilenumbrüche auf die Kodier-Seite (und die passende Weißraum-Toleranz auf die Dekodier-Seite, wo sie bereits still am Werk ist). - .NET Core 2.1, 2018. Die Span-Ära.
Convertbekommt dieTry-Methoden und eine span-basierte Kodierung, und die neueSystem.Buffers.Text.Base64-Klasse kommt mit ihremOperationStatus-Vertrag, In-Place-Dekodierung undIsValid, gebaut für die null-Allokation-Welt des speicherfokussierten Rewrites. - .NET 5, 2020. Die Hex-Geschwister (
Convert.ToHexStringund Freunde) shippen, dasselbe Designmuster wie Base64, angewendet auf ein 16-Symbole-Alphabet, ein Zeichen dafür, dass das Konversions-Klassen-Muster zu einem Hausstil geworden war. - .NET 6, 2021.
X509Certificate2.CreateFromPemmacht PEM zu einer First-Class-Eingabe, und eine ganze Klasse von manuellem Rüstung-Streifen-Code wird auf modernen Laufzeitumgebungen optional. - .NET 9, November 2024.
System.Buffers.Text.Base64Urllandet endlich im Kasten nach Jahren von Community-Wünschen, und dasMicrosoft.Bcl.Memory-Paket backportet es auf .NET Framework 4.6.2 und höher für die Legacy-Codebasen, die immer noch alles betreiben. - .NET 11, im Preview beim Schreiben dieses Artikels. Das nächste Release, Ende 2026 erwartet, fügt den vorhandenen Typen weitere Base64-Komfort-APIs und Overloads hinzu und setzt den langsamen Marsch hin zu einer ergonomischeren Oberfläche fort.
Es lohnt sich zu bedenken: Die Kodierung selbst ist viel älter als all das. Die erste standardisierte Nutzung dessen, was wir heute MIME-Base64 nennen, war das Privacy-Enhanced-Mail-Protokoll 1987 (RFC 989), MIME standardisierte die bei 76 Zeichen umgebrochene Form 1993, und RFC 4648 gab dem Format 2006 seine moderne, alphabet-bewusste Spezifikation, einschließlich der URL-sicheren Variante. C# hat all das geerbt: Jede Umbruch- und Padding-Eigenart, der Sie in einem 30 Jahre alten E-Mail-Format begegnen, ist eine Eigenart, die der C#-Dekodierer absorbiert.
Kuriose C#-Tatsachen
- Der kleinste Smoke-Test.
"TWFu"dekodiert zuMan. Drei Bytes, kein Padding, keine Ausreden. Es ist das Hello-World vom Base64-Debugging in C#, und es übt den gesamten Happy-Path in vier Zeichen aus. - Ein Dekodierer mit Post-Geschichte. Die Weißraum-Toleranz ist kein Zufall der Implementierung - es ist eine Designentscheidung, die von MIME geerbt wurde: Ein kompletter, bei 76 Zeichen umgebrochener E-Mail-Körper, mit all seinen CRLF-Paaren, ist ein gültiges einzelnes Argument für
Convert.FromBase64String. Der Dekodierer wurde gebaut, um das Format zu fressen, das die E-Mail seit dreißig Jahren verwendet. - Ein Fehler, drei Ursachen. Die klassische
FormatException-Meldung listet alle drei Fehlschlagsmodi auf, über die sie möglicherweise berichtet (schlechtes Zeichen, zu viel Padding, fehlplatziertes Padding), und sagt nicht, welcher ausgelöst wurde. Es ist die einzige Fehlermeldung in der API-Oberfläche, die wie eine Multiple-Choice-Frage funktioniert. - Ein Namespace, der ein bisschen lügt.
System.Buffers.Textklingt so, als ginge es um Textverarbeitung, aber es ist eigentlich das Zuhause der binär-zu-Text-Konversion im Allgemeinen: DieUtf8ParserundUtf8Formatter, die Zahlen und Daten direkt in UTF-8 parsen, wohnen gleich neben den Base64-Klassen. - Padding ist auf einer Seite der Familie optional. Die
Base64Url-Klasse dekodiertAQIDBA(sechs Zeichen, kein Padding) undAQIDBA==(dieselben Bytes mit Padding) zu denselben vier Bytes, während der klassische Dekodierer nur die gepaddete Form annimmt. Zwei Dekodierer, zwei Verträge, eine Laufzeitumgebung. - Strings, die nicht existieren sollten. Ein C#-String kann legal NUL-Bytes enthalten, also kann
Encoding.UTF8.GetStringüber dekodiertes Binär einen "String" voller Steuerzeichen produzieren, den die Konsole, Ihr CSV-Writer und die Hälfte der JSON-Bibliotheken auf dem Planeten jeweils unterschiedlich behandeln. Das Typsystem erlaubt es; das Ökosystem größtenteils nicht. - Ein 1.1-Relikt in bestem Zustand.
Convert.FromBase64CharArrayhat seit April 2003 dieselbe Drei-Parameter-Signatur und überlebte die Generics-Revolution, die Span-Revolution und die URL-sichere Revolution, ohne dass ein einziges Overload hinzugefügt wurde. Die char-Array-Ära von C# ist nicht vorbei; sie ruht nur. - Elf Zeichen, acht Bytes. YouTubes Video-Identifikatoren sind base64url ohne Padding: 11 Zeichen, die zu 8 Bytes dekodieren.
Base64Url.GetMaxDecodedLength(11)sagt Ihnen die 8, und das Dekodieren ist ein Einzeiler, was eine schöne Art ist, den Tag zu beenden, wenn Sie die Art von Person sind, die diese Art von Dingen schreibt.
Die andere Richtung
Das ist die Dekodier-Seite, und dort lebt der größte Teil des Schmerzes, denn beim Dekodieren treffen Sie auf die Daten anderer Leute: deren Padding-Entscheidungen, deren Zeilenumbrüche, deren Alphabete, deren Tokens. Die entgegengesetzte Richtung, die eigenen Bytes zu nehmen und sie in Base64 zu packen, ist ein ruhigeres Problem mit seinem eigenen Satz an Entscheidungen, die zu treffen sind, und seinem eigenen Satz an Fallen. Base64-Kodierung in C#, von der 76-Zeichen-Frage bis zu URL-sicheren Tokens, wird in dem unten verlinkten Begleit-Artikel ausführlich behandelt, und es ist ein kurzes, befriedigendes Lesen, wenn Sie wissen, wonach Sie suchen.
Zuletzt aktualisiert: 2026-09-08
Verwandter Artikel: Base64-Kodierung in C# (CSharp): Ein vollständiger Leitfaden