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

Base64-codering in PowerShell: een complete gids

Je hebt een tekenreeks, een bestand, een certificaat of een token, en de andere kant van de draad wil het als een lange reeks letters en cijfers: afdrukbaar, plakbaar in een e-mail, een URL of een configbestand, zonder één enkele binaire byte die de transportlaag zou breken. Dat is Base64. Het is een vertaling, geen compressie en geen slot: drie invoerbytes worden vier uitgaande tekens, dus de tekst die je verstuurt loopt zo'n 33% groter dan waar het mee begon, met een alfabet van 64 tekens en het gelijkteken als afsluitende padding.

De startpagina van deze site behandelt het alfabet, de bitrekening en de varianten in detail. Dit artikel behandelt de encodeerrichting vanuit de PowerShell-kant: de ene .NET-methode die je gaat aanroepen, de ontbrekende stap waar iedereen in z'n eerste script over struikelt, de regelomwikkelingsconventies die per protocol verschillen, het URL-veilige alfabet, en de handvol echte klussen waar encoderen in PowerShell de voorzichtigen beloont en de slordigen straft.

De methode en de ontbrekende stap

PowerShell levert geen eigen Base64-cmdlet mee. Het werk wordt verricht door een methode die sinds .NET Framework 1.1 in 2003 deel uitmaakt van het .NET-framework, drie jaar voordat PowerShell zelf verscheen:

$bytes = [System.Text.Encoding]::UTF8.GetBytes("Hello")
[System.Convert]::ToBase64String($bytes)
# SGVsbG8=

Dat is de hele API: één byte-array erin, één tekenreeks eruit, in elke PowerShell op elk besturingssysteem, want het is simpelweg .NET. De ontbrekende stap is de eerste regel van het voorbeeld, en daar verliezen beginners hun eerste uur. De methode accepteert je tekenreeks niet. Het accepteert bytes, en de vraag "welke bytes betekent mijn tekenreeks" is een tekensetvraag die alleen jij kunt beantwoorden. Hier is het contract van de overloads die je inderdaad vanuit PowerShell kunt aanroepen:

Wat je doorgeeft Wat je krijgt
byte[] Één lange regel standaard Base64, met =-padding waar de lengte dat vereist
byte[] plus InsertLineBreaks Dezelfde data, afgebroken op 76 tekens met CRLF tussen de regels
byte[], offset, count Alleen de gevraagde slice van de array, gecodeerd
Een tekenreeks zoals "Hello" Een conversie-exceptie. PowerShell kan een tekenreeks niet op eigen kracht naar een byte-array zetten
$null Een ArgumentNullException, voor je verpakt in een MethodInvocationException

Kijk eens wat er niet in die tabel staat: er is geen overload die zegt "codeer deze tekst". Tekst encoderen in PowerShell is altijd een tweestapsproces. Jij bepaalt de tekenset, jij produceert de bytes, en pas dan komt de Base64-methode in het spel. Houd die twee beslissingen zichtbaar gescheiden in het script, want de tweede is onzichtbaar en de eerste is waar de bugs zitten.

Tekst encoderen: kies eerst de tekenset

De veilige standaard voor alles wat over het moderne internet gaat, is UTF-8. Web-API's, JSON, JWT's, alles wat een browser of een server de afgelopen decennium schreef, verwacht UTF-8-bytes onder de Base64, en het tweestapspatroon is de gewoonte die je wilt aankweken:

$text = "Hello, PowerShell!"
$bytes = [System.Text.Encoding]::UTF8.GetBytes($text)
$encoded = [System.Convert]::ToBase64String($bytes)
# SGVsbG8sIFBvd2VyU2hlbGwh

Pak je een andere tekenset, dan bedien je meestal een legacy-systeem, en is de tabel hieronder de praktische gids:

Tekenset Gebruik het als Als je de verkeerde kiest
UTF8 Web-API's, JSON, JWT's, kortom alles uit het moderne. De standaardkeuze De decoder aan de andere kant ziet mojibake in plaats van je tekst
Unicode (UTF-16LE) De consument is een Windows- of .NET-component dat .NET-tekenreeksen codeert, of -EncodedCommand Je payload is tweemaal zo lang als de consument verwacht, en vol verrassingen
ASCII Klassieke 7-bits-protocollen zoals HTTP Basic-aanmeldgegevens Alles met een waarde boven 127 wordt vervangen vóórdat er überhaupt gecodeerd wordt
Latin1 Oudere Europese systemen van vóór UTF-8 Eén byte per teken, en elk niet-Latin-1-teken wordt een vraagteken

Een nuttige debugtrick werkt in beide richtingen: de padding en de lengte van de Base64 vertellen je hoeveel bytes gecodeerd werden, en de manier waarop de gedecodeerde tekst eruitziet vertelt je uit welke twee-byte- of één-byte-wereld hij komt. Een payload die verdacht even is qua grootte en vol zit met een afwisseling van normale en leeg-uitziende tekens, is meestal UTF-16 in een UTF-8-vermomming, of andersom.

De UTF-16-verrassing

PowerShell bewaart tekenreeksen intern als UTF-16, en dat feit lekt uit naar Base64-werk op één specifiek, zeer veelvoorkomend punt: je schrijft de Base64 voor een consument die zelf een .NET- of Windows-component is, en je codeert met Unicode omdat dat wat .NET-tekenreeksen zijn. Het is een correcte intuïtie voor sommige consumenten en een grootte-verdubbelende fout voor de rest. Dezelfde vier zichtbare tekens, twee tekensets:

$same = "Café"
[System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($same))
# Q2Fmw6k=  vijf bytes
[System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($same))
# QwBhAGYA6QA=  acht bytes

Zelfde tekst, dubbele grootte, en de twee tekenreeksen zijn niet onderling uitwisselbaar: een consument die de ene verwacht en de andere ontvangt gaat niet hardop mislukken; hij leest gewoon onzin. De regel die voorkomt dat dit je beet, is de tekenset te behandelen als onderdeel van het protocol en niet als een lokaal detail. Is het ontvangende systeem een browser, een REST-API of een moderne server, dan is het UTF-8 tenzij de documentatie anders zegt. Is het de PowerShell-host zelf via -EncodedCommand, of een .NET-tekenreeks in een puur Windows-pijplijn, dan is het UTF-16LE. Zegt het protocol niets, vraag dan aan de andere kant met wat die GetString gaat aanroepen, want dat is de vraag die het echt bepaalt.

Getallen, bytes en alles wat er nog is

De methode is gedeclareerd om een byte-array te accepteren, maar de typeconversie van PowerShell is soepel over wat daarbij telt, en de randjes kennen bespaart je verrassingen:

[System.Convert]::ToBase64String([byte[]](1, 2, 3, 250, 251))
# AQID+vs=
[System.Convert]::ToBase64String([char[]]"Café")
# Q2Fm6Q==  één byte per teken, de tekenwaarde als getal
[System.Convert]::ToBase64String([int[]](72, 101, 108, 108, 111))
# SGVsbG8=
[System.Convert]::ToBase64String(123)
# ew==  een enkel getal wordt geaccepteerd waar een hele array verwacht wordt

Twee randjes in dat blok verdienen aandacht. Tekens-arrays converteren één byte per teken met de numerieke tekenwaarde, wat voor Latijnse tekst precies is wat de legacy-systemen die deze truc gebruiken verwachten, en voor alles ernaast stilletjes de verkeerde bytes oplevert. Een geheel getal groter dan 255 is het randje dat in plaats daarvan hardop faalt: de byteconversie van PowerShell weigert waarden buiten 0-255 met een exceptie, dus 256 stopt het script bij de cast in plaats van stilletjes je data te beschadigen. Is je bron getallen, maak dan de cast expliciet: [byte[]](1, 2, 3) zegt precies wat het betekent.

Geef de methode een tekenreeks en je krijgt dezelfde harde behandeling om een andere reden: er is geen manier om te weten welke bytes een tekenreeks betekent, dus geeft de conversiemotor van PowerShell het op. Geef het $null en .NET gooit een exceptie vóórdat het iets doet. Beide is correct gedrag, en beide is de reden waarom het tweestapspatroon uit de eerste sectie het enige patroon is dat de moeite waard is.

Regelomwikkeling: 76, 64 en helemaal geen

Standaard produceert de encoder één lange regel, hoeveel data je hem ook geeft. Voor een bestand van een paar kilobyte is dat prima. Voor data die door een mens zal worden gelezen, in een e-mail geplakt, of vergeleken in een source-control-diff, is een muur van acht miljoen tekens een praktisch probleem, en is de conventie om te omwikken. PowerShell en de protocollen die het bedient kennen drie breedtes, en ze zijn niet onderling uitwisselbaar:

Breedte Wie verwacht het Regelafsluiting
76 tekens MIME, e-mail en de meeste textuele transports. De standaard van InsertLineBreaks CRLF
64 tekens PEM-bestanden: certificaten, private keys en de rest van de -----BEGIN-familie Conventioneel LF
Helemaal geen API's, tokens, configbestanden, alles waar de payload machinaal wordt verwerkt Helemaal geen regel

De ingebouwde omwikkeling is een verandering van één parameter, en dat is de keuze die je wilt voor e-mailachtige payloads:

$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 tekens per regel, CRLF ertussen, precies zoals MIME verwacht

PEM is de uitzondering op de ingebouwde, omdat OpenSSL en het hele -----BEGIN-ecosysteem op 64 tekens omwikken, en geen .NET-flag die breedte produceert. De lus is kort en het is het standaardrecept:

$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")

De reden dat de breedte überhaupt uitmaakt is dat Base64-groepen van vier tekens regeleinden niet respecteren, dus een decoder kan de regeleinden helemaal negeren of ze afdwingen. De decoder die deze site gebruikt negeert ze, maar strikte consumenten, en daarvan zijn er veel in de certificaat- en e-mailwereld, behandelen een onverwacht regeleinde als een vreemd teken en wijzen de payload af. Kies je een breedte, dan sluit je een contract met de consument, en is het de moeite waard om in een commentaar in het script te benoemen met wie je dat contract sluit.

base64url: twee tekens en een padding-besluit

De plus en de schuine streep van standaard Base64 zijn in een URL alleen legaal na percent-encoding, en de padding met gelijktekens leest als een veldscheider. Daarom definieerde RFC 4648 een URL- en bestandsnaam-veilig alfabet: dezelfde 64 tekens, behalve dat plus het koppelteken wordt en de schuine streep de lage streep, en de padding wordt meestal weggegooid omdat de lengte van de data dat overbodig maakt. Elke API-token en elke JWT die je ooit hebt verwerkt staat in deze variant, die de standaard er op staat om base64url te noemen en niet gewoon base64.

De standaardencoder van PowerShell produceert het standaardalfabet, dus de conversie naar base64url is twee tekenwissels en een padding-besluit:

$bytes = [System.Text.Encoding]::UTF8.GetBytes("Париж encoded 大阪")
$standard = [System.Convert]::ToBase64String($bytes)
$standard
# 0J/QsNGA0LjQtiBlbmNvZGVkIOWkp+mYqg==  het standaardalfabet, padding inbegrepen
$url = $standard.Replace("+", "-").Replace("/", "_").TrimEnd("=")
$url
# 0J_QsNGA0LjQtiBlbmNvZGVkIOWkp-mYqg  de URL-veilige vorm, padding verwijderd

Het verwijderen van de padding is veilig in de base64url-wereld, omdat de consument uit de lengte van de tekenreeks herberekent wat de padding zou zijn geweest. Dat geldt niet overal, dus neem het besluit expliciet: gooi de padding weg voor tokens, JWT-segmenten en URL-inbedding, behoud hem voor alles wat een strikte standaard-alfabet-consument voedt, en schrijf op welke je koos. De .NET-runtime levert wel een eigen class voor dit alfabet mee, System.Buffers.Text.Base64Url (toegevoegd in .NET 9), met methoden die rond ReadOnlySpan<T>-parameters zijn gebouwd. Actueel PowerShell (7.4 en later, zodra het draait op een .NET-versie die de class meelevert) kan deze inderdaad direct aanroepen - [System.Buffers.Text.Base64Url]::EncodeToString($bytes) werkt vandaag, omdat de method-binder een arrayargument nu impliciet naar een span converteert - maar de twee-tekens-wissel is degene die je pakt zolang het script moet draaien op Windows PowerShell 5.1, een oudere PowerShell 7.x-release, of een host op een runtime vóór .NET 9, en die werkt in al die versies.

Een JWT slaan

Een JSON Web Token is de vlaggenschip-toepassing van base64url in de echte wereld, en ook een prima volledige test van de encodeerpijplijn, want een JWT is drie gecodeerde segmenten, aan elkaar gekoppeld door punten: de header, de payload en de handtekening. De eerste twee zijn compact JSON in base64url, en de derde is de binaire uitvoer van een hash over de exacte tekst van de eerste twee. Hier is een complete HS256-token, in PowerShell gebouwd van begin tot eind:

$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
#  op PowerShell 7.4; de interne sleutelvolgorde van de segmenten - en dus de handtekening - kan per versie verschillen

Kijk naar het handtekeningsegment: een reeks van het base64url-alfabet, de variant waar plus als koppelteken zou verschijnen en de schuine streep als lage streep. Drie dingen over dat voorbeeld zullen je redden van productie-incidenten. Eerst: de handtekening wordt berekend over de exacte JSON-tekst, inclusief sleutelvolgorde en spaties, dus het JSON dat je ondertekent en het JSON waar je tegen verifieert, moeten byte-van-byte hetzelfde zijn. PowerShells ConvertTo-Json beslist de sleutelvolgorde voor je, en dat is niet iets wat je beheerst, dus herordel de segmenten van een token niet handmatig en formatteer ze niet opnieuw tussen ondertekenen en controleren. Tweede: -Compress is niet cosmetisch: een token waarvan de header of de payload één enkele spatie bevat, is een token dat nooit zal verifiëren tegen een compliant implementatie, omdat de standaardvorm compact is. Derde: de tijdsmarkering iat is seconden sinds de Unix-epoch, en een payload gebouwd uit Get-Date zonder omrekenen zal jaren buiten bereik vallen. De decodeerrichting, een blik in een token dat iemand anders sloeg, staat in het gerelateerde artikel op de zustersite.

Bestanden en de byte-stroom

Bestanden zijn de meest voorkomende payload van allemaal, en de pijplijn is kort. Lees het bestand in als bytes, codeer, schrijf tekst. De twee regels die tellen zijn de lees, die een byte-lees moet zijn, en de schrijf, die meestal geen afsluitende nieuwe regel mag toevoegen:

$bytes = [System.IO.File]::ReadAllBytes("./photo.png")
$encoded = [System.Convert]::ToBase64String($bytes)
Set-Content -Path "./photo.b64" -Value $encoded -NoNewline
$encoded.Length
# de tekstgrootte die je zo gaat versturen

Twee praktische notities. De eerste is rekenkunde: Base64 maakt alles groter, en voor een bestand van 10 megabyte is de tekst die je verstuurt zo'n 13,4 megabyte. Heeft de transportlaag een grootelimiet, of gaat deze tekst in een e-maillichaam of een URL, dan doe je de berekening vóórdat je encodeert, niet ná de fout. De tweede is de afsluitende nieuwe regel: Set-Content voegt er standaard één toe, en hoewel de decoder die deze site gebruikt en de meeste moderne decoders die negeren, doen sommige strikte consumenten dat niet. -NoNewline kost je niets en haalt de vraag weg.

PowerShell 6 en nieuwer bieden een tweede lees die in de taal blijft: Get-Content -AsByteStream -Raw geeft het bestand in één aanroep terug als één byte-array, een net alternatief voor de .NET ReadAllBytes, en gedraagt zich voor dit doel identiek. Op Windows PowerShell 5.1, dat geen -AsByteStream heeft, is de .NET-lees de enige optie, en die gedraagt zich hetzelfde in elke versie van de shell.

Certificaten: van PEM en PFX naar tekst

Certificaten zijn de zwaarste encodeerburgers in de dagelijkse operatie, omdat deployments er van houden om ze als tekst mee te dragen. Een PEM-certificaat is een omgewikkeld Base64-lichaam tussen pantserregels, en het recept uit de omwikkelingssectie is de hele 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)
# één lange regel met de binaire vorm van het certificaat

Het PFX-formaat is het andere werkpaard: één binair bestand dat het certificaat samen met de private key draagt, en daarom is het het formaat dat je het vaakst aantreft als Base64-tekst in deployment-scripts en configuratie-opslag. Een ervan encoderen is de gewone bestandspijplijn uit de vorige sectie, en de leesrichting in PowerShell 7 is een kwestie van één cmdlet:

$pfxBytes = [System.IO.File]::ReadAllBytes("./certificate.pfx")
$pfxB64 = [System.Convert]::ToBase64String($pfxBytes)
# de tekstvorm van de bundel, klaar voor een configbestand
Get-PfxCertificate -FilePath "./certificate.pfx" -Password (ConvertTo-SecureString "secret" -AsPlainText -Force)
# het live certificaat, geen handmatige decode nodig

Eén zin over veiligheid, openlijk gezegd omdat Base64 de tegenovergestelde aanname uitlokt: een PFX in Base64 is een private key in tekst. De codering verandert de vorm van het geheim en niets over de geheimhouding ervan, dus een Base64-PFX die in een chatvenster, een ticket of een commit is geplakt, is een private key die in een chatvenster, een ticket of een commit is geplakt. Behandel de tekstvorm met precies dezelfde zorg als de binaire vorm krijgt, en kies voor de certificaatopslag of een secrets manager boven beide.

Basic Auth, data-URIs en de oude gewoonten

Base64 is ouder dan het standaarddocument dat het zijn naam gaf. De MIME-familie RFC's uit 1996 zette het in de e-mail, en HTTP Basic-authenticatie zette het in elke header-ruil op het vroege web, waar de client het credentials-paar nog steeds codeert als één Base64-tekenreeks:

$credential = [System.Text.Encoding]::UTF8.GetBytes("alice:s3cret!")
[System.Convert]::ToBase64String($credential)
# YWxpY2U6czNjcmV0IQ==
# verstuurd als: Authorization: Basic YWxpY2U6czNjcmV0IQ==

Het standaardalfabet is hier het juiste, plus en schuine streep inbegrepen, want een header is geen URL en heeft het veilige alfabet niet nodig. Het zelfde mechanisme verschijnt in data-URIs, de manier waarop een document zijn eigen binair inline embedt, en de vorm is een letterlijke prefix plus de standaard-Base64 van de bytes:

$dataUri = "data:application/octet-stream;base64," + [System.Convert]::ToBase64String([byte[]](1, 2, 3, 250, 251))
# data:application/octet-stream;base64,AQID+vs=

Beide gewoonten zijn het weten waard minder als dingen die je gaat bouwen, dan als dingen die je zult tegenkomen: bevat een header of een link een lange Base64-reeks, dan zijn deze twee formaten de eerste om te checken, en met één simpele decode heb je in handen wat ze zeggen. Wat de bedoeling van het formaat is, en de reden dat de decoderezijde van deze site bestaat.

Gecodeerde commando's en de Windows-toolbox

PowerShell draagt sinds versie 1.0 een ingebouwde reden om te encoderen: de -EncodedCommand-parameter van de host zelf. Je geeft pwsh een Base64-tekenreeks, die decodeert de bytes als UTF-16LE, en het resultaat wordt als commando uitgevoerd. Het gedocumenteerde doel is commando's die vechten met het quotegedrag van de buitenste shell, en de encodeerkant is twee regels:

$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

Lees de encodeerregel zorgvuldig, want dat is de regel die iedereen fout doet: de payload moet UTF-16LE zijn, dat is de Unicode-tekenset, geen UTF-8. Codeer met de verkeerde en de host decodeert je bytes toch als UTF-16LE en voert een commando van mojibake uit, en levert zo een fout op die een perfect portret is van de fout. Het decodeer-artikel behandelt die mislukking volledig, en de oplossing aan deze kant is één woord: Unicode.

Buiten de taal zit in elke native tool een eigen stilsjes encodeerbeslissing. Op Windows produceert certutil -encode infile outfile.b64 een standaard-Base64-bestand met de pantserregels die PEM verwacht, -f overschrijft een bestaande uitvoer, en de flag die de moeite waard is om te onthouden is -unicodetext, die certutil het uitvoerbestand in Unicode laat schrijven (volgens Microsofts documentatie: "Schrijf het uitvoerbestand in Unicode") - één switch die een encodeerbeslissing verbergt. Op Linux is de klassieke utility base64 -w 0 file, waarbij -w 0 het dragende deel is: zonder die wikkelt GNU base64 af op 76 tekens en geeft je een MIME-achtig bestand terwijl je één regel wilde. Op macOS heeft de BSD-flavour geen dergelijke flag nodig, want die geeft standaard één ononderbroken regel uit.

Encoderen als de uitvoer enorm is

Voor alledaagse maten is de lees-alle-codeer-alle-pijplijn de snelle en simpele, en de juiste tot het bestand te groot is om comfortabel in het geheugen te houden, of de data stukje bij beetje aankomt uit een download of een socket. Dan is het gedocumenteerde instrument het stromende paar: System.Security.Cryptography.ToBase64Transform verpakt in een CryptoStream, waar je ruwe bytes in schrijft en Base64-tekst uitkomt, en op elk moment slechts een klein buffer actief is:

$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()

Eén verschil met de one-shot-methode is de moeite waard om op te schrijven: de stroom produceert één aaneengesloten regel zonder enige omwikkeling, hoe groot de invoer ook is. Een decoder die witruimte negeert maakt het zich niet, maar is de eindbestemming een PEM-bestand, dan draai je daarna de 64-koloms-lus uit de omwikkelingssectie over het resultaat. En in C# is de standaardvorm hetzelfde ToBase64Transform + CryptoStream-patroon dat je in het C#-artikel ziet; PowerShell stuurt het direct aan, zoals hierboven.

Waar gecodeerde payloads het verpesten

  • De tekenreeks coderen, niet de bytes. ToBase64String("Hello") gooit een conversie-exceptie, en dat is de methode die je vertelt dat de eerste beslissing, de tekenset, nog niet is genomen. Maak die zichtbaar in het script en de fout verdwijnt.
  • UTF-16 waar UTF-8 beloofd was. De payload is tweemaal zo lang als verwacht en de consument leest onzin. De tekenset is onderdeel van het protocol, en voor vrijwel elke draad op het moderne internet zegt het protocol UTF-8.
  • De 5.1-lees. Windows PowerShell 5.1 leest een tekstbestand zonder BOM met de ANSI-codepagina van de machine, vóórdat je script het ooit ziet, dus een UTF-8-bronbestand kan beschadigd raken vóór de encodeerstap. Op 5.1 lees je tekst met een expliciete UTF-8-lees en controleer je de eerste tekens van het resultaat.
  • De verkeerde omwikkelbreedte. MIME wil 76, PEM wil 64, API's willen geen, en een strikte consument behandelt een onverwacht regeleinde als een vreemd teken. Kies de breedte uit de consument en zeg dat in een commentaar.
  • Padding aan de verkeerde kant van de wissel. Het weggooien van de gelijktekens is correct voor base64url-tokens en verkeerd voor een consument die standaard-padding verwacht. Het alfabet wisselen en het padding-besluit zijn twee keuzes, niet één.
  • Wat je ondertekende, opnieuw formatteren. Een JWT-handtekening dekt de exacte JSON-tekst, inclusief sleutelvolgorde en spaties. Herorden je claims of voeg een spatie toe, en het token stopt met verifiëren, zonder dat een foutmelding ergens in de buurt van de oorzaak staat.
  • Waarden boven 255. Een geheel getal naar een byte converteren gooit een exceptie in plaats van te wrappen, dus 256 stopt het script bij de cast. Is je brondata getallen, cast dan expliciet en laat een fout een fout zijn die je ziet.
  • In de vermomming geloven. Base64 is geen versleuteling en geen compressie: het is een vertaling die de data met een derde laat groeien. Een geheim in Base64 is een geheim in platte tekst, en een bestand in Base64 is een bestand dat 33% meer ruimte nodig heeft.

Regels voor encoders waar je op kunt vertrouwen

  • Produceer de bytes met opzet. De eerste regel van elk encodeerscript moet een expliciete GetBytes of een byte-lees zijn, nooit de hoop dat PowerShell een tekenreeks naar de juiste bytes zal converteren.
  • Noem het alfabet en de breedte in een commentaar naast de code die de keuze maakt: standaard of base64url, omgewikkeld op 76, 64 of helemaal niet. De consument is een mens die het script over zes maanden leest, en die mens ben je.
  • Schrijf het tekstbestand met -NoNewline, tenzij de consument expliciet een afsluitende regeleinde verwacht, en kies de regelafsluiting (LF of CRLF) zoals de documentatie van de consument dat verwacht.
  • Test de rondreis tijdens het bouwen: encoderen, decoderen, bytes vergelijken. Een Compare-Object van dertig seconden over de twee byte-arrays vangt encodeerfouten, omwikkel-fouten en bytevolgordefouten allemaal in één keer, zolang de oorzaak nog vers is.
  • Log groottes, niet payloads. Het aantal bytes ervoor en het aantal tekens erna moeten in een verhouding van ongeveer 1,33 zitten, en als ze dat niet doen, zegt het grootteverschil je waar je moet kijken, zonder dat het log ooit de data bevat.

Hoe PowerShell zijn encoder erfde

De kortste waarheid over de geschiedenis van Base64-encoderen in PowerShell is dat PowerShell er nooit zelf één schreef. De methode die je aanroept, Convert.ToBase64String, verscheen met .NET Framework 1.1 in 2003, en elke PowerShell sinds versie 1.0 in november 2006 heeft simpelweg het .NET blootgesteld waarop het draait. Het project heette Monad terwijl het werd gebouwd, voor het eerst publiek getoond op de Professional Developers Conference in oktober 2003, en tegen de release was de .NET-encoder die het verpakt al drie jaar oud en druk bezig met webverkeer.

Het formaat werd gestandaardiseerd in hetzelfde jaar dat de shell verscheen. RFC 4648, gepubliceerd in oktober 2006, legde het alfabet, de paddingregels, de striktheid van decoderen en de base64url-variant vast, en het beschrijft nog steeds exact het gedrag dat het .NET-paar implementeert. De MIME-RFC's die er vóór kwamen, in 1996, hadden de 76-tekens-omwikkeling al in de e-mail gezet, en daarom is die breedte tot op de dag van vandaag de standaard van InsertLineBreaks. Toen PowerShell in augustus 2016 open source en cross-platform werd als PowerShell Core, kwam de encoder ongewijzigd mee naar Linux en macOS, omdat er niets te veranderen was.

Wat er later wél veranderde, gebeurde in .NET, en grotendeels buiten het bereik van PowerShell. De runtime kreeg in recente versies snellere, span-gebaseerde Base64-hulpmiddelen, waaronder de Base64Url-class en de decodeermethoden met Try-prefix. Spans zijn byref-achtige typen, en oudere PowerShell-releases konden er echt helemaal niet aan binden, maar de method-binder van actueel PowerShell voert nu een impliciete array-naar-span-conversie uit, zodat deze afkortingen vanuit een script aanroepbaar zijn op een recent genoeg host. Het community-antwoord voor alles oudere is het Microsoft.PowerShell.TextUtility-module uit de PowerShell Gallery, waarvan ConvertTo-Base64 dezelfde .NET-methode verpakt en een -Text-parameter toevoegt met een UTF-8-standaard en een -InsertBreakLines-switch voor de 76-koloms-omwikkeling. Installeer hem met Install-Module -Name Microsoft.PowerShell.TextUtility als je de cmdlet-vorm prefereert, en merk op dat het module gearchiveerd is en niet meer actief onderhouden wordt, wat nog een reden meer is waarom de ingebouwde methode de aanbeveling blijft voor nieuwe scripts.

De getallen en namen die je moet onthouden

  • Elke drie invoerbytes worden vier uitgaande tekens, dus gecodeerde data loopt zo'n 33% groter dan het origineel, en de padding is nooit meer dan twee gelijktekens.
  • De standaarduitvoer is één ononderbroken regel. InsertLineBreaks wikkelt af op 76 tekens met CRLF, de MIME-conventie uit 1996. PEM wil 64, en geen ingebouwde flag produceert die breedte.
  • base64url is standaard Base64 met plus en schuine streep omgewisseld naar koppelteken en lage streep, de padding meestal weggegooid, en het is het alfabet van elke JWT en elke API-token.
  • "Café" is vijf bytes in UTF-8 en acht in UTF-16LE. Dezelfde zichtbare tekst, dubbele grootte, en de twee tekensets zijn over de draad niet onderling uitwisselbaar.
  • -EncodedCommand bestaat sinds de eerste PowerShell-release, en de payload moet UTF-16LE zijn, geen UTF-8. Het enkele woord dat de meest voorkomende fout aan deze kant oplost, is Unicode.
  • certutil -encode kan een encodeerbeslissing verstoppen in -unicodetext, en GNU base64 heeft -w 0 nodig om je één regel te geven in plaats van de 76-koloms-omwikkeling.
  • De span-gebaseerde Base64-hulpmiddelen van .NET, waaronder Base64Url, waren ooit onbereikbaar vanuit PowerShell, omdat spans byref-achtige typen zijn waaraan de oudere method-binder niet kon binden. Actueel PowerShell (7.4 of later, op een .NET-runtime die nieuw genoeg is om de class mee te leveren) lost een arrayargument op tegen een span-parameter zonder klacht, dus de directe aanroep werkt vandaag - maar de twee-tekens-wissel blijft het ene recept dat in elke versie werkt, oud en nieuw.
  • Eén enkele byte, 123, codeert als ew==: het kleinste mogelijke voorbeeld van de regel dat de lengte van de uitvoer je de lengte van de invoer vertelt.

De pijl omkeren

Alles in dit artikel gaat over je data pakken en omzetten naar een Base64-tekenreeks. De spiegelbeweging, een tekenreeks pakken en je data terugkrijgen, heeft zijn eigen cast aan problemen: een decoder die vier soorten witruimte negeert, één foutmelding die drie misdaden dekt, een JWT waarin je kunt kijken, een certificaat dat je kunt ontrollen, en een -EncodedCommand die je kunt uitleggen. Die richting krijgt zijn eigen volledige behandeling, met zijn eigen valkuilen en zijn eigen geschiedenis, in het gerelateerde artikel op de zustersite, Base64 decoderen in PowerShell, dat hieronder gelinkt staat.

Laatst bijgewerkt: 2026-10-06

Gerelateerd artikel: Base64-decodering in PowerShell: een complete gids