Base64-Kodierung in C# (CSharp): Ein vollständiger Leitfaden
Sie haben die Bytes. Ein PNG, das in einer JSON-Antwort reisen muss, ein Token, das in eine URL passen muss, eine Textzeile, die gleich in ein System einzieht, das nur Buchstaben und Ziffern annimmt. Irgendwo zwischen dem byte[] in Ihrer Hand und dem Kanal, den es überqueren muss, bietet C# ein Menü aus Base64-Encodern, und das Wä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 Runtimes, und jede macht andere Versprechen über Größe, Zeilenumbrüche und Alphabet. Dieser Artikel geht das ganze Menü durch, mit lauffähigen Beispielen für jeden echten Job, für den man einen Encoder heranzieht.
Die Hausregeln zuerst, in einem Atemzug, denn die Startseite dieser Site erklärt das Format in der Tiefe: Der Encoder nimmt jeweils drei Bytes und schreibt vier Zeichen aus einem Alphabet mit 64 Symbolen, füllt den Tail mit ein oder zwei =-Zeichen auf, und so verlassen Ihre Daten das Haus etwa 33 Prozent dicker, als sie angekommen sind. Diese Zahl, nicht irgendwelcher Code, ist die wichtigste Tatsache in diesem Artikel, und alles, was unten kommt, dreht sich darum, sie klug zu bezahlen.
Das Encoder-Menü: Wählen Sie Ihr Werkzeug
Hier ist die komplette Familie an Kodier-APIs in der .NET-Welt, mit der Situation, für die jede einzelne gebaut wurde. Alles steckt in der Runtime selbst, ausgenommen die URL-sichere Klasse auf älteren Frameworks, die auf einem kleinen NuGet-Paket mitreitet:
| API | Verfügbar seit | Wofür es gut ist |
|---|---|---|
Convert.ToBase64String(byte[]) |
.NET Framework 1.1 (2003) | Der Klassiker. Ganzes Array hinein, gepaddeter String hinaus. 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 einen Zeilenumbruch alle 76 Zeichen einfügen, die MIME-Art. |
Convert.ToBase64String(ReadOnlySpan<byte>, Base64FormattingOptions) |
.NET Core 2.1 (2018) | Die Span-Version: Eine Ansicht in einen Puffer kodieren, keine Array-Kopie, keine Ausschnitt-Allokation. |
Convert.ToBase64CharArray(byte[], int, int, char[], int) |
.NET Framework 1.1 (2003) | In einen Zeichenpuffer 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 Exceptionen: Kodieren, wenn der Puffer passt, melden, wenn er es nicht tut. |
System.Buffers.Text.Base64.EncodeToUtf8, EncodeToUtf8InPlace |
.NET Core 2.1 (2018) | Die strenge Span-Familie: Statuscodes statt Exceptionen 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, ohne den ganzen Payload jemals im Speicher zu halten. |
Wenn Ihr Projekt eine .NET-Version ab 2018 als Ziel hat, sind die ersten sieben Zeilen und das Streaming-Paar unten an Bord. Base64Url braucht .NET 9 oder neuer, oder auf allem Älteren das Microsoft.Bcl.Memory-Paket. Und ein Ausblick: Die .NET-11-Bibliotheken, zur Zeit des Schreibens in der Vorschau, mit einer allgemeinen Veröffentlichung für Ende 2026 erwartet, fügen den vorhandenen Typen weitere Base64-Bequemlichkeits-APIs und Overloads hinzu, also wächst das Menü weiter. Kein anderes Paket in diesem Artikel ist nötig.
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
Achten Sie auf die Zwei-Schritte-Form, denn sie ist die häufigste "Warum passt mein Base64 nicht?"-Frage in C#. Es gibt keine Overload, die direkt einen string nimmt, und das ist gewollt: Ein C#-String ist UTF-16, und das Framework weigert sich zu raten, welche Bytes Sie meinten, als Sie "kodiere diesen Text" sagten. Sie wählen zuerst die Byte-Repräsentation, 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 engerer Taille: Die (byte[], int, int)-Overload kodiert einen Ausschnitt eines Puffers, ohne den Ausschnitt herauszukopieren, und die Span-Overload macht dasselbe von einem ReadOnlySpan<byte>, das das richtige Werkzeug ist, wenn die Daten ein Fenster in einen größeren Lese-Puffer sind. Eine weitere Eigenschaft des klassischen Encoders verdient eine klare Aussage: Er scheitert nie und fragt nie. Er gibt immer das Standard-Alphabet aus, enthält immer Padding und gibt Ihnen für dieselbe Eingabe immer denselben String, also ist ein Base64-String ein verlässlicher Fingerabdruck der Bytes, die ihn erzeugt haben.
Die 76-Zeichen-Frage: Zeilenumbrüche und Base64FormattingOptions
Es gibt einen Regler am klassischen Encoder, und er ist seit .NET 2.0 dabei: Base64FormattingOptions. Stellen Sie ihn auf InsertLineBreaks, und der Encoder fügt nach jeder Ausgabe von 76 Zeichen 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
Zwei Details über diesen Regler zählen in der Praxis. Erstens ist der Zeilenumbruch, den er einfügt, das Windows-Paar, Wagenrücksetzer plus Zeilenumbruch, kein nackter Zeilenumbruch. Die umgewickelte Ausgabe enthält also \r\n-Sequenzen, und jeder Code, der den String später "saubermacht", indem er nur \n entfernt, wird verirrte Wagenrücksetzer in den Daten zurücklassen. 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-Grenzen nie eine Gruppe von vier Base64-Zeichen über Zeilen verteilt: 76 ist ein Vielfaches von vier, also endet jede Zeile auf einer Gruppengrenze. Die umgewickelte Form wollen Sie, wenn Sie E-Mail-Bodies, PEM-Stil-Textblöcke oder alles produzieren, was eine Legacy-Mail-Pipeline tragen wird. Die nicht umgewickelte Form wollen Sie überall sonst: JSON-Payloads, URL-Tokens, API-Antworten und Dateien, die von einem strengen Parser dekodiert werden, der keine Überraschungen mag. Und die umgewickelte Form wollen Sie nie in einem JWT, wo die Spezifikation Zeilenumbrüche, Weißraum und sogar Padding ausdrücklich verbietet.
Die Ausgabe besitzen: Zeichenpuffer und die Try-APIs
Manchmal ist der String nicht das Ziel, der Puffer schon. Sie hängen an einem festen Zeichenpuffer an, Sie schreiben in einen Protokoll-Frame, oder Sie wollen einfach nicht, dass die Runtime die Ausgabe für Sie alloziert. Für solche Momente hat der Encoder seit den 1.1-Tagen einen Zeichenpuffer-Modus, und seit dem Span-Zeitalter einen Try-Modus. Die Zeichenpuffer-Methode schreibt in ein Array, das Sie bereitstellen, und sagt Ihnen, wie viele Zeichen sie verwendet hat, also ist das Bemessen des Puffers Ihr Job, und die Standardbibliothek übergibt Ihnen sogar die Bemessungsformel:
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-Bruder erledigt denselben Job von 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 etwas zu schreiben. Die letzte Eigenschaft macht ihn sicher für Eingabegrößen, denen Sie nicht vertrauen: Sie bekommen nie einen halb gefü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 eines Booleschen Werts: EncodeToUtf8 füllt einen Byte-Span, den Sie besitzen, und sagt Ihnen per Status, ob sie fertig war, keinen Platz mehr hatte oder mehr Eingabe braucht, und EncodeToUtf8InPlace ist die, zu der Sie greifen, wenn die Binärdaten bereits in einem Puffer sitzen, in den Sie hineinwachsen: 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. All diese teilen sich eine Regel zum Bemessen: Die Ausgabe für n Eingabe-Bytes sind immer 4 * ceil(n / 3) Zeichen einschließlich Padding, und die Helpers GetMaxEncodedToUtf8Length und Base64Url.GetEncodedLength implementieren diese Arithmetik - das zweite für die ungepaddete Länge, die immer die gepaddete Größe oder kürzer ist - also bemessen Sie von den Helpers und nie von einer aus dem Gedächtnis gegriffenen Konstante.
Der URL-sichere Encoder: Base64Url
Das Standard-Alphabet hat zwei Zeichen, die URLs nicht lieben. Das + in einem Query-String wird routinemäßig von den Regeln der Formular-Parade als Leerzeichen dekodiert, und sowohl / als auch = wollen Prozent-kodiert 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 nirgends maskiert werden müssen, und macht das nachgestellte =-Padding optional. Seit .NET 9 hat die Runtime eine dedizierte Klasse dafür, System.Buffers.Text.Base64Url, und sie hat ein Verhalten, das Leute 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
Der Unterschied ist der ganze Punkt. Ein JWT-Segment, eine Upload-ID, ein Token in einem Query-String, ein Wert in einem URL-Pfad: All diese wollen die URL-sichere, ungepaddete Form, und Base64Url.EncodeToString liefert sie direkt, mit Alphabet und Padding, beide so behandelt, wie diese Formate es vorschreiben. Die Klasse hat die volle Familie, Kodierung in String, in Zeichen-Span und in UTF-8-Byte-Span, plus GetEncodedLength zum Bemessen von Puffern und IsValid zum Prüfen der Eingabe auf dem Weg herein. Wenn Ihr Projekt auf einer älteren Runtime läuft, fügen Sie das Microsoft.Bcl.Memory-Paket hinzu, das Microsoft publiziert, um die Klasse auf .NET Framework 4.6.2 und höher zurückzuporten:
dotnet add package Microsoft.Bcl.Memory
Und wenn Sie das Paket nicht verwenden können, ist die selbst gebaute Version der klassische Encoder plus zwei Replaces und ein Trim, die Sie in unzähligen 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 ohne Padding
Die Reihenfolge der Operationen in dieser Kette ist bemerkenswert: Der Zeichen-Tausch passiert auf der Standard-Ausgabe, und das Padding wird zuletzt gestutzt, denn zuerst zu stützen ändert nichts und macht den Code schwerer zu lesen, und nach dem Stutzen zu tauschen würde zwar funktionieren, so aber werden subtile Bugs geboren. Verwenden Sie diese Form für Tokens, Identifikatoren und alles, was in einer URL leben wird, und reservieren Sie das Standard-Alphabet für E-Mail-Bodies, JSON-Payloads und Dateien, wo +, / und = sich vollkommen zu Hause fühlen.
Den Encoder füttern: Strings, Zeichensätze und die Encoding-Wahl
Jeder Kodier-Job, der mit Text anfängt, 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 sich die Ausgaben trennen, und die Trennung kann still sein. UTF-8 ist die Standardannahme im modernen Web, und hier ist es die richtige Standardannahme: Es roundtript jede Sprache, es ist das, was jede andere Plattform annehmen wird, wenn sie Ihren Payload dekodiert, und es ist das, was Encoding.UTF8 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==
Sehen Sie jetzt dasselbe Zeichen durch einen anderen Zeichensatz kodiert, und sehen Sie, warum "derselbe Text" ohne angehängten Zeichensatz kein wohldefinierter Begriff 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 weitesten Wirkungskreis ist Encoding.Default: Auf .NET Framework unter Windows ist es die ANSI-Zeichensatzseite des Systems, während es auf .NET (Core) UTF-8 ist, also produziert ein Programm, das mit Encoding.Default kodiert, auf einer Maschine von 2010 anderes Base64 als auf einer von 2025, und beide Ausgaben dekodieren auf ihrer Heimatplattform "korrekt". Wenn ein dekodierter Payload voller Kauderwelsch mit Akzenten ankommt, hat die ursprüngliche Kodierung einen anderen Zeichensatz verwendet als der Dekodierer annahm, und die Korrektur liegt auf dieser Seite der Pipe: Fixieren Sie die Encoding explizit, in beiden Richtungen, in Code, der das Team überlebt, das ihn geschrieben hat. Und ein letzter Hinweis auf das Typsystem selbst: Ein C#-String ist UTF-16, also wenn Sie je rohe UTF-16-Codeeinheiten in den Encoder geben (indem Sie Encoding.Unicode.GetBytes aufrufen), kostet jedes ASCII-Zeichen zwei Bytes und Ihre Ausgabe verdoppelt sich in der Größe, ohne jeden Nutzen, denn der Dekodierer auf der anderen Seite wird es als UTF-16-Text lesen, nicht als die Bytes Ihres ursprünglichen Strings. Base64 trägt die Bytes, die Sie ihm geben, und es ist ihm egal, was sie bedeuten.
Dateien: Von der Festplatte in einen String
Dateien sind der häufigste Kodier-Payload und der nachsichtigste, denn es gibt gar keine Zeichensatz-Frage: Die Bytes auf der Festplatte sind die Daten, und der Encoder ist es egal, ob sie ein Wort oder eine Wellenform buchstabieren. Der Round-Trip ist ein Lesen, ein Kodieren und ein Schreiben, und die einzige echte Entscheidung ist, wohin das Ergebnis geht:
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ößenrechnung ist die ganze Geschichte, und sie lohnt sich, bevor Sie sich ein Transportmittel aussuchen. Ein Megabyte Datei werden 1.333.336 Base64-Zeichen, und weil ein C#-String zwei Bytes pro Zeichen speichert, belegt dieses kodierte Ergebnis als String etwa 2,7 Megabyte im Speicher. Eine 10-Megabyte-Datei wird ein 13-Megabyte-String, der in 26 Megabyte verwaltetem Speicher sitzt. Keine davon ist ein Problem für ein Foto oder einen Config-Blob, und es ist ein sehr guter Grund, den Streaming-Encoder weiter unten zu verwenden, wenn der Payload ein Video ist. Das Muster oben ist das, zu dem Sie für alles greifen, was bequem in den Speicher passt, und es ist auch das Muster, das jede "eine Datei als Base64 in einem JSON-Body hochladen"-Funktion still verwendet: Datei lesen, kodieren, String ins JSON legen, und die API-Ebene ihre Arbeit machen lassen.
Bilder im Web: Data-URIs bauen
Der sichtbarste Konsument für kodierte Bilder ist das Web, und das Web-Format für "ein Bild, das im Dokument lebt", ist die Data-URI: ein data:-Scheme, gefolgt vom MIME-Typ, einer ;base64-Flagge, einem Komma und den kodierten Bytes. Eine in C# zu bauen ist String-Konkatenation, und der Encoder 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 langen PNG-Signatur, also beginnt jedes PNG, das Sie kodieren, so, und jede PNG-Data-URI, die das nicht tut, ist kein PNG. Drei praktische Hinweise gehören zu diesem Muster. Erstens ist die Data-URI eine vollständige Kopie des Bildes, um ein Drittel aufgeblasen, eingebettet in Ihrem HTML oder CSS, also tauscht sie eine Netzwerk-Anfrage gegen dauerhaftes Seitengewicht, ein gutes Geschäft für ein 4-kB-Favicon und ein Wuchergeschäft für ein 4-MB-Hauptbild, und der Encoder verhandelt nicht über die 33 Prozent. Zweitens: Wenn das Bild groß ist, skalieren Sie es herunter oder komprimieren Sie es neu, bevor Sie es kodieren, denn jedes Byte des Originals taucht in der Seite auf. Drittens: Seien Sie vorsichtig mit von Nutzern gelieferten SVGs in nutzer-sichtbarem HTML: Ein SVG kann Script tragen, also ist das Einbetten - inline oder über <object>/<embed> - eine klassische XSS-Fläche. Eine einfache <img>-Data-URI wird sie in modernen Browsern nicht ausführen, aber dasselbe Markup, in jenen Kontexten wiederverwendet, wird es ausführen. PNG, JPEG, GIF und WebP in Data-URIs sind harmlos; SVG ist das eine, das es nicht ist.
Einen JWT von Hand zusammenbauen
Einen JSON Web Token von Grund auf zu bauen ist ein Initiationsritual, und in C# ist es ein besseres Ritual als in den meisten Sprachen, denn die Bausteine sind kurz. Ein JWT sind drei base64url-Segmente, verbunden durch Punkte: der kodierte Header, der 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 der komplette Zusammenbau, mit einer Stellvertreter-Signatur, denn der kryptografische Schritt gehört zu Ihrem Signier-Key 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"; // Stellvertreter für den echten HMAC- oder ECDSA-Wert
string jwt = header + "." + payload + "." + signature;
Console.WriteLine(jwt);
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0MiIsIm5hbWUiOiJBZGEifQ.c2lnbmF0dXJl
Zwei Eigenschaften von Base64Url.EncodeToString machen in diesem Beispiel stille Arbeit. Sie gibt das URL-sichere Alphabet aus, also kann weder + noch / im Token auftauchen, und sie lässt das Padding weg, also taucht nie ein = auf, und genau das verlangt die JWS-Spezifikation, und genau das würde Convert.ToBase64String ohne Hilfe nicht tun. Wenn Sie auf einer Runtime vor .NET 9 sind, läuft derselbe Job durch den Standard-Encoder plus die Korrektur-Kette aus dem URL-sicheren Abschnitt: kodieren, die zwei Zeichen tauschen, das Padding stutzen. Die Reihenfolge der Segmente ist für die Signatur entscheidend, die über header plus Punkt plus payload als rohe ASCII-Bytes berechnet wird, also bauen Sie die beiden Segmente zuerst und signieren Sie ihre exakte Verkettung, nicht eine neu formatierte Version des JSON. Und eine Grenze, die scharf zu halten ist: Für alles, was ein Nutzer erreichen kann, bauen Sie JWTs gar nicht von Hand. Das System.IdentityModel.Tokens.Jwt-Paket macht Bauen, Signieren, Validierung und Ablauf für Sie, und seine base64url-Behandlung ist genau dieses Alphabet und diese Padding-Regel. Zusammenbau von Hand ist für Tests, Demos und den Tag, an dem Sie verstehen müssen, was die Bibliothek genau tut.
HTTP-Header: Basic Auth
Base64 taucht im klaren HTTP im Basic-Authentifizierungsschema auf, und die Kodier-Seite gehört zu den kürzesten Header-Bauten im Protokoll: Benutzername und Passwort mit einem Doppelpunkt verbinden, das Ergebnis als UTF-8 kodieren, Base64 machen und mit dem Schema-Namen präfixieren:
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 das heikle Teil: RFC 7617 lässt den Standard-Zeichensatz des Basic-Schemas aus Kompatibilitätsgründen undefiniert und bietet nur einen empfehlenden UTF-8-Hinweis, aber genau das erwarten alle modernen Server, also sollte ein Benutzername mit einem Akzent durch Encoding.UTF8 gehen und nicht durch was auch immer der Plattform-Standard ist, sonst dekodiert der Server einen anderen Byte-String und lehnt die Anmeldung ab. Der Base64-Schritt ist die einzige Kodierung im Header: Prozent-kodieren Sie das Ergebnis nicht, URL-kodieren Sie es nicht, doppel-Base64-machen Sie es nicht. Jeder dieser "hilfsbereiten" Extraschritte ist ein bekannter Bug, und der Doppel-Kodierungs-Bug ist der häufigste, denn die Anmeldedaten kommen manchmal bereits vor-kodiert von einer Schicht, die sie schon einmal Base64 gemacht hat, und eine zweite Kodierung produziert einen Header, der plausibel aussieht und auf dem Server still fehlschlägt. Zwei Warnungen zum Schema selbst, damit sie hier ankommen und nicht im Sicherheits-Abschnitt verwässert werden: Basic Auth überträgt das Passwort in einer Form, die einen Befehl vom Lesbaren entfernt ist, also ist es nur über TLS akzeptabel, und selbst dann ist es für die meisten API-Arbeiten das falsche Werkzeug, deshalb übernahmen Bearer-Tokens und JWTs. Die Arbeit des Encoders in all dem ist die kleine, ehrliche: die mit Doppelpunkt verbundenen Anmeldedaten in einen header-sicheren String verwandeln, und nichts mehr.
E-Mail: MIME und warum ToBase64Transform nicht umwickelt
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 bricht kodierte Bodies bei 76 Zeichen um, mit CRLF zwischen den Zeilen, damit kein SMTP-Hop einen Grund hat, sie neu umzubrechen. C# gibt Ihnen zwei Encoder für diesen Job, und sie machen verschiedene Versprechen, und das versteht sich besser, bevor man einen wählt. Der erste ist der klassische Convert.ToBase64String mit InsertLineBreaks, den Sie im Zeilenumbruch-Abschnitt gesehen haben, und er ist exakt die MIME-Form, bei 76 umgewickelt mit CRLF, bereit zum Einfügen unter einen Content-Transfer-Encoding: base64-Header. Der zweite ist ToBase64Transform, der Streaming-Cousin, und hier ist die Überraschung: Er fügt keine Zeilenumbrüche ein. Er hat keinen Modus dafür, keine Option, keinen Konstruktor-Flag, und seine Ausgabe ist ein langer, nicht umgewickelter Strom:
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");
Die praktische Regel lautet also: Für kleine bis mittlere E-Mail-Payloads lesen Sie die Bytes und verwenden Sie den umwickelnden klassischen Encoder, 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, und teilen Sie die Ausgabe an Gruppengrenzen (alle 76 Zeichen, die immer eine Gruppengrenze ist, wie der Zeilenumbruch-Abschnitt erklärte). Die Transform tut das Richtige, indem sie nicht umwickelt: Sie verarbeitet die Eingabe in Gruppen von drei Bytes, und Zeilenumbrüche sind eine Formatierungs-Entscheidung, die zur Schicht gehört, die den Transport kennt, nicht zu der, die Bytes in einer Pipe in Zeichen umwandelt.
Streaming: Große Dateien kodieren, ohne sie zweimal zu lesen
Wenn der Payload ein Video, ein Backup oder alles ist, was Sie sich schämen würden, in einem String zu halten, ist der Streaming-Encoder die komplette Lösung. Das Muster ist der Spiegel des Dekodier-Seiten-Streamings: ein CryptoStream über die Quell-Datei, mit ToBase64Transform im Lese-Modus, und ein CopyTo ins Ziel. Die Datei fließt rein, das Base64 fließt raus, 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 lohnen 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-Speicher reservieren, die Länge für einen content-length-Header vorberechnen oder eine Disk-Quote budgetieren, bevor ein einziges Byte fließt. Zweitens erwartet die Transform ihre Eingabe in Gruppen von drei Bytes, und CryptoStream kümmert sich um diese Ausrichtung für Sie, indem er der Transform genau das zufüttert, was sie will, während die Datei vorüberströmt. Wenn Sie die Transform je manuell mit TransformBlock fahren, füttern Sie Vielfache von drei, und lassen Sie TransformFinalBlock den Rest abfließen, die ein oder zwei übrig gebliebenen Bytes, die die letzte teilweise 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 einer Speichergrenze gut verhält, und genau dort leben große Dateien gerne.
Konfiguration, Umgebungsvariablen und Datenbanken
Der andere häufige Kodier-Job in C#-Anwendungen ist der Speicher-Job: Ein Geheimnis oder einen binären Blob nehmen und ihn an einen Ort 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 normalerweise 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));
Dieser Converter ist die gesamte Datenbank-Integration: Der C#-Code sieht ein byte[], die Spalte sieht einen Base64-String, und der Round-Trip ist auf der Aufrufstelle unsichtbar. Zwei Warnungen gehören in diesen Abschnitt. Erstens zahlt die Spalte die 33-Prozent-Steuer: Eine Textspalte, bemessen für die kodierte Länge, hält ein Drittel weniger Daten als dieselbe Breite als Binär, also wenn Sie eine Spalte mit fester Breite haben, bemessen Sie sie für die Base64-Länge, und wenn Sie eine varchar(max) oder Entsprechendes haben, ist die Steuer nur ein Abrechnungs-Problem. Zweitens, und das ist die, die immer wieder zurückkommt: Base64 in einer Config-Datei ist eine Form, kein Schild. Es hält den Wert in einer Zeile, hält ihn aus dem Weg der Texteditoren, und es ist einen Befehl von lesbarem entfernt für jeden, der die Datei lesen kann. Geheimnisse brauchen echten Schutz, einen Secret Store, einen Key Vault, mindestens Dateiberechtigungen, und das Base64 ist nur das Transportformat, das das Geheimnis trägt, während es in der Config sitzt.
Von der Kommandozeile
Jeder Encoder verdient ein 15-zeiliges Konsolenleben, und der C#-Encoder ist angenehm, denn die Ausgabe ist ein gewöhnlicher String, wofür die Standardausgabe gemacht wurde. Hier ist das ganze Tool: Es nimmt einen Dateipfad oder Standard-Input, kodiert ihn und schreibt das Base64 in das Terminal, wo jede Shell-Pipe 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 steht neben der eigenen base64-Nutzlichkeit der Shell für die Tage, an denen Sie spezifisch das Verhalten des .NET-Encoders wollen: dasselbe Alphabet, dasselbe Padding und die UTF-8-Behandlung der C#-Runtime für alles, was die Pipe ihr gibt. Für Binärdateien ist dasselbe Gerüst mit File.ReadAllBytes statt File.ReadAllText die gesamte Änderung, und die Ausgabe beschreibt dann die exakten Bytes der Datei, nicht ihre Text-Interpretation. Das Tool ist auch ein guter Prüfkopf: Leiten Sie eine Datei hindurch, leiten Sie die Ausgabe zurück durch den Decoder aus dem Dekodierungs-Artikel, und diffen Sie die zwei Dateien, was ein befriedigender End-zu-Ende-Check ist, dass beide Seiten der Pipe sich über jedes Byte einig sind.
Padding, oder die nachgestellten Gleichheitszeichen
Die letzten =-Zeichen eines Base64-Strings sind das Buchhaltungswesen des Formats, und die C#-Encoder sind sich darüber uneinig, was die Quelle eines spezifischen und häufigen Interop-Bugs ist. Der klassische Convert.ToBase64String paddet immer, denn der klassische Dekodierer, mit dem er paart, erwartet es immer. Base64Url.EncodeToString paddet nie, denn die URL-sicheren Konsumenten, die es anvisiert, JWTs und Token-APIs, erwarten immer die kompakte Form. Wenn Ihre Ausgabe in eine Welt mit der entgegengesetzten Erwartung überquert, ist die Korrektur Arithmetik, und es ist dieselbe Arithmetik, die der Dekodierungs-Artikel für die umgekehrte Richtung gezeigt hat:
using System;
string padded = Convert.ToBase64String(new byte[] { 1, 2 });
Console.WriteLine(padded); // AQI=
Console.WriteLine(padded.TrimEnd('=')); // AQI, was ein URL-sicherer Konsument will
string compact = "AQI";
string restored = compact + new string('=', (4 - compact.Length % 4) % 4);
Console.WriteLine(restored); // AQI=, was ein klassischer Dekodierer will
Die Formel (4 - length % 4) % 4 ist das gesamte Padding-Universum: Sie fügt null, eins oder zwei Zeichen hinzu, um die Länge auf ein Vielfaches von vier zu bringen, und das äußere Modulo verhindert, dass bereits gepaddete Eingabe zusätzliche erhält. Zwei Warnungen zum Padding, denn dort geht wohlgemeinter Code schief. Behandeln Sie das = nie als Daten: Es trägt keine Information, also ein String zu kodieren, der bereits Padding enthält, als wäre es Payload, oder das = in %3D in einer Query-String URL-kodieren, sind beide Wege, eine Ausgabe zu produzieren, die richtig aussieht und falsch dekodiert. Und achten Sie auf die kleine Familie von Legacy-Payloads, wo das Padding als ein anderes Zeichen geschrieben wurde, ein Punkt in einigen älteren Systemen, statt des Standard-=: Wenn ein Wert, den Sie erhalten, dort einen Punkt verwendet, wo Sie Padding erwarten, normalisieren Sie ihn zurück zu = vor dem Dekodieren, oder fahren Sie ihn ungepaddet 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 Runtime-Implementierungen sind mit SIMD-Vektoranweisungen optimiert, wo die Hardware sie unterstützt, und Mehr-Megabyte-Eingaben kodieren in einstelligen bis niedrigen zweistelligen Millisekunden auf einer gewöhnlichen Desktop-Maschine, schnell genug, dass der Encoder in jeder Anwendung, die Sie schreiben, effektiv umsonst ist. Der Performance-Rat, der tatsächlich Code verändert, hat mit der Form zu tun. Die Ausgabe ist ein C#-String, und ein C#-String speichert zwei Bytes pro Zeichen, also sind die Speicherkosten eines kodierten Ergebnisses etwa 2,7 Bytes pro Eingabe-Byte (4 Zeichen pro 3 Eingabe-Bytes, zu 2 Bytes pro Zeichen), eine Zahl, die sich zu kennen lohnt, wenn der Payload in Megabyte reicht. Wenn Sie Tausende kleiner Payloads in einer Schleife kodieren, bevorzugen Sie die Span- und Zeichenpuffer-APIs, die in Puffer schreiben, die Sie wiederverwenden, über den String-APIs, die pro Aufruf einen frischen verwalteten String allozieren. Wenn Sie eine große Datei kodieren, lassen Sie den String komplett aus und verwenden Sie die Streaming-Transform, denn die 2-Bytes-pro-Zeichen-Kosten, einen 13-Megabyte-String zu halten, sind reine Verschwendung, wenn ein CopyTo den Arbeitsspeicher in Stream-Puffern gehalten hätte. Und wenn Sie MIME-umwickelte Ausgabe produzieren, erinnern Sie sich daran, dass der Umbruch-Durchgang eine zweite Runde über die Daten ist, also wickeln Sie nur, wenn der Transport es braucht, nicht als Standard.
Das Gespräch über Sicherheit
Die Encoder-Seite von Base64 hat eine Sicherheitslektion, und sie ist die Umkehrung der Dekodierer-Seite: Sie sind der, der die Wahl trifft, lesbare Daten zu exponieren, und das Format wird Sie nicht aufhalten. Base64 ist Kodierung, keine Verschlüsselung. Es hat keinen Key, keinen Algorithmus und keine Geheimhaltung jeglicher Art, und die Ausgabe Ihres ToBase64String-Aufrufs ist einen Befehl von der Eingabe entfernt, auf jedem Computer, in jeder Sprache, von jedem. Also ist die erste Regel darüber, was Sie zu kodieren wählen: Legen Sie nie ein Passwort, ein Token oder ein Geheimnis in eine Config-Datei, die durch Base64 "geschützt" ist, denn der Schutz ist genau einen Dekodier-Aufruf tief, und die Person, die die Config 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 in dem 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 jeder Middlebox lesen kann, deshalb ist das Schema nur über TLS akzeptabel und außerhalb von Legacy-Integrationen größtenteils überholt. Eine Data-URI im HTML trägt das Bild, und wenn das Bild ein von Nutzern geliefertes SVG ist, trägt sie alles, was das SVG trägt, deshalb braucht der SVG-in-Data-URI-Fall dieselbe Vorsicht wie jeder nutzer-gelieferte Content. Und ein Base64-Wert in einer URL ist, wörtlich, in der URL, was bedeutet, er ist im Browser-Verlauf, im Server-Zugriffslog, im Referrer-Header und im Proxy-Cache, also gehören Tokens, die privat bleiben müssen, nicht in Query-Strings, gepaddet oder ungepaddet. Der Encoder tut in allen drei Fällen seine ehrliche Arbeit, Bytes in einen tragbaren String zu verwandeln. Die Sicherheit liegt darin, was Sie tragen, und wo, und das Format ist ein besserer Bote als die meisten, aber es ist ein Bote, kein Tresor.
Fallen, in die C#-Encoder stürzen
Das sind die Fallen, die immer wieder auf der Kodier-Seite von C#-Code auftauchen, und jede hat eine konkrete Ursache darin, wie das Framework arbeitet:
- Der Zeichensatz, den Sie nicht gewählt haben. Einen String mit
Encoding.Defaultzu kodieren, produziert auf .NET Framework (der Windows-ANSI-Zeichensatz) und auf .NET (UTF-8) anderes Base64. Beide Ausgaben sind gültig, beide dekodieren auf ihrer Heimatplattform "korrekt", und sie sind nicht dieselben Bytes. Fixieren Sie die Encoding explizit. - Doppelte Kodierung. Die Eingabe war bereits Base64 (eine Config, die einen kodierten Wert kodiert hat, eine API, die ihre Eingabe neu kodiert), und der Encoder, der genau das tut, was man ihm sagte, produzierte Base64-von-Base64. Das Ergebnis sieht plausibel aus, und es dekodiert eine Schicht zur anderen, so wird ein Bug entdeckt, der zwei Dekodierungen zum Fixen braucht, in Produktion.
- Zeilenumbrüche am falschen Ort. Die MIME-umwickelte 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 gesagt hat. Wickeln Sie für die Post, lassen Sie es überall sonst in Ruhe, und wenn Sie das Umwickeln von jemand anderem entfernen, entfernen Sie das
\rebenso wie das\n. - Standard-Alphabet in einer URL. Ein
+in einer Query-String wird von den Regeln der Formular-Parade als Leerzeichen dekodiert, also kommt ein Standard-Base64-Wert, der in eine URL gesetzt wurde, zurück mit Buchstaben dort, wo die Plus-Zeichen waren. Verwenden Sie das URL-sichere Alphabet, oder Prozent-kodieren Sie den ganzen Wert, aber nie beides. - Die Padding-Unterscheidung. Ihre Ausgabe ist gepaddet, der Konsument will kompakt, oder umgekehrt, und keine Seite hat unrecht - sie sind sich einfach nicht einig. Die Korrektur ist die Arithmetik aus dem Padding-Abschnitt, angewandt auf der Seite, die die Erwartung des Konsumenten kennt, und das ist meistens die Seite, die das Token schreibt.
- Speicher, der nicht budgetiert wurde. Der kodierte String belegt im Speicher zwei Bytes pro Zeichen, also wird eine 10-MB-Datei zu einem String von 13 Millionen Zeichen, der in verwaltetem Speicher etwa 27 MB wiegt, und eine Schleife, die solche Strings einen nach dem anderen baut, taucht im Profiler als Allokations-Churn ohne sichtbare Ursache auf. Bemessen Sie Puffer mit den Längen-Helpers, streamen Sie die großen, und verwenden Sie Puffer in den heißen Schleifen wieder.
- Die Transform, die nicht umwickelt.
ToBase64Transformgibt eine einzige lange Zeile aus. Code, der einen "MIME-fertigen" Anhang hindurch streamt und ihn dann verschickt, produziert eine 120.000-Zeichen-Zeile, die manche Transporte in der Mitte einer Gruppe neu umwickeln, was genau die Korruption ist, die die 76-Zeichen-Regel verhindern sollte. - Die Kodierung kodieren. Einem Encoder einen Base64-String zu geben, weil "die Daten schon Text sind", produziert eine zweite Schicht. Der Encoder weiß es nicht, und es ist ihm auch egal, dass seine Eingabe wie Base64 aussieht; er kodiert, wie viele Zeichen der String gerade hat, und der Dekodierer auf der anderen Seite bekommt einen Base64-String, wo er Ihre Daten erwartet hat.
Wie der Encoder wuchs: Eine Versionstour
Die Kodier-Seite der API hat ihre eigene Zeitleiste, und sie läuft von der zweiten .NET-Veröffentlichung bis zu der, die gerade in der Vorschau ist:
- .NET Framework 1.1, April 2003.
Convert.ToBase64StringundToBase64CharArraykommen, die ganze klassische Familie in einem Release, mit den Ausschnitt-Overloads bereits inklusive, was für eine API von 2003 ein kleines Wunder der Vorsehung ist. - .NET 2.0, 2005.
Base64FormattingOptionsund derInsertLineBreaks-Wert treten der Familie bei, bringen das MIME-Zeilenumbruch in das Framework und beenden ein Zeitalter von selbst gebautenSubstring-Schleifen im E-Mail-Code. - .NET Core 2.1, 2018. Das Span-Zeitalter.
Convertbekommt das span-basierte Kodieren undTryToBase64Chars, und die neueSystem.Buffers.Text.Base64-Klasse kommt mit ihremOperationStatus-Vertrag und dem In-Place-Aufblähen, gebaut für die Welt der Null-Allokation. - .NET 5, 2020. Die Hex-Geschwister (
Convert.ToHexStringund Freunde) erscheinen, dasselbe Designmuster, angewandt auf ein 16-symboliges Alphabet, und das Konversions-Klassen-Muster wird zu einem Hausstil. - .NET 7, 2022.
X509Certificate2.ExportCertificatePemlässt das Framework PEM für Sie produzieren, Rüstungs-Marker, 64-Zeichen-Umbruch und Base64-Body inklusive, was still und leise eine Klasse an manuellem Zertifikats-Formatierungs-Code in den Ruhestand versetzt. - .NET 9, November 2024.
System.Buffers.Text.Base64Urllandet an Bord nach Jahren von Community-Wünschen, mit demMicrosoft.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 selbst gebaut hat. - .NET 11, zur Zeit des Schreibens in der Vorschau. Das nächste Release, für Ende 2026 erwartet, fügt den vorhandenen Typen weitere Base64-Bequemlichkeits-APIs und Overloads hinzu und setzt den Marsch 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 Verwendung dessen, was wir heute MIME-Base64 nennen, war das Privacy-Enhanced-Mail-Protokoll 1987 (RFC 989), die MIME-Spezifikation fixierte die 76-Zeichen-umgewickelte 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-Encoder 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#-Encoder ist der Ort, an dem sich alle drei treffen.
Kleine Wunder
- Das Vier-Zeichen-Minimum. Die kleinste mögliche nicht-leere Base64-Ausgabe sind vier Zeichen, denn das Format denkt in Gruppen zu viert, 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 Configs und Tokens zu erkennen beginnen werden. - Nullen sind willkommen. Der Encoder hat keine Meinung darüber, was die Bytes bedeuten, also kodiert ein Puffer voller Nullen fröhlich zu einer Mauer aus
A-Zeichen, und eine Binärdatei mit ihren NUL-Bytes intakt roundtript, ohne ein einziges davon zu verlieren. Die "Strings können kein Binäres halten"-Sorge gehört zur String-Seite des Typsystems, nicht zum Encoder, der nie einen String sieht. - Determinismus als Feature. Dieselben Bytes, dieselben Optionen, immer derselbe String. Kein Zeitstempel, kein zufälliges Salz, keine Variation, deshalb ist ein Base64-String ein brauchbarer Schnell-und-Schmutzig-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 in verwaltetem Speicher. Der Encoder verkündet das nicht, die Length-Eigenschaft meldet es nicht, und ein 13-Millionen-Zeichen-String wiegt einfach 26 MB, und das ist die Zahl, die im Kopf sein sollte, wenn der Payload groß ist.
- CRLF aus Herkunft. Das MIME-Umwickeln fügt Wagenrücksetzer-Zeilenumbruch-Paare ein, selbst wenn Ihr Code auf Linux läuft, denn die Regel kommt aus der E-Mail-Spezifikation, nicht aus der Plattform. Der Encoder ist ebenso Historiker wie Umwandler, und er bewahrt die Zeilenenden von 1993 auf einer Maschine von 2026.
- Eine Ausschnitt-Overload vom ersten Tag an.
ToBase64String(byte[], int, int)kodiert seit 2003 ein Fenster in ein größeres Array, fünfzehn Jahre, bevor Spans die Idee modisch machten. Die API-Designer der 1.1-Ära sahen sich echte Puffer an und fügten die Offset-und-Länge-Form hinzu, und sie ist immer noch der richtige Griff, wenn die Daten ein Abschnitt eines größeren Lese-Vorgangs sind. - Die 64-Zeichen-Zertifikatszeile. PEM wickelt bei 64 Zeichen um, nicht 76, und
ExportCertificatePemweiß das und wickelt entsprechend, was eines der stillen Details ist, die "lass das Framework es tun" zum richtigen Rat für Zertifikats-Arbeit machen. Zwei Umbruch-Breiten, eine Format-Familie, und das Framework behält sie auseinander. - 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: dem 62. und 63. Slot.
+und/auf der einen Seite,-und_auf der anderen, und jeder Interop-Bug in diesem Artikel lebt in dem Moment, in dem jemand annahm, die zwei Seiten seien dieselben.
Wieder im Kreis
Das war die Encoder-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 ihren Padding-Entscheidungen, ihren Zeilenumbrüchen, ihren Alphabeten und ihren Tokens, ist der Ort, an dem der größte Teil des Schmerzes lebt, denn mit einem Payload kann man nicht verhandeln. Base64-Dekodierung in C#, vom klassischen Einzeiler bis zu den Span- und URL-sicheren Familien, wird im unten verlinkten Begleit-Artikel vertieft behandelt, und zusammen passen die beiden das ganze Thema in Ihren Arbeitsspeicher, und das ist der Punkt bei einem Format, das so alt und so klein ist.
Zuletzt aktualisiert: 2026-09-08
Verwandter Artikel: Base64-Dekodierung in C# (CSharp): Ein vollständiger Leitfaden