Haben Sie mit dem Base64-Format zu tun? Dann ist diese Website genau das Richtige für Sie! Nutzen Sie unser superpraktisches Online-Tool, um Ihre Daten zu kodieren oder zu dekodieren.

Base64-Kodierung in C# (CSharp): Ein vollständiger Leitfaden

Sie haben die Bytes. Eine PNG, die in einer JSON-Antwort reisen muss, ein Token, das in eine URL passen muss, eine Zeile Text, die gleich ein System betritt, das nur Buchstaben und Ziffern annimmt. Irgendwo zwischen dem byte[] in Ihrer Hand und dem Kanal, den es überqueren muss, bietet C# ein Menü an Base64-Kodierern an, und das Auswählen unter ihnen ist die eigentliche Kunst dieses Themas. Der klassische Einzeiler ist seit 2003 im Framework, die span-basierten und URL-sicheren Optionen kamen mit den modernen Laufzeitumgebungen, und jeder von ihnen macht andere Versprechen über Größe, Zeilenumbrüche und Alphabet. Dieser Artikel geht das ganze Menü durch, mit funktionierenden Beispielen für jeden echten Job, den ein Kodierer bekommen soll.

Zuerst die Hausregeln, in einem Atemzug, denn die Startseite dieser Site erklärt das Format im Detail: Der Kodierer nimmt je drei Bytes und schreibt vier Zeichen aus einem Alphabet von 64 Symbolen, füllt den Tail mit einem oder zwei =-Zeichen, also verlässt Ihre Daten etwa 33 Prozent fetter, als sie angekommen sind. Diese Zahl, nicht irgendein Code, ist die wichtigste Tatsache in diesem Artikel, und alles unten dreht sich darum, sie sinnvoll zu bezahlen.

Das Kodierer-Menü: Wählen Sie Ihr Werkzeug

Hier ist die komplette Familie von Kodier-APIs in der .NET-Welt, mit der Situation, für die jede einzelne gebaut wurde. Alles ist in der 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.ToBase64String(byte[]) .NET Framework 1.1 (2003) Der Klassiker. Ganzes Array hinein, gepaddeter String heraus. Keine Optionen, keine Überraschungen.
Convert.ToBase64String(byte[], int, int) .NET Framework 1.1 (2003) Ein Ausschnitt eines größeren Arrays kodieren, ohne ihn erst herauszukopieren.
Convert.ToBase64String(byte[], Base64FormattingOptions) .NET 2.0 (2005) Der Klassiker mit einem Regler: optional nach jedem 76. Zeichen einen Zeilenumbruch einfügen, die MIME-Art.
Convert.ToBase64String(ReadOnlySpan<byte>, Base64FormattingOptions) .NET Core 2.1 (2018) Die Span-Version: eine Ansicht in einen Puffer kodieren, kein Array-Kopieren, keine Slice-Allokation.
Convert.ToBase64CharArray(byte[], int, int, char[], int) .NET Framework 1.1 (2003) In einen Zeichens-Puffer schreiben, den Sie besitzen, und zurückbekommen, wie viele Zeichen verwendet wurden.
Convert.TryToBase64Chars(ReadOnlySpan<byte>, Span<char>, out int, ...) .NET Core 2.1 (2018) Boolesch statt Ausnahmen: kodieren, wenn der Puffer passt, sonst false melden.
System.Buffers.Text.Base64.EncodeToUtf8, EncodeToUtf8InPlace .NET Core 2.1 (2018) Die strenge Span-Familie: Statuscodes statt Ausnahmen, und In-Place-Aufblähung für Puffer, die Sie bereits besitzen.
System.Buffers.Text.Base64Url.EncodeToString und Geschwister .NET 9 (2024) Das URL-sichere Alphabet, ausgegeben ohne Padding. Auf .NET Framework 4.6.2+ und .NET Standard 2.0: das Microsoft.Bcl.Memory-NuGet-Paket.
ToBase64Transform + CryptoStream .NET Framework 1.1 (2003) Streaming: eine Datei kodieren, während sie fließt, hält das gesamte Payload nie im Speicher.

Wenn Ihr Projekt eine .NET-Version ab 2018 als Ziel hat, sind die ersten sieben Zeilen und das Streaming-Paar unten 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 das Menü weiter. Kein anderes Paket in diesem Artikel ist erforderlich.

Der Standard-Aufruf: Convert.ToBase64String

Neunzig Prozent des Kodier-Alltags in C# sind ein einziger Aufruf. Geben Sie ihm Bytes, und er gibt Ihnen den String zurück, der sie trägt:

using System;
using System.Text;

string text = "Man";
byte[] bytes = Encoding.UTF8.GetBytes(text);
string packed = Convert.ToBase64String(bytes);
Console.WriteLine(packed);
// TWFu

Beachten Sie die zwei-Schritte-Form, denn das ist die häufigste "warum passt mein Base64 nicht"-Frage in C#. Es gibt kein Overload, das direkt einen string nimmt, und das ist by Design: Ein C#-String ist UTF-16, und das Framework verweigert es zu raten, welche Bytes Sie meinten, wenn Sie "kodiere diesen Text" sagen. Sie wählen zuerst die Byte-Darstellung, mit Encoding.UTF8.GetBytes (oder welchem Zeichensatz die Daten wirklich sind), und erst dann passiert der Base64-Schritt. Der Rest der klassischen Familie ist derselbe Aufruf mit schmalerer Taille: Das (byte[], int, int)-Overload kodiert einen Ausschnitt eines Puffers, ohne den Ausschnitt herauszukopieren, und das Span-Overload tut dasselbe von einem ReadOnlySpan<byte> aus, was das richtige Werkzeug ist, wenn die Daten ein Fenster in einen größeren Read-Puffer sind. Eine weitere Eigenschaft des klassischen Kodierers gilt es offen auszusprechen: Er schlägt nie fehl und fragt nie. Er gibt immer das Standard-Alphabet aus, immer mit Padding, und immer denselben String für dieselbe Eingabe, also ist ein Base64-String ein verlässlicher Fingerabdruck der Bytes, die ihn produzierten.

Die 76-Zeichen-Frage: Zeilenumbrüche und Base64FormattingOptions

Der klassische Kodierer hat einen Regler, und der ist seit .NET 2.0 dabei: Base64FormattingOptions. Stellen Sie ihn auf InsertLineBreaks, und der Kodierer fügt nach jedem 76. Zeichen der Ausgabe einen Zeilenumbruch ein, die Zeilenlänge, die die MIME-Spezifikation für E-Mail-Anhänge verwendet. Stellen Sie ihn auf None, oder verwenden Sie die Overloads ohne die Option, und Sie bekommen einen langen, ununterbrochenen String:

using System;

byte[] bytes = new byte[90];
string plain = Convert.ToBase64String(bytes);
string wrapped = Convert.ToBase64String(bytes,
  Base64FormattingOptions.InsertLineBreaks);

Console.WriteLine(plain.Length);   // 120
Console.WriteLine(wrapped.Length); // 122, ein Zeilenumbruch nach Zeichen 76 eingefügt

In der Praxis zählen zwei Details zu diesem Regler. Erstens ist der eingefügte Zeilenumbruch das Windows-Paar, Wagenrücklauf plus Zeilenvorschub, nicht ein nackter Zeilenvorschub. Die umgebrochene Ausgabe enthält also \r\n-Sequenzen, und jeder Code, der den String später "aufräumt", indem er nur \n entfernt, wird mit versehentlich versteckten Wagenrückläufen in den Daten zurückbleiben. Zweitens passiert der Umbruch bei 76 Zeichen kodierter Ausgabe, und genau deshalb konnte der MIME-Standard garantieren, dass der E-Mail-Transport mit seinen 76- oder 78-Zeichen-Zeilenlimiten eine Gruppe von vier Base64-Zeichen nie über Zeilen aufteilen würde: 76 ist ein Vielfaches von vier, also endet jede Zeile auf einer Gruppen-Grenze. Sie wollen die umgebrochene Form, wenn Sie E-Mail-Körper, PEM-artige Textblöcke oder etwas produzieren, das eine Legacy-Mail-Pipeline tragen wird. Sie wollen die nicht umgebrochene Form überall anders: JSON-Payloads, URL-Tokens, API-Antworten und Dateien, die von einem strengen Parser dekodiert werden, der Überraschungen nicht mag. Und nie wollen Sie die umgebrochene Form in einem JWT, wo die Spezifikation Zeilenumbrüche, Weißraum und sogar Padding ausdrücklich verbietet.

Die Ausgabe besitzen: Char-Puffer und die Try-APIs

Manchmal ist der String nicht das Ziel, der Puffer ist es. Sie hängen an einem fest großen Zeichen-Array, schreiben in ein Protokoll-Frame, oder Sie wollen einfach nicht, dass die Laufzeitumgebung die Ausgabe für Sie allokiert. Für solche Momente hat der Kodierer seit den 1.1-Tagen einen Char-Buffer-Modus und seit der Span-Ära einen Try-Modus. Die Char-Buffer-Methode schreibt in ein Array, das Sie bereitstellen, und sagt Ihnen, wie viele Zeichen sie verwendet hat, also ist die Puffergröße Ihre Aufgabe, und die Standardbibliothek gibt Ihnen sogar die Größenformel:

using System.Buffers.Text;
using System.Text;

byte[] bytes = Encoding.ASCII.GetBytes("Man");
char[] buffer = new char[Base64.GetMaxEncodedToUtf8Length(bytes.Length)];
int written = Convert.ToBase64CharArray(bytes, 0, bytes.Length, buffer, 0);

string packed = new string(buffer, 0, written);
Console.WriteLine(packed);
// TWFu

Der Try-Geschwisterling erledigt dieselbe Aufgabe aus Spans und antwortet mit einem Booleschen Wert. Er kodiert die Eingabe in Ihren Ziel-Span, meldet die Zeichenzahl im Out-Parameter und gibt false zurück, wenn der Zielbereich zu klein war, ohne irgendetwas zu schreiben. Diese letzte Eigenschaft macht ihn sicher für die Verwendung mit nicht vertrauenswürdigen Eingabegrößen: Sie bekommen nie einen halbgefüllten Puffer aus einem fehlgeschlagenen Aufruf:

using System;

byte[] bytes = { 1, 2, 3 };
char[] buffer = new char[4];

if (Convert.TryToBase64Chars(bytes, buffer, out int written,
  Base64FormattingOptions.None))
{
  Console.WriteLine(new string(buffer, 0, written));
  // AQID
}
else
{
  Console.WriteLine("Buffer too small, nothing was written.");
}

Für die strenge Span-Familie in System.Buffers.Text.Base64 existiert dieselbe Form mit dem OperationStatus-Vertrag statt einem Booleschen Wert: EncodeToUtf8 füllt einen Byte-Span, den Sie besitzen, und sagt Ihnen per Status, ob es fertig wurde, keinen Platz mehr hatte oder mehr Eingabe braucht, und EncodeToUtf8InPlace ist das, nach dem Sie greifen, wenn die binären Daten bereits in einem Puffer sitzen, den Sie zu vergrößern bereit sind: Das Kodieren bläht die Daten auf, also schreibt die Methode den Base64-Text über das Ende desselben Puffers und meldet, wie lang das Ergebnis ist. Alle diese teilen eine einzige Regel zum Dimensionieren: Die Ausgabe für n Eingabe-Bytes sind immer 4 * ceil(n / 3) Zeichen, Padding inklusive, und die GetMaxEncodedToUtf8Length- und Base64Url.GetEncodedLength-Helpers implementieren diese Arithmetik - das Letztere für die ungepaddete Länge, die immer die gepaddete Größe oder kürzer ist - also dimensionieren Sie von den Helpers, nie von einer gemerkten Konstante.

Der URL-sichere Kodierer: Base64Url

Das Standard-Alphabet hat zwei Zeichen, die URLs nicht mögen. Ein + in einem Query-String wird routinemäßig von Form-Parsing-Regeln als Leerzeichen dekodiert, und sowohl / als auch = wollen percent-encodiert werden, bevor sie in einem Pfad oder Parameter reisen können. Die URL-sichere Variante von Base64, definiert in Abschnitt 5 von RFC 4648, tauscht + und / gegen - und _, die nirgendwo Escaping brauchen, und macht das abschließende =-Padding optional. Seit .NET 9 hat die Laufzeitumgebung eine eigene Klasse dafür, System.Buffers.Text.Base64Url, und sie hat ein Verhalten, das Menschen beim ersten Mal überrascht: Sie gibt überhaupt kein Padding aus:

using System.Buffers.Text;

byte[] bytes = { 1, 2 };
string classic = Convert.ToBase64String(bytes);
string urlSafe = Base64Url.EncodeToString(bytes);

Console.WriteLine(classic); // AQI=
Console.WriteLine(urlSafe); // AQI

Dieser Unterschied ist der ganze Punkt. Ein JWT-Segment, ein Upload-Identifikator, ein Token in einem Query-String, ein Wert in einem URL-Pfad: Alle wollen die ungepaddete URL-sichere Form, und Base64Url.EncodeToString gibt sie direkt, mit Alphabet und Padding, die beide so behandelt werden, wie diese Formate es spezifizieren. Die Klasse hat die volle Familie, kodieren zu String, zu Char-Span und zu UTF-8-Byte-Span, plus GetEncodedLength für das Puffer-Dimensionieren und IsValid zum Validieren der Eingabe auf dem Weg hinein. Wenn Ihr Projekt auf einer älteren Laufzeitumgebung läuft, fügen Sie das Microsoft.Bcl.Memory-Paket hinzu, das Microsoft veröffentlicht, um die Klasse auf .NET Framework 4.6.2 und höher zurück zu portieren:

dotnet add package Microsoft.Bcl.Memory

Und wenn Sie das Paket nicht verwenden können, ist die handgerollte Version der klassische Kodierer plus zwei Replaces und ein Trim, was Sie in sehr vielen C#-Codebasen treffen werden:

using System;
using System.Text;

byte[] bytes = Encoding.UTF8.GetBytes("Hello World!");
string packed = Convert.ToBase64String(bytes)
  .Replace('+', '-')
  .Replace('/', '_')
  .TrimEnd('=');

Console.WriteLine(packed);
// SGVsbG8gV29ybGQh, URL-sicher und ungepaddet

Die Reihenfolge der Operationen in dieser Kette lohnt einen Blick: Die Zeichentausche passieren auf der Standard-Ausgabe, und das Padding wird zuletzt gestrichen, denn zuerst zu streichen würde nichts verändern, aber den Code schwerer lesbar machen, und nach dem Streichen zu tauschen würde immer noch funktionieren, aber so werden subtile Bugs geboren. Verwenden Sie diese Form für Tokens, Identifikatoren und alles, was in einer URL leben wird, und behalten Sie das Standard-Alphabet für E-Mail-Körper, JSON-Payloads und Dateien, wo +, / und = sich völlig zu Hause fühlen.

Den Kodierer füttern: Strings, Zeichensätze und die Encoding-Wahl

Jeder Kodier-Job, der von Text ausgeht, beginnt mit derselben stillen Entscheidung: Welche Bytes wird dieser Text? Der Base64-Schritt ist deterministisch und unschuldig, aber der Encoding-Schritt davor ist es, wo Ausgaben auseinandergehen, und die Divergenz kann still sein. UTF-8 ist die Standard-Annahme im modernen Web, und es ist die richtige Vorgabe hier: Es round-trippt jede Sprache, es ist das, was jede andere Plattform annehmen wird, wenn sie Ihr Payload dekodiert, und es ist das, was Encoding.UTF8 Ihnen in einem Aufruf gibt:

using System;
using System.Text;

string original = "h\u00e9llo \u4e16\u754c";
byte[] utf8 = Encoding.UTF8.GetBytes(original);
string packed = Convert.ToBase64String(utf8);
Console.WriteLine(packed);
// aMOpbGxvIOS4lueVjA==

Jetzt sehen Sie dasselbe Zeichen, kodiert durch einen anderen Zeichensatz, und verstehen, warum "derselbe Text" ohne angehängten Zeichensatz keine wohldefinierte Sache ist:

using System;
using System.Text;

string euro = "\u20ac";
string asUtf8 = Convert.ToBase64String(Encoding.UTF8.GetBytes(euro));
string asLatin1 = Convert.ToBase64String(
  Encoding.GetEncoding("ISO-8859-1").GetBytes(euro));

Console.WriteLine(asUtf8);   // 4oKs
Console.WriteLine(asLatin1); // Pw==

Zwei verschiedene Base64-Strings für dasselbe Euro-Zeichen, beide vollkommen gültig, und nur einer davon wird auf der anderen Seite wieder zu einem Euro-Zeichen dekodiert. Die Falle mit dem größten Wirkungsradius ist Encoding.Default: Auf .NET Framework unter Windows ist es die ANSI-Codepage des Systems, während es auf .NET (Core) UTF-8 ist, also produziert ein Programm, das mit Encoding.Default kodiert, auf einem 2010er-Rechner anderes Base64 als auf einem 2025er, und beide Ausgaben werden auf ihrer Heimat-Plattform "korrekt" dekodiert. Wenn ein dekodiertes Payload voller akzentuierter Mojibake ankommt, hat die ursprüngliche Kodierung einen anderen Zeichensatz verwendet, als das Dekodieren annahm, und die Korrektur liegt auf dieser Seite der Pipe: Legen Sie die Encoding explizit fest, in beide Richtungen, in Code, der das Team, das ihn schrieb, überleben wird. Und eine letzte Anmerkung zum Typsystem selbst: Ein C#-String ist UTF-16, also kostet jedes ASCII-Zeichen zwei Bytes und Ihre Ausgabe verdoppelt sich in der Größe ohne jeden Nutzen, wenn Sie je rohe UTF-16-Codeeinheiten in den Kodierer geben (indem Sie Encoding.Unicode.GetBytes aufrufen), denn der Dekodierer auf der anderen Seite wird es als UTF-16-Text lesen und nicht als die Bytes Ihres ursprünglichen Strings. Base64 trägt die Bytes, die Sie ihm geben, und es kümmert sich nicht, was sie bedeuten.

Dateien: Von der Festplatte zu einem String

Dateien sind das häufigste Kodier-Payload und das nachsichtigste, denn es gibt keine Zeichensatz-Frage: Die Bytes auf der Festplatte sind die Daten, und der Kodierer kümmert sich nicht, ob sie ein Wort oder eine Wellenform buchstabieren. Der Rundweg ist ein Lesen, ein Kodieren und ein Schreiben, und die einzige echte Entscheidung ist, wo das Ergebnis hingeht:

using System.IO;

byte[] bytes = File.ReadAllBytes("photo.png");
string packed = Convert.ToBase64String(bytes);
File.WriteAllText("photo.b64", packed);

Console.WriteLine(packed.Length + " characters for "
  + bytes.Length + " bytes of image.");

Die Größen-Rechnung ist die ganze Geschichte, und es lohnt sich, sie zu machen, bevor Sie einen Transport wählen. Ein Megabyte Datei wird zu 1.333.336 Base64-Zeichen, und weil ein C#-String zwei Bytes pro Zeichen speichert, belegt das kodierte Ergebnis als String etwa 2,7 Megabyte im Speicher. Eine 10-Megabyte-Datei wird zu einem 13-Megabyte-String, der in 26 Megabyte verwaltetem Speicher sitzt. Keines davon ist ein Problem für ein Foto oder einen Konfigurations-Blob, und es ist ein sehr guter Grund, den Streaming-Kodierer unten zu verwenden, wenn das Payload ein Video ist. Das obige Muster ist das, nach dem Sie für alles greifen, das bequem in den Speicher passt, und es ist auch das Muster, das jede "eine Datei als Base64 in einem JSON-Körper hochladen"-Funktion still verwendet: die Datei lesen, sie kodieren, den String in das JSON legen und die API-Schicht ihre Arbeit tun lassen.

Bilder im Web: Data-URIs bauen

Der sichtbarste Konsument kodierter Bilder ist das Web, und das Format des Webs für "ein Bild, das im Dokument lebt", ist der Data-URI: das data:-Schema, gefolgt vom MIME-Typ, einer ;base64-Flagge, einem Komma und den kodierten Bytes. In C# ist das Bauen eines String-Konkatenation, und der Kodierer macht die ganze echte Arbeit:

using System.IO;

byte[] png = File.ReadAllBytes("logo.png");
string packed = Convert.ToBase64String(png);
string dataUri = "data:image/png;base64," + packed;

Console.WriteLine(dataUri.Substring(0, 30));
// data:image/png;base64,iVBORw0K

Das Präfix iVBORw0KGgo in dieser Ausgabe ist ein nützlicher Kontrollpunkt: Es ist die Base64-Form der acht-byte-PNG-Signatur, also beginnt jede PNG, die Sie kodieren, so, und jeder PNG-Data-URI, der nicht so beginnt, ist keine PNG. Drei praktische Hinweise gehören zu diesem Muster. Erstens ist der Data-URI eine vollständige Kopie des Bildes, um ein Drittel aufgebläht, eingebettet in Ihr HTML oder CSS, also tauscht er eine Netzwerkanfrage gegen permanentes Page-Gewicht, ein gutes Geschäft für eine 4-kB-Favicon und ein schlechtes für ein 4-MB-Hero-Bild, und der Kodierer verhandelt nicht über die 33 Prozent. Zweitens, wenn das Bild groß ist, skalieren oder komprimieren Sie es neu, bevor Sie es kodieren, denn jedes Byte des Originals taucht in der Seite auf. Drittens: Seien Sie vorsichtig mit benutzerbeliefertem SVG in HTML, das Nutzer sehen: Ein SVG kann Script tragen, also ist das Einbetten - inline oder via <object>/<embed> - eine klassische XSS-Fläche. Ein gewöhnlicher <img>-Data-URI wird es in modernen Browsern nicht ausführen, aber dasselbe Markup, in diesen Kontexten wiederverwendet, schon. PNG, JPEG, GIF und WebP in Data-URIs sind inaktiv; SVG ist das eine, das es nicht ist.

Ein JWT per Hand zusammenbauen

Ein JSON Web Token von Grund auf zu bauen, ist eine Initiation, und in C# ist sie eine bessere als in den meisten Sprachen, denn die Teile sind kurz. Ein JWT sind drei base64url-Segmente, verbunden durch Punkte: der kodierte Header, das kodierte Payload und die Signatur. Die ersten beiden sind UTF-8-JSON-Dokumente, und die Signatur wird über die ersten beiden Segmente berechnet, verbunden durch einen Punkt. Hier ist die gesamte Montage, mit einer Ersatz-Signatur, denn der kryptographische Schritt gehört zu Ihrem Signier-Schlüssel und nicht zur Base64-Geschichte:

using System;
using System.Buffers.Text;
using System.Text;

string headerJson = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}";
string payloadJson = "{\"sub\":\"42\",\"name\":\"Ada\"}";

string header = Base64Url.EncodeToString(Encoding.UTF8.GetBytes(headerJson));
string payload = Base64Url.EncodeToString(Encoding.UTF8.GetBytes(payloadJson));
string signature = "c2lnbmF0dXJl"; // Ersatz für den echten HMAC- oder ECDSA-Wert

string jwt = header + "." + payload + "." + signature;
Console.WriteLine(jwt);
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0MiIsIm5hbWUiOiJBZGEifQ.c2lnbmF0dXJl

Zwei Eigenschaften von Base64Url.EncodeToString leisten in diesem Beispiel leise Arbeit. Sie gibt das URL-sichere Alphabet aus, also können weder + noch / im Token auftauchen, und sie lässt das Padding weg, also taucht nie ein = auf, was genau das ist, was die JWS-Spezifikation verlangt, und genau das, was Convert.ToBase64String ohne Hilfe nicht tun würde. Wenn Sie auf einer Laufzeitumgebung vor .NET 9 sind, läuft dieselbe Aufgabe durch den Standard-Kodierer plus die Aufräum-Kette aus dem URL-sicheren Abschnitt: kodieren, die zwei Zeichen tauschen, das Padding streichen. Die Reihenfolge der Segmente ist für die Signatur wichtig, die über header plus ein Punkt plus payload als einfache ASCII-Bytes berechnet wird, also bauen Sie zuerst die zwei Segmente und signieren Sie ihre exakte Verkettung, nicht eine umgeformatete Version des JSON. Und eine Grenze, die scharf bleiben muss: Für alles, was ein Nutzer erreichen kann, bauen Sie JWTs gar nicht erst per Hand. Das System.IdentityModel.Tokens.Jwt-Paket erledigt Bauen, Signieren, Validierung und Ablauf für Sie, und seine base64url-Behandlung ist genau dieses Alphabet und diese Padding-Regel. Montage per Hand ist für Tests, Demos und den Tag, an dem Sie genau verstehen müssen, was die Bibliothek tut.

HTTP-Header: Basic-Auth

Base64 taucht im klaren HTTP im Basic-Authentifizierungsschema auf, und die Kodier-Seite ist einer der kürzesten Header-Bauer im Protokoll: Benutzername und Passwort mit einem Doppelpunkt verbinden, das Ergebnis als UTF-8 kodieren, Base64 machen und mit dem Schemannamen präfixen:

using System;
using System.Text;

string user = "ada";
string password = "s3cret";
string credentials = user + ":" + password;

string header = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(credentials));
Console.WriteLine(header);
// Basic YWRhOnMzY3JldA==

Der Zeichensatz ist der fummelige Teil: RFC 7617 lässt den Standard-Zeichensatz des Basic-Schemas aus Abwärtskompatibilität undefined und bietet nur einen empfehlenden UTF-8-Hinweis an, aber genau das erwartet jeder moderne Server, also sollte ein akzentuierter Benutzername durch Encoding.UTF8, nicht durch was auch immer der Plattform-Standard ist, gehen, oder der Server dekodiert eine andere Byte-Sequenz und lehnt den Login ab. Der Base64-Schritt ist die einzige Kodierung im Header: Percent-encodieren Sie das Ergebnis nicht, URL-encodieren Sie es nicht, doppel-Base64-ieren Sie es nicht. Jeder dieser "hilfsbereiten" Extra-Schritte ist ein bekannter Bug, und die Doppel-Kodierung ist die häufigste, denn die Anmeldedaten kommen manchmal bereits vor-kodiert von einer Schicht an, die sie bereits Base64-gekodet hat, und eine zweite Kodierung produziert einen Header, der plausibel aussieht und still auf dem Server scheitert. Zwei Vorsichtshinweise zum Schema selbst, damit sie hier ankommen und nicht im Sicherheits-Abschnitt, wo sie verdünnt würden: Basic-Auth überträgt das Passwort in einer Form, die einen Befehl von lesbar entfernt ist, also ist sie nur über TLS akzeptabel, und selbst dann ist sie das falsche Werkzeug für die meisten API-Arbeiten, weshalb Bearer-Tokens und JWTs übernahmen. Die Aufgabe des Kodierers in alldem ist die kleine, ehrliche: die durch Doppelpunkt verbundenen Anmeldedaten in einen header-sicheren String zu verwandeln, und nichts mehr.

E-Mail: MIME und warum ToBase64Transform nicht umbricht

E-Mail ist das historische Zuhause von Base64, und es ist immer noch der Ort, von dem die 76-Zeichen-Regel kommt: Die MIME-Spezifikation wickelt kodierte Körper bei 76 Zeichen um, mit CRLF zwischen den Zeilen, damit kein SMTP-Hop einen Grund hat, sie erneut umzuwickeln. C# gibt Ihnen zwei Kodierer für diesen Job, und sie machen unterschiedliche Versprechen, was sich zu verstehen lohnt, bevor Sie einen wählen. Der erste ist der klassische Convert.ToBase64String mit InsertLineBreaks, den Sie im Zeilenumbruch-Abschnitt sahen, und er ist genau die MIME-Form, umgebrochen bei 76 mit CRLF, bereit zum Einfügen unter einem Content-Transfer-Encoding: base64-Header. Der zweite ist ToBase64Transform, der streaming-Geschwisterling, und hier ist die Überraschung: Er fügt keine Zeilenumbrüche ein. Er hat keinen Modus dafür, keine Option, keinen Konstruktions-Flag, und seine Ausgabe ist ein langer, nicht umgebrochener Stream:

using System.IO;
using System.Security.Cryptography;

using FileStream source = File.OpenRead("photo.png");
using MemoryStream destination = new MemoryStream();
using ToBase64Transform transform = new ToBase64Transform();
using CryptoStream encoder = new CryptoStream(source, transform, CryptoStreamMode.Read);

encoder.CopyTo(destination);
Console.WriteLine(destination.Length + " characters, no line breaks");

Also lautet die praktische Regel: Für kleine bis mittlere E-Mail-Payloads lesen Sie die Bytes und verwenden den umbrichenden klassischen Kodierer, denn Sie bekommen die MIME-Form direkt. Für große Anhänge streamen Sie mit ToBase64Transform, um den Speicher flach zu halten, und wickeln das Ergebnis selbst, wenn der Transport wirklich 76-Zeichen-Zeilen braucht, indem Sie die Ausgabe an Gruppen-Grenzen aufteilen (jedes 76. Zeichen, was immer eine Gruppen-Grenze ist, wie der Zeilenumbruch-Abschnitt erklärte). Die Transformation tut das Richtige, indem sie nicht umbricht: Sie verarbeitet die Eingabe in Gruppen von drei Bytes, und Zeilenumbrüche sind eine Formatierungs-Entscheidung, die zu der Schicht gehört, die den Transport kennt, nicht zu der Schicht, die Bytes in einer Pipe in Zeichen umwandelt.

Streaming: Große Dateien kodieren, ohne sie zweimal zu lesen

Wenn das Payload ein Video ist, ein Backup oder etwas, das Sie sich schämen würden, in einem String zu halten, ist der Streaming-Kodierer die ganze Lösung. Das Muster ist der Spiegel des Streaming auf der Dekodier-Seite: ein CryptoStream über die Quell-Datei, mit ToBase64Transform im Lese-Modus, und ein CopyTo in das Ziel. Die Datei fließt hinein, das Base64 fließt heraus, und der einzige Speicher, den der Prozess hält, ist der Puffer, den der Stream intern verwendet:

using System.IO;
using System.Security.Cryptography;

using FileStream source = File.OpenRead("video.mp4");
using FileStream target = File.Create("video.b64");
using ToBase64Transform transform = new ToBase64Transform();
using CryptoStream encoder = new CryptoStream(source, transform, CryptoStreamMode.Read);

encoder.CopyTo(target);
Console.WriteLine("Wrote " + target.Length + " characters.");

Zwei Fakten über dieses Muster lohnt es zu behalten. Erstens ist die Größe der Ausgabe vollständig durch die Größe der Eingabe bestimmt, 4 Zeichen pro 3 Bytes, also können Sie den Ziel-Platz reservieren, die Länge für einen Content-Length-Header vorberechnen oder ein Disk-Kontingent einkalkulieren, bevor ein einziges Byte fließt. Zweitens erwartet die Transformation ihre Eingabe in Gruppen von drei Bytes, und CryptoStream kümmert sich um diese Ausrichtung für Sie und füttert die Transformation genau mit dem, was sie will, während die Datei vorüberstreamt. Wenn Sie die Transformation je von Hand mit TransformBlock steuern, füttern Sie sie mit Vielfachen von drei und lassen Sie TransformFinalBlock den Tail abfließen, die ein oder zwei übrig gebliebenen Bytes, die zur letzten teilweisen Gruppe mit einem oder zwei Padding-Zeichen werden. Für die meisten Anwendungen ist die CopyTo-Form alles, was Sie je schreiben werden, und es ist die Form, die sich unter einem Speicherlimit gut benimmt, was genau der Ort ist, an dem große Dateien gerne leben.

Konfiguration, Umgebungsvariablen und Datenbanken

Der andere häufige Kodier-Job in C#-Anwendungen ist der Speicher-Job: Ein Geheimnis oder einen binären Blob zu nehmen und ihn an einen Ort zu legen, der nur Text annimmt. Umgebungsvariablen sind das sichtbare Beispiel, denn eine Umgebungsvariable ist, per Definition, ein String:

using System;
using System.Text;

string secret = "p@ssw0rd+and/symbols";
string packed = Convert.ToBase64String(Encoding.UTF8.GetBytes(secret));
Environment.SetEnvironmentVariable("SECRET_B64", packed);

string back = Encoding.UTF8.GetString(
  Convert.FromBase64String(Environment.GetEnvironmentVariable("SECRET_B64")));
Console.WriteLine(back == secret);
// True

In Datenbanken taucht dieselbe Idee gewöhnlich als byte[]-Eigenschaft auf, die eine Textspalte halten muss, und Entity Framework Core hat genau dafür einen eingebauten Mechanismus, einen Value-Converter, der Ihre Kodier- und Dekodier-Funktionen 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));

Der Converter ist die gesamte Datenbank-Integration: Der C#-Code sieht ein byte[], die Spalte sieht einen Base64-String, und der Rundweg ist an der Aufrufstelle unsichtbar. Zwei Vorsichtshinweise gehören zu diesem Abschnitt. Erstens zahlt die Spalte die 33-Prozent-Steuer: Eine Textspalte, dimensioniert für die kodierte Länge, hält ein Drittel weniger Daten als dieselbe Breite als Binär, also dimensionieren Sie eine feste Breite auf die Base64-Länge, und wenn Sie eine varchar(max) oder Entsprechendes haben, ist die Steuer nur ein Abrechnungs-Thema. Zweitens, und das ist der, der immer wieder zurückkommt, ist Base64 in einer Konfigurationsdatei eine Form, kein Schild. Es hält den Wert in einer Zeile, hält ihn aus dem Weg von Texteditoren und ist einen Befehl von lesbar für jeden, der die Datei lesen kann. Geheimnisse brauchen echten Schutz, einen Secret-Store, ein Key-Vault, mindestens Datei-Rechte, und das Base64 ist nur das Transport-Format, das das Geheimnis trägt, während es in der Konfiguration sitzt.

Von der Kommandozeile

Jeder Kodierer verdient ein 15-zeiliges Konsolen-Leben, und der C#-Kodierer ist angenehm, weil die Ausgabe ein gewöhnlicher String ist, der für die Standard-Ausgabe gemacht wurde. Hier ist das gesamte Tool: Es nimmt einen Datei-Pfad oder die Standard-Eingabe, kodiert sie und schreibt das Base64 in das Terminal, wo jede Shell-Pipeline es aufnehmen kann:

using System;
using System.IO;
using System.Text;

string input = args.Length > 0
  ? File.ReadAllText(args[0])
  : Console.In.ReadToEnd();

byte[] bytes = Encoding.UTF8.GetBytes(input);
Console.WriteLine(Convert.ToBase64String(bytes));

Bauen Sie es einmal, und es sitzt neben dem eigenen base64-Utility der Shell für die Tage, an denen Sie gezielt das Verhalten des .NET-Kodierers wollen: dasselbe Alphabet, dasselbe Padding und die UTF-8-Behandlung der C#-Laufzeitumgebung für alles, was die Pipe ihr hinstellt. Für binäre Dateien ist dasselbe Gerüst mit File.ReadAllBytes statt File.ReadAllText der gesamte Wechsel, und die Ausgabe beschreibt dann die exakten Bytes der Datei und nicht ihre Text-Interpretation. Das Tool ist auch eine gute Sonde: Schicken Sie eine Datei durch, schicken Sie die Ausgabe durch den Dekodierer des Dekodierungs-Artikels zurück, und diffen Sie die zwei Dateien, was eine befriedigende End-zu-Ende-Prüfung ist, dass beide Seiten der Pipe sich auf jedes Byte einigen.

Padding, oder die abschließenden Gleichheitszeichen

Die letzten =-Zeichen eines Base64-Strings sind die Buchhaltung des Formats, und C#'s Kodierer sind sich darüber nicht einig, was die Quelle eines spezifischen und häufigen Interop-Bugs ist. Der klassische Convert.ToBase64String paddet immer, weil der klassische Dekodierer, mit dem er gepaart ist, es immer erwartet. Base64Url.EncodeToString paddet nie, weil die URL-sicheren Konsumenten, die es anvisiert, JWTs und Token-APIs, immer die kompakte Form erwarten. Wenn Ihre Ausgabe in eine Welt mit der entgegengesetzten Erwartung übergeht, ist die Korrektur Arithmetik, und es ist dieselbe Arithmetik, die der Dekodierungs-Artikel für die umgekehrte Richtung zeigte:

using System;

string padded = Convert.ToBase64String(new byte[] { 1, 2 });
Console.WriteLine(padded);           // AQI=
Console.WriteLine(padded.TrimEnd('=')); // AQI, was ein URL-sicherer Konsument möchte

string compact = "AQI";
string restored = compact + new string('=', (4 - compact.Length % 4) % 4);
Console.WriteLine(restored);         // AQI=, was ein klassischer Dekodierer möchte

Die Formel (4 - length % 4) % 4 ist das gesamte Padding-Universum: Sie fügt null, ein oder zwei Zeichen hinzu, um die Länge auf ein Vielfaches von vier zu landen, und das äußere Modulo verhindert, dass bereits gepaddete Eingabe zusätzliche bekommt. Zwei Warnungen zum Padding, denn genau dort macht gutgemeinter Code Fehler. Behandeln Sie = nie als Daten: Es trägt keine Information, also sind sowohl das Kodieren eines Strings, der bereits Padding enthält, als wäre es Payload, als auch das URL-encodieren des = zu %3D in einem Query-String Wege, Ausgabe zu produzieren, die richtig aussieht und falsch dekodiert. Und hüten Sie sich vor der kleinen Familie von Legacy-Payloads, bei denen das Padding als ein anderes Zeichen geschrieben wurde, ein Punkt in einigen älteren Systemen, statt des Standard-=: Wenn ein Wert, den Sie erhalten, einen Punkt verwendet, wo Sie Padding erwarten, normalisieren Sie ihn vor dem Dekodieren zurück zu =, oder laufen Sie ihn unpaddet durch den URL-sicheren Pfad.

Wie schnell läuft es

Base64-Kodierung in modernem .NET ist schnell, und der interessante Teil ist die Speicher-Geschichte, nicht die CPU-Geschichte. Die Implementierungen der Laufzeitumgebung sind dort mit SIMD-Vektoranweisungen optimiert, wo die Hardware sie unterstützt, und mehr-Megabyte-Eingaben kodieren auf einem gewöhnlichen Desktop-Rechner in einstelligen bis niedrigen zweistelligen Millisekunden, schnell genug, dass der Kodierer in jeder Anwendung, die Sie schreiben werden, effektiv kostenlos ist. Der Performance-Rat, der Code tatsächlich ändert, hat mit der Form zu tun. Die Ausgabe ist ein C#-String, und ein C#-String speichert zwei Bytes pro Zeichen, also ist die Kosten im Speicher eines kodierten Ergebnisses ungefähr 2,7 Bytes pro Eingabe-Byte (4 Zeichen pro 3 Eingabe-Bytes, zu je 2 Bytes pro Zeichen), was eine Zahl ist, die man wissen sollte, wenn das Payload in Megabytes liegt. Wenn Sie Tausende kleiner Payloads in einer Schleife kodieren, bevorzugen Sie die Span- und Char-Buffer-APIs, die in Puffer schreiben, die Sie wiederverwenden, über die String-APIs, die bei jedem Aufruf einen frischen verwalteten String allozieren. Wenn Sie eine große Datei kodieren, überspringen Sie den String komplett und verwenden die Streaming-Transformation, denn die Kosten von 2-Bytes-pro-Zeichen für das Halten eines 13-Megabyte-Strings ist reine Verschwendung, wenn ein CopyTo den Arbeitsbereich in Stream-Puffern gehalten hätte. Und wenn Sie MIME-umgebrochene Ausgabe produzieren, erinnern Sie sich daran, dass der Umbruch-Durchgang ein zweiter Lauf über die Daten ist, also umbrechen Sie nur, wenn der Transport es braucht, nicht als Standard.

Das Sicherheits-Gespräch

Die Kodierer-Seite von Base64 hat eine Sicherheits-Lektion, und sie ist das Gegenteil von der des Dekodierers: Sie sind es, der die Entscheidung trifft, lesbare Daten zu exponieren, und das Format wird Sie nicht aufhalten. Base64 ist Kodierung, keine Verschlüsselung. Es hat keinen Schlüssel, keinen Algorithmus und keine Geheimhaltung irgendeiner Art, und die Ausgabe Ihres ToBase64String-Aufrufs ist einen Befehl von der Eingabe entfernt, auf jedem Rechner, in jeder Sprache, von jedermann. Also ist die erste Regel über das, was Sie wählen zu kodieren: 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 Dekodier-Aufruf tief, und die Person, die die Konfiguration liest, hat den Befehl. Wenn der Wert geheim sein muss, braucht er echten Schutz, und das Base64 ist nur die Form, die es trägt, während es im Textfeld sitzt.

Die zweite Lektion betrifft den Kanal, und sie ist spezifisch für die Dinge, die dieser Artikel baut. Ein Basic-Auth-Header trägt das Passwort in einer Form, die jeder Proxy, jedes Log und jede Middlebox lesen kann, weshalb das Schema nur über TLS akzeptabel ist und außerhalb von Legacy-Integrationen weitgehend überholt. Ein Data-URI im HTML trägt das Bild, und wenn das Bild ein benutzerbeliefertes SVG ist, trägt es alles, was das SVG trägt, weshalb der SVG-im-Data-URI-Fall dieselbe Vorsicht braucht wie jeder Nutzerinhalt. Und ein Base64-Wert in einer URL ist, buchstäblich, in der URL, was bedeutet, dass er in der Browser-Historie, im Server-Access-Log, im Referrer-Header und im Proxy-Cache ist, also gehören Tokens, die privat bleiben müssen, nicht in Query-Strings, gepaddet oder nicht. Der Kodierer tut in allen drei Fällen seinen ehrlichen Job, Bytes in einen transport-sicheren String zu verwandeln. Die Sicherheit liegt darin, was Sie tragen und wohin, und das Format ist ein besserer Bote als die meisten, aber es ist ein Bote, kein Tresor.

Fallen, in die C#-Kodierer hineinfallen

Das sind die Fallen, die immer wieder auf der Kodierer-Seite von C#-Code auftauchen, und jede hat eine konkrete Ursache darin, wie das Framework arbeitet:

  • Der Zeichensatz, den Sie nicht wählten. Einen String mit Encoding.Default zu kodieren, produziert auf .NET Framework (die Windows-ANSI-Codepage) und auf .NET (UTF-8) anderes Base64. Beide Ausgaben sind gültig, beide dekodieren "korrekt" auf ihrer Heimat-Plattform, und es sind nicht dieselben Bytes. Legen Sie die Encoding explizit fest.
  • Doppel-Kodierung. Die Eingabe war bereits Base64 (eine Konfiguration, die einen kodierte Wert kodiert hat, eine API, die ihre Eingabe neu kodiert), und der Kodierer, der genau tat, was man ihm sagte, produzierte Base64-von-Base64. Das Ergebnis sieht plausibel aus, und es dekodiert eine Schicht nach der anderen, und so wird ein Bug, der zwei Dekodierungen braucht, um behoben zu werden, in der Produktion entdeckt.
  • Zeilenumbrüche am falschen Ort. Die MIME-umgebrochene Form, mit ihren CRLF-Paaren, landet in einem JSON-String, einem JWT-Segment oder einem URL-Parameter, wo der strenge Konsument an dem Weißraum erstickt, dessen Existenz man ihm nie angekündigt hat. Wickeln Sie für die Mail, lassen Sie es überall sonst in Ruhe, und wenn Sie das Einwickeln jemand anderes streichen, streichen Sie das \r ebenso wie das \n.
  • Standard-Alphabet in einer URL. Ein + in einem Query-String wird von Form-Parsing-Regeln als Leerzeichen dekodiert, also kommt ein Standard-Base64-Wert, der in eine URL gelegt wurde, mit Buchstaben zurück, wo die Plus-Zeichen waren. Verwenden Sie das URL-sichere Alphabet, oder percent-encodieren Sie den ganzen Wert, und nie beides.
  • Der Padding-Verpass. Ihre Ausgabe ist gepaddet, der Konsument will kompakt, oder umgekehrt, und keine Seite hat Unrecht - sie sind sich nur nicht einig. Die Korrektur ist die Arithmetik aus dem Padding-Abschnitt, angewendet auf der Seite, die die Erwartung des Konsumenten kennt, und das ist gewöhnlich die Seite, die das Token schreibt.
  • Speicher, der nicht eingeplant war. Der kodierte String ist im Speicher zwei Bytes pro Zeichen, also wird eine 10-MB-Datei zu einem 13-Millionen-Zeichen-String, der ungefähr 27 MB im verwalteten Speicher wiegt, und eine Schleife, die solche Strings nacheinander baut, taucht im Profiler als Allokations-Churn ohne sichtbare Ursache auf. Dimensionieren Sie Puffer mit den Längen-Helpers, streamen Sie die großen und wiederverwenden Sie Puffer in den heißen Schleifen.
  • Die Transformation, die nicht umbricht. ToBase64Transform gibt eine lange Zeile aus. Code, der einen "MIME-bereiten" Anhang durch sie streamt und ihn dann mailt, produziert eine 120.000-Zeichen-Zeile, die manche Transportsysteme mitten in einer Gruppe neu umwickeln, was genau die Korruption ist, die die 76-Zeichen-Regel verhindern sollte.
  • Die Kodierung der Kodierung. Einen Base64-String in den Kodierer zu geben, weil "die Daten schon Text sind", produziert eine zweite Schicht. Der Kodierer weiß nicht und kümmert sich nicht, dass seine Eingabe wie Base64 aussieht; er kodiert, wie viele Zeichen der String auch immer hat, und der Dekodierer auf der anderen Seite bekommt einen Base64-String, wo er Ihre Daten erwartete.

Wie der Kodierer wuchs: Eine Versionstour

Die Kodier-Seite der API hat ihre eigene Zeitachse, und sie läuft vom zweiten .NET-Release bis zu dem, das aktuell im Preview ist:

  • .NET Framework 1.1, April 2003. Convert.ToBase64String und ToBase64CharArray kommen an, die ganze klassische Familie in einem Release, mit den Slice-Overloads bereits inklusive, was ein kleines Wunder der Weitsicht für eine 2003er-API ist.
  • .NET 2.0, 2005. Base64FormattingOptions und der Wert InsertLineBreaks gesellen sich zur Familie und bringen das MIME-Zeilenumbruch in das Framework, was eine Ära von handgerollten Substring-Schleifen im E-Mail-Code beendet.
  • .NET Core 2.1, 2018. Die Span-Ära. Convert bekommt die span-basierte Kodierung und TryToBase64Chars, und die neue System.Buffers.Text.Base64-Klasse kommt mit ihrem OperationStatus-Vertrag und der In-Place-Aufblähung, gebaut für die null-Allokation-Welt.
  • .NET 5, 2020. Die Hex-Geschwister (Convert.ToHexString und Freunde) shippen, dasselbe Designmuster, angewendet auf ein 16-Symbole-Alphabet, und das Konversions-Klassen-Muster wird zu einem Hausstil.
  • .NET 7, 2022. X509Certificate2.ExportCertificatePem lässt das Framework PEM für Sie produzieren, PEM-Marker, 64-Zeichen-Umbruch und Base64-Körper inklusive, was eine Klasse von manuellem Zertifikat-Formatierungs-Code still in den Ruhestand schickt.
  • .NET 9, November 2024. System.Buffers.Text.Base64Url landet im Kasten nach Jahren von Community-Wünschen, mit dem Microsoft.Bcl.Memory-Paket, das es auf .NET Framework 4.6.2 und höher zurückportiert, und dem Unpadding-Verhalten, das JWT-Code die ganze Zeit über handgerollt hatte.
  • .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 Marsch hin zu einer ergonomischeren Oberfläche fort.

Das Format selbst hat eine ältere Biografie, und sie ist der Grund, warum die C#-API so aussieht, wie sie aussieht. Die erste standardisierte Nutzung dessen, was wir heute MIME-Base64 nennen, war das Privacy-Enhanced-Mail-Protokoll 1987 (RFC 989), die MIME-Spezifikation fixierte 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, für die C# erst 2024 einen First-Class-Kodierer bekam. Drei Jahrzehnte E-Mail- und Web-Konventionen sind der Grund, warum die Zeilenumbrüche, das Padding und die zwei Alphabete alle existieren, und der C#-Kodierer ist der Ort, an dem alle drei aufeinandertreffen.

Kleine Wunder

  • Das Vier-Zeichen-Minimum. Die kleinste mögliche nicht-leere Base64-Ausgabe sind vier Zeichen, denn das Format denkt in Gruppen von vier, selbst wenn Sie ihm ein Byte geben. Ein Byte von irgendetwas kodiert zu zwei Buchstaben und zwei =-Zeichen, und diese Form, zwei Daten-Zeichen mit einer Padding-Mütze, ist ein Fingerabdruck, den Sie in Konfigurationen und Tokens zu erkennen beginnen werden.
  • Nullen sind willkommen. Der Kodierer hat keine Meinung darüber, was die Bytes bedeuten, also kodiert ein Puffer voller Nullen fröhlich in eine Mauer aus A-Zeichen, und eine binäre Datei mit ihren NUL-Bytes intakt round-trippt, ohne ein einziges zu verlieren. Die "Strings können kein Binär halten"-Sorge gehört zur String-Seite des Typsystems, nicht zum Kodierer, der nie einen String sieht.
  • Determinismus als Feature. Dieselben Bytes, dieselben Optionen, immer derselbe String. Kein Zeitstempel, kein zufälliges Salt, keine Variation, und genau deshalb macht ein Base64-String einen brauchbaren Schnell-und-Dirty-Fingerabdruck des Inhalts einer Datei: Zwei Dateien mit demselben Base64 sind dieselbe Datei, und der Check ist ein String-Vergleich.
  • Zwei Bytes pro Zeichen, kostenlos. Ein C#-String ist UTF-16, also belegt jedes Zeichen in Ihrer Base64-Ausgabe zwei Bytes im verwalteten Speicher. Der Kodierer kündigt das nicht an, die Längen-Eigenschaft meldet es nicht, und ein 13-Millionen-Zeichen-String wiegt einfach 26 MB, was die Zahl ist, die im Kopf zu haben ist, wenn das Payload groß ist.
  • CRLF aus Erbgut. Das MIME-Einwickeln setzt Wagenrücklauf-Zeilenvorschub-Paare ein, auch wenn Ihr Code auf Linux läuft, denn die Regel kommt aus der E-Mail-Spezifikation, nicht aus der Plattform. Der Kodierer ist Historiker so sehr wie Konverter, und er bewahrt die Zeilenenden von 1993 auf einem 2026er-Rechner.
  • Ein Slice-Overload vom ersten Tag. ToBase64String(byte[], int, int) kodiert seit 2003 ein Fenster in ein größeres Array, fünfzehn Jahre, bevor Spans die Idee fashionable machten. Die API-Designer der 1.1er-Ära schauten sich echte Puffer an und fügten die Offset-und-Längen-Form hinzu, und sie ist immer noch der richtige Zug, wenn die Daten ein Abschnitt eines größeren Reads sind.
  • Die 64-Zeichen-Zertifikats-Zeile. PEM wickelt bei 64 Zeichen um, nicht 76, und ExportCertificatePem weiß das und wickelt entsprechend, was eines der leisen Details ist, die "lass das Framework es tun" zum guten Rat für Zertifikats-Arbeit machen. Zwei Umbruch-Breiten, eine Format-Familie, und das Framework hält sie gerade.
  • Zwei Alphabete, zwei Namen. Die 64 Werte heißen in einem Teil der API "standard" und in einem anderen "URL-sicher", und sie unterscheiden sich in genau zwei Zeichen: den 62. und 63. Plätzen. + und / auf der einen Seite, - und _ auf der anderen, und jeder Interop-Bug in diesem Artikel lebt im Moment, in dem jemand annahm, die zwei Seiten seien gleich.

Den Kreis schließen

Das ist die Kodierer-Seite, und dort treffen Sie die Entscheidungen: das Alphabet, das Padding, die Zeilenumbrüche, der Zeichensatz, der Puffer. Die andere Richtung, Base64 von anderen Leuten zu empfangen, mit deren Padding-Wahlen, deren Zeilenumbrüchen, deren Alphabeten und deren Tokens, ist der Ort, an dem der größte Teil des Schmerzes lebt, denn mit einem Payload verhandelt man nicht. Base64-Dekodierung in C#, vom klassischen Einzeiler bis zu den Span- und URL-sicheren Familien, wird in dem unten verlinkten Begleit-Artikel ausführlich behandelt, und zusammen passen beide das ganze Thema in Ihren Arbeitspeicher, was der Punkt eines Formats ist, das so alt und so klein ist.

Zuletzt aktualisiert: 2026-09-08

Verwandter Artikel: Base64-Dekodierung in C# (CSharp): Ein vollständiger Leitfaden