Base64-Kodierung in PowerShell: Ein vollständiger Leitfaden
Sie haben einen String, eine Datei, ein Zertifikat oder einen Token, und die andere Seite der Leitung will es als eine lange Kette aus Buchstaben und Ziffern: druckbar, in eine E-Mail, eine URL oder eine Konfigurationsdatei kopierbar, ohne ein einziges Binär-Byte, das den Transport kaputt macht. Das ist Base64. Es ist eine Übersetzung, keine Kompression und kein Schloss: drei Bytes Eingabe werden zu vier Zeichen Ausgabe, also wird der Text, den Sie verschicken, etwa 33% größer als das, was ihn gestartet hat, mit einem Alphabet von 64 Zeichen plus dem Gleichheitszeichen als Padding am Ende.
Die Startseite dieser Site deckt das Alphabet, die Bit-Mathematik und die Varianten im Detail ab. Dieser Artikel deckt die Kodierungsrichtung von der PowerShell-Seite ab: die eine .NET-Methode, die Sie aufrufen, der fehlende Schritt, der alle bei ihrem ersten Skript ausbremst, die Umbruch-Konventionen, die je nach Protokoll unterschiedlich sind, das URL-sichere Alphabet, und die Handvoll echter Jobs, bei denen das Kodieren in PowerShell die Sorgfältigen belohnt und die Nachlässigen bestraft.
Die Methode und der fehlende Schritt
PowerShell liefert kein eigenes Base64-Cmdlet mit. Die Arbeit erledigt eine Methode, die seit .NET Framework 1.1 im Jahr 2003 Teil des .NET-Frameworks ist, drei Jahre bevor PowerShell selbst erschien:
$bytes = [System.Text.Encoding]::UTF8.GetBytes("Hello")
[System.Convert]::ToBase64String($bytes)
# SGVsbG8=
Das ist die ganze API: ein Byte-Array rein, ein String raus, in jedem PowerShell auf jedem Betriebssystem, weil es schlicht .NET ist. Der fehlende Schritt ist die erste Zeile des Beispiels, und genau dort verlieren Anfänger ihre erste Stunde. Die Methode akzeptiert nicht Ihren String. Sie akzeptiert Bytes, und die Frage "welche Bytes bedeutet mein String" ist eine Kodierungsfrage, die nur Sie beantworten können. Hier ist der Vertrag der Overloads, die Sie von PowerShell aus tatsächlich aufrufen können:
| Was Sie übergeben | Was Sie bekommen |
|---|---|
byte[] |
Eine lange Zeile Standard-Base64, mit =-Padding, wo die Länge es verlangt |
byte[] plus InsertLineBreaks |
Dieselben Daten, bei 76 Zeichen umgebrochen, mit CRLF zwischen den Zeilen |
byte[], offset, count |
Nur die angeforderte Scheibe des Arrays, kodiert |
Ein String wie "Hello" |
Eine Umwandlungs-Ausnahme. PowerShell kann einen String nicht von allein in ein Byte-Array verwandeln |
$null |
Eine ArgumentNullException, eingepackt für Sie in eine MethodInvocationException |
Beachten Sie, was in dieser Tabelle fehlt: Es gibt keinen Overload, der sagt "kodiere diesen Text". Text in PowerShell zu kodieren ist immer ein Zwei-Schritte-Prozess. Sie entscheiden die Kodierung, Sie erzeugen die Bytes, und erst dann tritt die Base64-Methode ins Gespräch ein. Halten Sie diese beiden Entscheidungen im Skript sichtbar getrennt, weil die zweite unsichtbar ist und die erste der Ort ist, an dem die Bugs leben.
Text kodieren: Erst die Kodierung wählen
Die sichere Standardwahl für alles, was das moderne Internet überquert, ist UTF-8. Web-APIs, JSON, JWTs, alles, was ein Browser oder ein Server im letzten Jahrzehnt geschrieben hat, erwartet UTF-8-Bytes unter dem Base64, und das Zwei-Schritte-Muster ist die Gewohnheit, die Sie aufbauen wollen:
$text = "Hello, PowerShell!"
$bytes = [System.Text.Encoding]::UTF8.GetBytes($text)
$encoded = [System.Convert]::ToBase64String($bytes)
# SGVsbG8sIFBvd2VyU2hlbGwh
Wenn Sie zu einer anderen Kodierung greifen, bedienen Sie in der Regel ein Legacy-System, und die Tabelle unten ist der praktische Leitfaden:
| Kodierung | Wann Sie sie verwenden | Wenn Sie die falsche wählen |
|---|---|---|
UTF8 |
Web-APIs, JSON, JWTs, alles Moderne. Die Standardwahl | Der Decoder auf der anderen Seite sieht Mojibake statt Ihres Textes |
Unicode (UTF-16LE) |
Der Konsument ist eine Windows- oder .NET-Komponente, die .NET-Strings kodiert, oder -EncodedCommand |
Ihr Payload ist doppelt so lang, wie der Konsument erwartet, und voller Überraschungen |
ASCII |
Klassische 7-Bit-Protokolle wie HTTP-Basic-Zugangsdaten | Alles über dem Wert 127 wird ersetzt, bevor die Kodierung überhaupt stattfindet |
Latin1 |
Alte europäische Systeme von vor UTF-8 | Ein Byte pro Zeichen, und jedes nicht-Latin-1-Zeichen wird zu einem Fragezeichen |
Ein nützlicher Debugging-Trick funktioniert in beide Richtungen: Das Padding und die Länge des Base64 sagen Ihnen, wie viele Bytes kodiert wurden, und das Aussehen des dekodierten Textes sagt Ihnen, aus welcher Zwei-Byte- oder Ein-Byte-Welt er kommt. Ein Payload, der verdächtig gerade in der Größe ist und voller abwechselnder normaler und leeraussehender Zeichen, ist meistens UTF-16 in einem UTF-8-Kostüm, oder umgekehrt.
Die UTF-16-Überraschung
PowerShell speichert Strings intern als UTF-16, und diese Tatsache sickert an einer bestimmten, sehr häufigen Stelle in die Base64-Arbeit ein: Sie schreiben das Base64 für einen Konsumenten, der selbst eine .NET- oder Windows-Komponente ist, und kodieren mit Unicode, weil das das ist, was .NET-Strings sind. Das ist für einige Konsumenten der richtige Instinkt und für alle anderen ein Größen-Verdopplungs-Fehler. Dieselben vier sichtbaren Zeichen, zwei Kodierungen:
$same = "Café"
[System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($same))
# Q2Fmw6k= fünf Bytes
[System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($same))
# QwBhAGYA6QA= acht Bytes
Derselbe Text, doppelte Größe, und die beiden Strings sind nicht austauschbar: Ein Konsument, der das eine erwartet und das andere erhält, schlägt nicht laut fehl; er liest einfach Müll. Die Regel, die Sie davor bewahrt, sich daran zu verbrennen, ist, die Kodierung als Teil des Protokolls zu behandeln, nicht als lokale Einzelheit. Wenn das empfangende System ein Browser, eine REST-API oder ein moderner Server ist, ist es UTF-8, es sei denn, die Dokumentation sagt etwas anderes. Wenn es der PowerShell-Host selbst über -EncodedCommand ist, oder ein .NET-String in einer nur-Windows-Pipeline, ist es UTF-16LE. Wenn das Protokoll nichts sagt, fragen Sie die andere Seite, womit sie GetString aufrufen wird, denn das ist die Frage, die tatsächlich entscheidet.
Zahlen, Bytes und alles andere
Die Methode ist deklariert, ein Byte-Array zu nehmen, aber die Typumwandlung von PowerShell ist großzügig, was als eines zählt, und die Kanten zu kennen bewahrt Sie vor Überraschungen:
[System.Convert]::ToBase64String([byte[]](1, 2, 3, 250, 251))
# AQID+vs=
[System.Convert]::ToBase64String([char[]]"Café")
# Q2Fm6Q== ein Byte pro Zeichen, der Zahlenwert des Zeichens
[System.Convert]::ToBase64String([int[]](72, 101, 108, 108, 111))
# SGVsbG8=
[System.Convert]::ToBase64String(123)
# ew== eine einzelne Zahl wird akzeptiert, wo ein ganzes Array erwartet wird
Zwei Kanten in diesem Block verdienen Aufmerksamkeit. Zeichen-Arrays wandeln ein Byte pro Zeichen mit dem Zahlenwert des Zeichens um, was für lateinischen Text genau das ist, was die Legacy-Systeme, die diesen Trick verwenden, erwarten, und für alles darüber hinaus still die falschen Bytes produziert. Eine Ganzzahl über 255 ist die Kante, die stattdessen laut fehlschlägt: Die Byte-Umwandlung von PowerShell verweigert Werte außerhalb von 0-255 mit einer Ausnahme, also stoppt 256 das Skript beim Cast, statt Ihre Daten still zu beschädigen. Wenn Ihre Quelle Zahlen sind, machen Sie den Cast explizit: [byte[]](1, 2, 3) sagt genau das, was es bedeutet.
Übergeben Sie der Methode einen String, und Sie bekommen dieselbe laute Behandlung aus einem anderen Grund: Es gibt keinen Weg zu wissen, welche Bytes ein String bedeutet, also gibt die Umwandlungs-Engine von PowerShell auf. Übergeben Sie ihr $null, und .NET wirft eine Ausnahme, bevor es irgendetwas tut. Beides ist korrektes Verhalten, und beides ist der Grund, warum das Zwei-Schritte-Muster aus dem ersten Abschnitt das einzige Muster ist, das es wert ist, zu haben.
Zeilenumbruch: 76, 64 und keiner
Standardmäßig produziert der Encoder eine einzige lange Zeile, egal wie viele Daten Sie ihm geben. Für eine Datei von wenigen Kilobytes ist das in Ordnung. Für Daten, die von einem Menschen gelesen, in eine E-Mail kopiert oder in einem Source-Control-Diff verglichen werden, ist eine Mauer von acht Millionen Zeichen ein praktisches Problem, und die Konvention ist, umzubrechen. PowerShell und die Protokolle, die es bedient, kennen drei Breiten, und sie sind nicht austauschbar:
| Breite | Wer erwartet sie | Zeilenende |
|---|---|---|
| 76 Zeichen | MIME, Mail und die meisten Text-Transporte. Der Standard von InsertLineBreaks |
CRLF |
| 64 Zeichen | PEM-Dateien: Zertifikate, private Schlüssel und der Rest der -----BEGIN-Familie |
Konventionell LF |
| Keine | APIs, Tokens, Konfigurationsdateien, alles, wo der Payload maschinell verarbeitet wird | Überhaupt keine Zeile |
Der eingebaute Umbruch ist eine Ein-Parameter-Änderung, und er ist der, den Sie für Mail-artige Payloads wollen:
$text = "The quick brown fox jumps over the lazy dog. Base64 output arrives wrapped at different widths depending on who is reading it."
$wrapped = [System.Convert]::ToBase64String(
[System.Text.Encoding]::UTF8.GetBytes($text),
[Base64FormattingOptions]::InsertLineBreaks)
# 76 Zeichen pro Zeile, CRLF dazwischen, genau wie MIME es erwartet
PEM ist die Ausnahme vom Eingebauten, weil OpenSSL und das gesamte -----BEGIN-Ökosystem bei 64 Zeichen umbricht und kein .NET-Flag diese Breite produziert. Die Schleife ist kurz und sie ist das Standard-Rezept:
$der = [System.IO.File]::ReadAllBytes("./certificate.der")
$b64 = [System.Convert]::ToBase64String($der)
$lines = for ($i = 0; $i -lt $b64.Length; $i += 64) {
$b64.Substring($i, [Math]::Min(64, $b64.Length - $i))
}
$pem = @("-----BEGIN CERTIFICATE-----") + @($lines) + @("-----END CERTIFICATE-----")
Set-Content -Path "./certificate.pem" -Value ($pem -join "`n")
Der Grund, warum die Breite überhaupt wichtig ist, ist, dass Base64-Gruppen von vier Zeichen Zeilenumbrüche nicht respektieren, also kann ein Decoder die Umbrüche entweder komplett ignorieren oder sie durchsetzen. Der Decoder, den diese Site verwendet, ignoriert sie, aber strenge Konsumenten, und davon gibt es viele in den Zertifikat- und Mail-Welten, behandeln einen unerwarteten Umbruch als fremdes Zeichen und lehnen den Payload ab. Wenn Sie eine Breite wählen, schließen Sie einen Vertrag mit dem Konsumenten, und es lohnt sich, im Skript mit einem Kommentar zu benennen, mit wem Sie diesen Vertrag schließen.
base64url: Zwei Zeichen und eine Padding-Entscheidung
Das Plus und der Slash von Standard-Base64 sind innerhalb einer URL nur nach Prozent-Kodierung legal, und das Padding aus Gleichheitszeichen liest sich wie ein Feldtrenner. Also hat RFC 4648 ein URL- und Dateinamens-sicheres Alphabet definiert: dieselben 64 Zeichen, nur dass Plus zum Bindestrich wird und Slash zum Unterstrich, und Padding wird in der Regel weggelassen, weil die Länge der Daten es unnötig macht. Jedes API-Token und jeder JWT, den Sie je bearbeitet haben, ist in dieser Variante geschrieben, die der Standard darauf besteht, base64url zu nennen und nicht einfach base64.
Der Standard-Encoder von PowerShell produziert das Standard-Alphabet, also ist die Umwandlung in base64url zwei Zeichen-Tausche und eine Padding-Entscheidung:
$bytes = [System.Text.Encoding]::UTF8.GetBytes("Париж encoded 大阪")
$standard = [System.Convert]::ToBase64String($bytes)
$standard
# 0J/QsNGA0LjQtiBlbmNvZGVkIOWkp+mYqg== das Standard-Alphabet, Padding inklusive
$url = $standard.Replace("+", "-").Replace("/", "_").TrimEnd("=")
$url
# 0J_QsNGA0LjQtiBlbmNvZGVkIOWkp-mYqg die URL-sichere Form, Padding entfernt
Das Entfernen des Paddings ist in der base64url-Welt sicher, weil der Konsument das Padding aus der Länge des Strings neu berechnet. Das stimmt aber nicht überall, also treffen Sie die Entscheidung explizit: Padding weglassen für Tokens, JWT-Segmente und URL-Einbettung, beibehalten für alles, was einen strengen Standard-Alphabet-Konsumenten füttert, und aufschreiben, was Sie gewählt haben. Die .NET-Laufzeit liefert tatsächlich eine dedizierte Klasse für dieses Alphabet mit, System.Buffers.Text.Base64Url (hinzugefügt in .NET 9), mit Methoden, die um ReadOnlySpan<T>-Parameter herum gebaut sind. Aktuelles PowerShell (7.4 und neuer, sobald es auf einer .NET-Version läuft, die die Klasse mitliefert) kann diese tatsächlich direkt aufrufen - [System.Buffers.Text.Base64Url]::EncodeToString($bytes) funktioniert heute, weil der Method-Binder jetzt ein Array-Argument implizit in einen Span umwandelt - aber der Zwei-Zeichen-Tausch ist immer noch der, zu dem Sie greifen, wann immer das Skript auf Windows PowerShell 5.1, einem älteren PowerShell-7.x-Release oder einem Host mit einer Laufzeit vor .NET 9 laufen muss, und er funktioniert in jeder dieser Versionen.
Einen JWT prägen
Ein JSON Web Token ist die Flaggschiff-Anwendung von base64url in der realen Welt, und er ist auch ein guter Volltest der Kodierungs-Pipeline, denn ein JWT ist drei kodierte Segmente, verbunden durch Punkte: der Header, der Payload und die Signatur. Die ersten beiden sind kompaktes JSON in base64url, und das dritte ist die binäre Ausgabe eines Hashes über den exakten Text der ersten beiden. Hier ist ein vollständiger HS256-Token, von Anfang bis Ende in PowerShell gebaut:
$header = @{ alg = "HS256"; typ = "JWT" } | ConvertTo-Json -Compress
$payload = @{ sub = "1234567890"; name = "John Doe"; iat = 1516239022 } | ConvertTo-Json -Compress
function UrlEncode64([byte[]]$bytes) {
$standard = [System.Convert]::ToBase64String($bytes).TrimEnd("=")
return $standard.Replace("+", "-").Replace("/", "_")
}
$left = (UrlEncode64 ([System.Text.Encoding]::UTF8.GetBytes($header))) + "." + (UrlEncode64 ([System.Text.Encoding]::UTF8.GetBytes($payload)))
$hmac = [System.Security.Cryptography.HMACSHA256]::new([System.Text.Encoding]::UTF8.GetBytes("secret"))
$signature = UrlEncode64 ($hmac.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($left)))
$jwt = $left + "." + $signature
# eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1MTYyMzkwMjIsInN1YiI6IjEyMzQ1Njc4OTAiLCJuYW1lIjoiSm9obiBEb2UifQ.6MWZy9doHbfyomJd4soTRUQft7PmRM2EyxxT3SLoiyE
# in PowerShell 7.4; die innere Schlüsselreihenfolge der Segmente - und damit die Signatur - kann je nach Version abweichen
Schauen Sie sich das Signatur-Segment an: eine Kette aus dem base64url-Alphabet, dem, in dem Plus als Bindestrich und Slash als Unterstrich auftaucht würden. Drei Dinge an diesem Beispiel werden Sie vor Produktivunfällen retten. Erstens wird die Signatur über den exakten JSON-Text berechnet, einschließlich seiner Schlüsselreihenfolge und seiner Abstände, also müssen das JSON, das Sie signieren, und das JSON, gegen das Sie prüfen, bytefürbyte dasselbe sein. PowerShells ConvertTo-Json entscheidet die Schlüsselreihenfolge für Sie, und das ist nichts, was Sie kontrollieren, also sortieren Sie die Segmente eines Tokens nicht von Hand um und formatieren Sie sie nicht zwischen Signieren und Prüfen um. Zweitens ist -Compress keine Kosmetik: Ein Token, dessen Header oder Payload ein einziges Leerzeichen enthält, ist ein Token, das sich gegenüber einer konformen Implementierung nie verifizieren lässt, weil die Standardform kompakt ist. Drittens ist der Zeitstempel iat Sekunden seit dem Unix-Epoch, und ein Payload, gebaut aus Get-Date ohne Umwandlung, wird jahrelang außerhalb des Bereichs liegen. Die Dekodierungsrichtung, ein Blick in einen Token, den jemand anderes geprägt hat, ist im verwandten Artikel auf der Schwester-Site abgedeckt.
Dateien und der Byte-Stream
Dateien sind der häufigste Payload von allen, und die Pipeline ist kurz. Die Datei als Bytes lesen, kodieren, Text schreiben. Die beiden Zeilen, die zählen, sind das Lesen, das ein Byte-Lesen sein muss, und das Schreiben, das normalerweise keinen abschließenden Zeilenumbruch hinzufügen darf:
$bytes = [System.IO.File]::ReadAllBytes("./photo.png")
$encoded = [System.Convert]::ToBase64String($bytes)
Set-Content -Path "./photo.b64" -Value $encoded -NoNewline
$encoded.Length
# die Textgröße, die Sie gleich verschicken
Zwei praktische Anmerkungen. Die erste ist Arithmetik: Base64 macht alles größer, und für eine 10-Megabyte-Datei ist der Text, den Sie verschicken, etwa 13,4 Megabyte. Wenn der Transport ein Größenlimit hat, oder wenn dieser Text in einen E-Mail-Körper oder eine URL geht, rechnen Sie vorher, nicht erst nach dem Fehler. Die zweite ist der abschließende Zeilenumbruch: Set-Content fügt standardmäßig einen hinzu, und obwohl der Decoder, den diese Site verwendet, und die meisten modernen Decoder ihn ignorieren, tun das einige strenge Konsumenten nicht. -NoNewline kostet Sie nichts und entfernt die Frage.
PowerShell 6 und neuer bieten ein zweites Lesen an, das in der Sprache bleibt: Get-Content -AsByteStream -Raw gibt die Datei als ein einziges Byte-Array in einem Aufruf zurück, was eine saubere Alternative zum .NET-ReadAllBytes ist und für diesen Zweck identisch verhält. Auf Windows PowerShell 5.1, das kein -AsByteStream hat, ist das .NET-Lesen die einzige Option, und es ist das, das sich in jeder Version der Shell gleich verhält.
Zertifikate: Von PEM und PFX zu Text
Zertifikate sind die häufigsten Kodierungsfälle im täglichen Betrieb, weil Deployments sie gerne als Text mitführen. Ein PEM-Zertifikat ist ein umgebrochener Base64-Körper zwischen Panzerungs-Zeilen, und das Rezept aus dem Umbruch-Abschnitt ist der komplette Export:
$cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new([System.IO.File]::ReadAllBytes("./certificate.der"))
$cert.Subject
# CN=example.org
$b64 = [System.Convert]::ToBase64String($cert.RawData)
# eine lange Zeile der binären Form des Zertifikats
Das PFX-Format ist das andere Arbeitspferd: eine einzelne binäre Datei, die das Zertifikat zusammen mit seinem privaten Schlüssel hält, und deshalb ist es das Format, das Sie am häufigsten als Base64-Text in Deployment-Skripten und Konfigurations-Speichern vorfinden. Eines davon zu kodieren ist die schlichte Dateipipeline aus dem vorherigen Abschnitt, und die Leserichtung in PowerShell 7 ist eine Ein-Cmdlet-Sache:
$pfxBytes = [System.IO.File]::ReadAllBytes("./certificate.pfx")
$pfxB64 = [System.Convert]::ToBase64String($pfxBytes)
# die Textform des Pakets, bereit für eine Konfigurationsdatei
Get-PfxCertificate -FilePath "./certificate.pfx" -Password (ConvertTo-SecureString "secret" -AsPlainText -Force)
# das lebende Zertifikat, kein manuelles Dekodieren nötig
Ein Satz Sicherheit, offen gesagt, weil Base64 die gegenteilige Annahme einlädt: Ein PFX in Base64 ist ein privater Schlüssel in Text. Die Kodierung ändert die Form des Geheimnisses und nichts an seiner Geheimhaltung, also ist ein Base64-PFX, kopiert in ein Chat-Fenster, ein Ticket oder ein Commit, ein privater Schlüssel, kopiert in ein Chat-Fenster, ein Ticket oder ein Commit. Behandeln Sie die Textform mit genau derselben Sorgfalt wie die binäre Form, und bevorzugen Sie den Zertifikat-Speicher oder einen Secrets-Manager vor beidem.
Basic Auth, Data-URIs und die alten Gewohnheiten
Base64 ist älter als das Normdokument, das es benannt hat. Die MIME-Familie der RFCs aus 1996 brachte es in die E-Mail, und die HTTP-Basic-Authentifizierung brachte es in jeden Header-Austausch des frühen Webs, wo der Client das Zugangsdaten-Paar immer noch als einen einzigen Base64-String kodiert:
$credential = [System.Text.Encoding]::UTF8.GetBytes("alice:s3cret!")
[System.Convert]::ToBase64String($credential)
# YWxpY2U6czNjcmV0IQ==
# gesendet als: Authorization: Basic YWxpY2U6czNjcmV0IQ==
Das Standard-Alphabet ist hier das richtige, Plus und Slash eingeschlossen, denn ein Header ist keine URL und braucht das sichere Alphabet nicht. Derselbe Mechanismus taucht in Data-URIs auf, dem Weg, auf dem ein Dokument sein eigenes Binary inline einbettet, und die Form ist ein wörtliches Präfix plus das Standard-Base64 der Bytes:
$dataUri = "data:application/octet-stream;base64," + [System.Convert]::ToBase64String([byte[]](1, 2, 3, 250, 251))
# data:application/octet-stream;base64,AQID+vs=
Beide Gewohnheiten lohnen es sich zu kennen, weniger als Dinge, die Sie bauen werden, sondern mehr als Dinge, die Sie antreffen werden: Wenn ein Header oder ein Link eine lange Base64-Kette enthält, sind diese beiden Formate die Ersten, die Sie prüfen, und beide sind nur ein schlichtes Dekodieren von dem, was sie behaupten. Was der Punkt des Formats ist, und der Grund, warum die Dekodierungsseite dieser Site existiert.
Kodierte Kommandos und die Windows-Werkzeugkiste
PowerShell trägt seit Version 1.0 einen eingebauten Grund zum Kodieren: der -EncodedCommand-Parameter des Hosts selbst. Sie geben pwsh einen Base64-String, er decodiert die Bytes als UTF-16LE, und das Ergebnis läuft als Kommando. Der dokumentierte Zweck sind Kommandos, die mit dem Quoting der äußeren Shell kämpfen, und die Kodierungsseite sind zwei Zeilen:
$command = "Write-Host 'Hello from the encoded side'"
$encoded = [System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($command))
# VwByAGkAdABlAC0ASABvAHMAdAAgACcASABlAGwAbABvACAAZgByAG8AbQAgAHQAaABlACAAZQBuAGMAbwBkAGUAZAAgAHMAaQBkAGUAJwA=
pwsh -NoProfile -EncodedCommand $encoded
# Hello from the encoded side
Lesen Sie die Kodierungs-Zeile genau, denn sie ist die, die jeder falsch macht: der Payload muss UTF-16LE sein, das ist die Unicode-Kodierung, nicht UTF-8. Kodieren mit der falschen, und der Host decodiert Ihre Bytes trotzdem als UTF-16LE und führt ein aus Mojibake bestehendes Kommando aus, das einen Fehler produziert, der ein perfektes Porträt des Fehlers ist. Der Dekodierungsartikel deckt diesen Fehler vollständig ab, und der Fix auf dieser Seite ist ein einzelnes Wort: Unicode.
Außerhalb der Sprache tragen die nativen Werkzeuge jeweils ihre eigene stille Kodierungs-Entscheidung. Auf Windows produziert certutil -encode infile outfile.b64 eine Standard-Base64-Datei mit den Panzerungs-Zeilen, die PEM erwartet, -f überschreibt eine vorhandene Ausgabe, und das Flag, das sich zu merken lohnt, ist -unicodetext, das certutil veranlasst, die Ausgabedatei in Unicode zu schreiben (laut Microsofts Doku: "Schreibt die Ausgabedatei in Unicode") - ein einzelner Schalter, der eine Kodierungs-Entscheidung versteckt. Auf Linux ist das klassische Werkzeug base64 -w 0 file, wobei das -w 0 der tragende Teil ist: Ohne es bricht GNU base64 bei 76 Zeichen um und gibt Ihnen eine MIME-artige Datei, wenn Sie eine Zeile wollten. Auf macOS braucht die BSD-Variante kein solches Flag, weil sie standardmäßig eine ununterbrochene Zeile ausgibt.
Kodieren, wenn die Ausgabe riesig ist
Für Alltagsgrößen ist die Alles-lesen-Alles-kodieren-Pipeline die schnelle und einfache, und sie ist die richtige, bis die Datei zu groß ist, um sie bequem im Speicher zu halten, oder die Daten stückweise aus einem Download oder einem Socket ankommen. Dann ist das dokumentierte Werkzeug das Streaming-Paar: System.Security.Cryptography.ToBase64Transform, eingepackt in einen CryptoStream, wo Sie rohe Bytes hineinschreiben und Base64-Text herauskommt, und in jedem Moment nur ein kleiner Puffer aktiv ist:
$source = [System.IO.File]::OpenRead("./photo.png")
$destination = [System.IO.File]::Create("./photo.b64")
$transform = [System.Security.Cryptography.ToBase64Transform]::new()
$stream = [System.Security.Cryptography.CryptoStream]::new($destination, $transform, [System.Security.Cryptography.CryptoStreamMode]::Write)
$buffer = New-Object byte[] 65536
while (($read = $source.Read($buffer, 0, $buffer.Length)) -gt 0) {
$stream.Write($buffer, 0, $read)
}
$stream.Dispose()
$source.Dispose()
$destination.Dispose()
Ein Unterschied zur Einmal-Methode ist aufzuschreiben: Der Stream produziert eine einzige durchgehende Zeile ohne jeglichen Umbruch, egal wie groß die Eingabe ist. Ein Decoder, der Leerraum ignoriert, kümmert sich nicht darum, aber wenn das finale Ziel eine PEM-Datei ist, führen Sie nachher die 64-spaltige Schleife aus dem Umbruch-Abschnitt über das Ergebnis aus. Und in C# ist die Standardform dasselbe ToBase64Transform + CryptoStream-Muster, das Sie im C#-Artikel sehen; PowerShell steuert es direkt, wie oben.
Wo kodierte Payloads falsch laufen
- Den String kodieren, nicht die Bytes.
ToBase64String("Hello")wirft eine Umwandlungs-Ausnahme, und das ist die Methode, die Ihnen sagt, dass die erste Entscheidung, die Kodierung, nicht getroffen wurde. Machen Sie sie im Skript sichtbar, und der Fehler verschwindet. - UTF-16, wo UTF-8 versprochen wurde. Der Payload ist doppelt so lang wie erwartet, und der Konsument liest Müll. Die Kodierung ist Teil des Protokolls, und für fast jeden Draht im modernen Internet sagt das Protokoll UTF-8.
- Das 5.1-Lesen. Windows PowerShell 5.1 liest eine Textdatei ohne BOM mit der ANSI-Codepage der Maschine, bevor Ihr Skript sie überhaupt sieht, also kann eine UTF-8-Quelldatei beschädigt sein, bevor der Kodierungsschritt kommt. Auf 5.1 Text mit einem expliziten UTF-8-Lesen lesen und die ersten Zeichen des Ergebnisses prüfen.
- Die falsche Umbruch-Breite. MIME will 76, PEM will 64, APIs wollen keine, und ein strenger Konsument behandelt einen unerwarteten Zeilenumbruch als fremdes Zeichen. Wählen Sie die Breite nach dem Konsumenten aus und sagen Sie es in einem Kommentar.
- Padding auf der falschen Seite des Tauschs. Die Gleichheitszeichen zu entfernen ist für base64url-Tokens richtig und für einen Konsumenten, der Standard-Padding erwartet, falsch. Der Alphabet-Tausch und die Padding-Entscheidung sind zwei Wahlmöglichkeiten, keine einzige.
- Umsortieren dessen, was Sie signiert haben. Eine JWT-Signatur deckt den exakten JSON-Text ab, einschließlich Schlüsselreihenfolge und Abstände. Sortieren Sie die Claims um oder fügen Sie ein Leerzeichen hinzu, und der Token hört auf, sich zu verifizieren, ohne dass eine Fehlermeldung auch nur in der Nähe der Ursache wäre.
- Werte über 255. Das Umwandeln einer Ganzzahl in ein Byte wirft eine Ausnahme statt umzuwickeln, also stoppt 256 das Skript beim Cast. Wenn Ihre Quelldaten Zahlen sind, casten Sie explizit und lassen Sie einen Fehler ein Fehler sein, den Sie sehen.
- Dem Kostüm glauben. Base64 ist keine Verschlüsselung und keine Kompression: Es ist eine Übersetzung, die die Daten um ein Drittel vergrößert. Ein Geheimnis in Base64 ist ein Geheimnis im Klartext, und eine Datei in Base64 ist eine Datei, die 33% mehr Platz braucht.
Regeln für Encoder, denen Sie vertrauen können
- Erzeugen Sie die Bytes bewusst. Die erste Zeile eines jeden Kodierungs-Skripts sollte ein explizites
GetBytesoder ein Byte-Lesen sein, nie die Hoffnung, dass PowerShell einen String in die richtigen Bytes umwandelt. - Nennen Sie das Alphabet und die Breite in einem Kommentar neben dem Code, der die Wahl trifft: standard oder base64url, umgebrochen bei 76, 64 oder gar nicht. Der Konsument ist ein Mensch, der das Skript in sechs Monaten liest, und dieser Mensch sind Sie.
- Schreiben Sie die Textdatei mit
-NoNewline, es sei denn, der Konsument erwartet ausdrücklich ein abschließendes Zeilenende, und wählen Sie das Zeilenende (LF oder CRLF) so, wie es die Dokumentation des Konsumenten erwartet. - Testen Sie die Runde während des Baus: kodieren, dekodieren, die Bytes vergleichen. Ein dreißigsekündiges
Compare-Objectüber die beiden Byte-Arrays fängt Kodierungsfehler, Umbruch-Fehler und Byte-Reihenfolge-Fehler alle auf einmal, während die Ursache noch frisch ist. - Größen loggen, keine Payloads. Die Byte-Anzahl vorher und die Zeichen-Anzahl danach sollten bei einem Verhältnis von etwa 1,33 sitzen, und wenn das nicht der Fall ist, sagt Ihnen die Größen-Diskrepanz, wo Sie hinsehen, ohne dass das Log jemals die Daten enthält.
Wie PowerShell seinen Encoder geerbt hat
Die kürzeste wahre Geschichte der Base64-Kodierung in PowerShell ist, dass PowerShell nie einen geschrieben hat. Die Methode, die Sie aufrufen, Convert.ToBase64String, erschien mit .NET Framework 1.1 im Jahr 2003, und jedes PowerShell seit Version 1.0 im November 2006 hat schlicht das .NET, auf dem es läuft, ausgelegt. Das Projekt hieß während der Entwicklung Monad, wurde zum ersten Mal öffentlich auf der Professional Developers Conference im Oktober 2003 gezeigt, und zur Veröffentlichung war der .NET-Encoder, den es einpackt, bereits drei Jahre alt und trug Web-Traffic.
Das Format wurde im selben Jahr standardisiert, in dem die Shell startete. RFC 4648, veröffentlicht im Oktober 2006, legte das Alphabet, die Padding-Regeln, die Strenge des Dekodierens und die base64url-Variante fest, und es beschreibt immer noch exakt das Verhalten, das das .NET-Paar implementiert. Die MIME-RFCs, die davor kamen, 1996, hatten den 76-Zeichen-Umbruch bereits in die E-Mail gebracht, und deshalb ist diese Breite bis heute der Standard von InsertLineBreaks. Als PowerShell im August 2016 als PowerShell Core Open Source und plattformübergreifend wurde, kam der Encoder auf Linux und macOS unverändert mit, weil es nichts zu ändern gab.
Was sich später änderte, geschah in .NET, und meist außerhalb der Reichweite von PowerShell. Die Laufzeit bekam in neueren Versionen schnellere, span-basierte Base64-Helfer, einschließlich der Base64Url-Klasse und der Try-gepräfixten Dekodier-Methoden. Spans sind byref-ähnliche Typen, und ältere PowerShell-Releases konnten tatsächlich überhaupt nicht an sie binden, aber der Method-Binder des aktuellen PowerShell führt jetzt eine implizite Array-zu-Span-Umwandlung aus, also sind diese Kurzwege von einem Skript auf einem ausreichend aktuellen Host aufrufbar. Die Community-Antwort für alles Ältere ist das Microsoft.PowerShell.TextUtility-Modul aus der PowerShell Gallery, dessen ConvertTo-Base64 dieselbe .NET-Methode einpackt und einen -Text-Parameter mit UTF-8-Standard plus einen -InsertBreakLines-Schalter für den 76-spaltigen Umbruch hinzufügt. Installieren Sie es mit Install-Module -Name Microsoft.PowerShell.TextUtility, wenn Sie die Cmdlet-Form bevorzugen, und beachten Sie, dass das Modul archiviert ist und nicht mehr aktiv gepflegt wird, was ein weiterer Grund ist, warum die eingebaute Methode für neue Skripte die Empfehlung bleibt.
Die Zahlen und Namen, die bleiben
- Alle drei Eingabe-Bytes werden zu vier Ausgabe-Zeichen, also wird kodiertes Datenmaterial etwa 33% größer als das Original, und das Padding ist nie mehr als zwei Gleichheitszeichen.
- Die Standard-Ausgabe ist eine einzige ununterbrochene Zeile.
InsertLineBreaksbricht bei 76 Zeichen mit CRLF um, die MIME-Konvention aus 1996. PEM will 64, und kein eingebauter Schalter produziert diese Breite. - base64url ist Standard-Base64, bei dem Plus und Slash gegen Bindestrich und Unterstrich getauscht sind, Padding wird meist weggelassen, und es ist das Alphabet jedes JWT und API-Tokens.
- "Café" sind fünf Bytes in UTF-8 und acht in UTF-16LE. Derselbe sichtbare Text, doppelte Größe, und die beiden Kodierungen sind über den Draht hinweg nicht austauschbar.
-EncodedCommandexistiert seit der ersten PowerShell-Veröffentlichung, und sein Payload muss UTF-16LE sein, nicht UTF-8. Das einzelne Wort, das den häufigsten Fehler auf dieser Seite behebt, istUnicode.certutil -encodekann eine Kodierungs-Entscheidung in-unicodetextverstecken, und GNUbase64braucht-w 0, um Ihnen eine Zeile zu geben statt des 76-spaltigen Umbruchs.- Die span-basierten Base64-Helfer von .NET, einschließlich
Base64Url, waren zeitweise aus PowerShell unerreichbar, weil Spans byref-ähnliche Typen sind, an die der ältere Method-Binder nicht binden konnte. Aktuelles PowerShell (7.4+, auf einer .NET-Laufzeit, die neu genug ist, um die Klasse mitzuliefern) löst ein Array-Argument gegen einen Span-Parameter auf, ohne zu murren, also funktioniert der direkte Aufruf heute - aber der Zwei-Zeichen-Tausch bleibt das eine Rezept, das in jeder Version funktioniert, alt und neu gleichermaßen. - Ein einzelnes Byte, 123, wird zu
ew==kodiert: das kleinste mögliche Beispiel für die Regel, dass die Länge der Ausgabe Ihnen die Länge der Eingabe sagt.
Den Pfeil umdrehen
Alles in diesem Artikel dreht sich darum, Daten, die Sie halten, zu nehmen und sie in einen Base64-String zu verwandeln. Die Spiegeloperation, einen String zu nehmen und die eigenen Daten zurückzubekommen, hat ihre eigene Besetzung an Problemen: ein Decoder, der vier Arten von Leerraum ignoriert, eine Fehlermeldung, die drei Verbrechen abdeckt, ein JWT, in das man reinsehen kann, ein Zertifikat, das man auspackt, und ein -EncodedCommand, das man erklärt. Diese Richtung bekommt ihre eigene ausführliche Behandlung, mit ihren eigenen Fallen und ihrer eigenen Geschichte, im verwandten Artikel auf der Schwester-Site, Base64-Dekodierung in PowerShell, verlinkt unten.
Zuletzt aktualisiert: 2026-09-08
Verwandter Artikel: Base64-Dekodierung in PowerShell: Ein vollständiger Leitfaden