Base64-decodering in Bash: een complete gids
Af en toe landt er in je terminal een tekenreeks van letters, cijfers en af en toe een +, / of =, en dan heb je het origineel er weer bij nodig. Een JWT dat in een ticket is geplakt, een .b64-bestand als bijlage van een supportmail, een Kubernetes-secret dat leest als alfabetsoep, een PNG die zich verstopt in een HTML-data:-tag. Dit is de veldgids om de bytes in Bash en de shell er weer uit te halen, met de tools die je bijna zeker al geïnstalleerd hebt.
Het formaat in één adem: Base64 schrijft elke drie bytes ruwe data om naar vier tekens uit een alfabet van 64 letters (A-Z, a-z, 0-9, plus + en /), en voegt aan het eind één of twee =-tekens toe als het aantal bytes geen veelvoud van drie is. Decoderen is de krimpzijde van die ruil: vier tekens gaan erin, drie bytes komen eruit. De startpagina van deze site loopt het formaat stap voor stap door, dus besteedt dit artikel zijn tijd waar die thuishoort: aan de shell-kant van het werk.
Hier komt de plotwending: er is niet één base64-commando. De naam wordt gedeeld door een C-programma van GNU, een herschrijving in Rust, een BusyBox-applet, een overblijfsel uit de BSD-wereld en een OpenSSL-werktool waarvan de flags elkaar op een écht gevaarlijke manier overlappen. Over het alfabet zijn ze het allemaal eens, maar niet altijd over wat kapotte invoer is, en dat verschil is waar scripts het bij leggen. Dus stap één: ontdek wie er antwoordt als je base64 typt.
Ken de decoder die je aanspreekt
Eén commando vertelt je het grootste deel van het verhaal:
base64 --version
Afhankelijk van de machine heb je te maken met een van deze:
| Wat je ziet | Wat je hebt | Decode-flag |
|---|---|---|
base64 (GNU coreutils) 9.x |
De klassieke C-implementatie, nog steeds de standaard op de meeste Linux-distributies | -d of --decode |
base64 (uutils coreutils) 0.8.x |
De herschrijving van coreutils in Rust, de standaard gebruikersruimte in recente Ubuntu-releases | -d (en, ongebruikelijk genoeg, werkt -D ook) |
| Gebruikstekst in BSD-stijl, geen versie-flag | De BSD-base64 op macOS en de BSD's, afstammeling van het oude bintrans-werktool |
-D (in deze familie betekent kleine letters -d debuggen, niet decoderen) |
BusyBox v1.x |
Het alles-in-één binair van Alpine Linux en ingebouwde systemen | -d |
Kijk eens wat er ontbreekt in die tabel: OpenSSL. openssl base64 is een heel ander dier, en de -d-flag betekent daar ontcijferen, niet decoderen. Die ene flag zorgt voor meer stilletjes lege uitvoerbestanden dan enige andere gewoonte in dit artikel, dus leren we het daar pas goed kennen, in de sectie met de terugvalopties.
Als je distributie meer dan één familie naast elkaar levert (zoals actuele Ubuntu), tonen nog een paar commando's je het volledige plaatje:
command -v base64
base64 --version 2>&1 | head -1
Vier one-liners die de meeste dagen dekken
Decodeer een tekenreeks uit de standaardinvoer. Dit is de zet die je duizend keer gaat maken, en printf houdt de shell er van af om je payload op te sieren:
printf '%s' "SGVsbG8sIFdvcmxkIQ==" | base64 -d
Decodeer een bestand. Elke serieuze implementatie accepteert een FILE-argument, en dat is de schoonste manier om de data ver weg te houden van het quote-apparaat van de shell:
base64 -d payload.b64 > payload.bin
Decodeer uit een here-string. Een here-string voegt een afsluitende nieuwe regel toe, maar elke decoder behandelt nieuwe regels als te negeren witruimte, dus dit is helemaal veilig voor kleine blobs:
base64 -d <<< "SGVsbG8sIFdvcmxkIQ=="
Decodeer een blob die over meerdere regels is omgebroken, met een heredoc. Als je het afbakeningswoord tussen enkele aanhalingstekens zet, interpreteert de shell niets van wat erin staat:
base64 -d <<'EOF'
SGVs
bG8s
IFdv
cmxk
IQ==
EOF
Alle vier printen Hello, World!. Op macOS en de BSD's wissel je in alle bovenstaande voorbeelden -d om naar -D; de rest van de syntax is identiek.
Rommelige invoer is de norm
Het Base64 dat je in het wild tegenkomt is zelden één nette regel. Het komt omgebroken op 76 tekens (MIME-conventie) of 64 tekens (PEM-conventie), geëxporteerd vanuit Windows met CRLF-regelafsluitingen, of gekopieerd uit een chatvenster met losse spaties in het midden. Het goede nieuws: het maakt voor de decoder niet uit waar de regeleinden zitten, zolang het maar echte nieuwe regels zijn.
De universele geneeswijze voor elke vorm van regelomwikkeling is: de regeleinden wegwassen vóór je decodeert:
tr -d '\r\n' < blob.b64 | base64 -d
Carriage returns zijn het speciale geval. Een nieuwe regel is toegestane invoer, maar een \r is voor de strenge decoders geen nieuwe regel. Een blob die een Windows-systeem is gepasseerd, brengt de GNU-decoder aan het struikelen: die geeft een deels resultaat en faalt daarna. De oplossing is de carriage returns eerst te verwijderen:
printf 'SGVs\r\nbG8s\r\n' | tr -d '\r' | base64 -d
Zie je fragmenten als Hel gevolgd door een fout, dan sta je op een CRLF-blob. Diezelfde wasbeurt, tr -d '\r\n', vóór het decoderen, is de portabele gewoonte voor elke invoer die je niet zelf hebt aangemaakt.
Voor écht beschadigde invoer bieden GNU en uutils de -i (--ignore-garbage) flag, die tekens buiten het alfabet overslaat en decodeert wat het kan:
printf 'SGVs!bG8' | base64 -di
Die print Hello. Voordat je van -i een standaardgewoonte maakt, weet dan waarom de standaard ertegen waarschuwt: RFC 4648, sectie 3.3, zegt dat implementaties data met tekens buiten het alfabet moeten afwijzen, omdat genegeerde tekens kunnen worden uitgebuit als sluipkanaal dat data smust die nooit in de gedecodeerde uitvoer verschijnt. Pak -i erbij als een geplakte tekenreeks uit een document leestekens meebracht heeft, niet als je data verifieert die je vertrouwt.
Hier is hoe de drie grote decoders zich daadwerkelijk gedragen aan de randen, met de 2026-tools (uutils 0.8.x, GNU coreutils 9.7, BusyBox 1.37):
| Invoer | uutils 0.8.x | GNU 9.7 | BusyBox 1.37 |
|---|---|---|---|
SGVsbG8sIFdvcmxkIQ==, net |
Hello, World!, exit 0 |
Hello, World!, exit 0 |
Hello, World!, exit 0 |
SGVsbG8s, acht tekens, geen padding |
Hello,, exit 0 |
Hello,, exit 0 |
Hello,, exit 0 |
Zg, twee tekens, geen padding |
f, exit 0 |
f, exit 0 |
fout, "truncated input" |
SGV, drie tekens, geen padding |
fout, geen uitvoer | He geprint, daarna fout |
fout, "truncated input" |
| Met CRLF omgebroken regels | decodeert zonder problemen, exit 0 | deels uitvoer, daarna fout | decodeert zonder problemen, exit 0 |
SGVs!bG8, losse leestekens |
fout (met -i: Hello) |
deels uitvoer (met -i: Hello) |
deels uitvoer (geen -i-flag helemaal) |
SGV=, niet-canonieke restbits |
fout, geen uitvoer | He geprint, daarna fout |
He, exit 0 |
TQ==junk, rommel na het padding |
exit 0, gaat junk door decoderen |
exit 0, gaat junk door decoderen |
M geprint, daarna de fout "truncated input" (exit 1) |
Drie lessen uit die tabel. Eerst: er is geen universele regel voor staarten zonder padding. BusyBox wil dat de lengte een veelvoud van vier is, terwijl GNU en uutils de legale resten wél accepteren, maar alleen als de overgebleven bits allemaal nul zijn (waarom Zg slaat en SGV niet). Ten tweede: GNU en BusyBox schrijven de bytes die ze al gedecodeerd hebben weg vóór ze falen, dus een script dat naar een bestand redirecteert en daarna de exitcode controleert, houdt er graag een half gedecodeerd bestand over. Controleer altijd de exitstatus, en behandel elk bestand dat een mislukte decodeering achterlaat als verdacht. En ten derde, die laatste regel: uutils en GNU gaan door met junk decoderen na de == en sluiten af met 0, omdat er niets ze vertelt dat de stream zou moeten stoppen; BusyBox is de uitzondering en print de ene byte vóór het pad en faalt daarna met de fout "truncated input". Als afsluitende rommel voor je uitmaakt, valideer dan de vorm van de invoer vóór je de uitvoer vertrouwt.
base64url: het alfabet van tokens en URLs
Sectie 5 van RFC 4648 definieert een tweede dialect: dezelfde 6-bits-math, maar met - en _ in plaats van + en /, en het padding valt weg, omdat een URL zelden de exacte bytelengte hoeft te adverteren. De RFC is er duidelijk over: deze codering "moet niet als hetzelfde beschouwd worden als de base64-codering". Als je ooit naar een JWT hebt gekeken, ben je het dialect al tegengekomen, want de segmenten ervan zijn base64url met het padding eraf.
Het shell-recept is een ruil in twee stappen: zet de URL-veilige tekens terug naar hun standaard-neefjes en decodeer daarna:
printf '%s' "_k-C" | tr '_-' '/+' | base64 -d | xxd -p
Die levert fe4f82 op: drie ruwe bytes die toevallig in URL-kledij stonden. De ruil is positiegebonden, dus de richting doet ertoe: coderen gaat tr '+/' '-_', decoderen gaat tr '_-' '/+'. Verwissel je ze en er gebeurt geen fout; er ontstaan stilletjes andere bytes, en dat is het ergste soort bug.
Nu de val die vangt wie base64url behandelt als gewoon Base64. Padding is in het dialect optioneel, en een segment waarvan de lengte drie modulo vier is, is precies de vorm waar de strenge decoders hun ogen bij open houden. De degelijke zet is de ontbrekende =-tekens eerst weer aan te vullen, en dat is een klein functionetje:
b64url_decode () {
local s=$1
local n
n=$(printf '%s' "$s" | wc -c | tr -d '[:space:]')
while [ $(( n % 4 )) -ne 0 ]; do
s="${s}="
n=$(( n + 1 ))
done
printf '%s' "$s" | tr '_-' '/+' | base64 -d
}
b64url_decode "eyJzdWIiOiJob21lciJ9"
De laatste regel print {"sub":"homer"}: een kaal JSON-object dat een webserver een moment daarvoor heeft ondertekend of verzegeld. Segmenten waarvan de lengte één modulo vier is, zijn van oorsprong al misvormd en met hoeveel padding dan ook niet te redden, dus het weigeren van dat geval door de functie is een feature.
Er is ook een native pad in GNU coreutils: basenc, de grotere broer van base64, begrijpt het dialect direct:
printf '%s' "Zg==" | basenc --base64url -d
Die print f, de ene byte die in twee tekens verstopt zat. Eén waarschuwing vóór je er pipelines op bouwt: de GNU-basenc van deze tijd decodeert zelfs base64url-invoer zonder padding (een kaal Zg) zonder morren, terwijl de uutils-basenc het padding eerst wil herstellen, zoals het voorbeeld hierboven laat zien. Het functionetje hierboven werkt overal, en daarom is het de portabele keuze.
Een JWT openen
Een JSON Web Token is drie base64url-segmenten die met stippen aan elkaar worden gezet, conform RFC 7515. De eerste twee zijn gewoon JSON (de header en de payload-claims), dus decodeert het zonder omwegen naar leesbare tekst. Het derde is de handtekening, een rauw binair digest, dus laat het maar staan: als je het decodeert, krijg je de bytes van de handtekening, geen bericht.
token="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0MiJ9.abc123"
b64url_decode "$(printf '%s' "$token" | cut -d. -f2)"
Die print {"sub":"42"}, de subject-claim, zonder enige server erbij. Lees de claims met een helder hoofd over wat decoderen wél en niet is: het onthult, het verifieert niet. De handtekening zegt niets tot de server die de sleutel bezit hem opnieuw berekent, en dat is werk voor openssl dgst (het coderingsartikel toont het volledige muntritueel), niet voor deze. Een veelgemaakte fout in het veld is een gedecodeerde payload als bewijs te beschouwen dat een token geldig is; een aanvaller kan met de hand tokens munten zonder handtekening of met een zwakke handtekening, en een decoder leest ze allemaal met plezier.
Bestanden, bytes en de muur van variabelen
Decoderen geeft je ruwe bytes. Die kunnen een Engelse zin spellen, of het midden van een PNG, een gedeelde bibliotheek of een zip-archief zijn. Een bestand is de enige plek in een shellscript waar bytes helemaal veilig zijn, dus is de standaardworkflow: decoderen naar een bestand en daarna vergelijken:
base64 -d photo.b64 > photo.png
Als het origineel naast je ligt, is een byte-voor-byte-vergelijking het enige bewijs dat ertoe doet:
cmp photo.png photo.png.orig && echo "byte-for-byte identical"
Als het origineel ver weg is, vergelijk dan checksums:
sha256sum expected.bin
base64 -d blob.b64 | sha256sum
Twee overeenkomstige hashes en je decode is aantoonbaar exact, wat elke hoeveelheid met-je-ogen-kijken in een beeldviewer of hexdump verslaat.
Shellvariabelen zijn een ander verhaal, en ze zijn een muur om twee redenen. Commandosubstitutie $(...) haalt elke afsluitende nieuwe regel uit de uitvoer en kan helemaal geen NUL-byte bevatten: bash print een waarschuwing en gooit ze stilletjes weg. Een payload van drie bytes, 41 00 42, toont beide problemen in één demo:
printf 'QQBC' | base64 -d > out.bin
xxd out.bin
Het bestand houdt alle drie de bytes (41 00 42). De variabele-route niet:
v=$(printf 'QQBC' | base64 -d)
printf '%s' "$v" | wc -c
Je krijgt 2, met een waarschuwing op de standaardfoutuitvoer over de genegeerde nul-byte. De les is kort: als de data binair kan zijn, decodeer dan naar een bestand, inspecteer het met xxd en laat het nooit door een variabele gaan.
Waar Base64 zich in echt werk verstopt
Zodra decoderen natuurlijk voelt, begin je het formaat overal te zien. Hier zijn de hoeken van echt shellwerk waar het opduikt, elk met de exacte zet.
Kubernetes-secrets. Elk veld onder .data in een secret is Base64, en de officiële documentatie benadrukt dat dit codering is, geen versleuteling. Eentje weer eruit lezen is een klassieke one-liner:
kubectl get secret db-creds -o jsonpath='{.data.password}' | base64 -d; echo
Git-binaire patches. Als een diff binaire bestanden raakt, geeft git diff --binary een GIT binary patch-blok. Pak hier geen base64 -d erbij: de regels in dat blok zijn de eigen base85-stijl-codering van git (elke regel begint met een lengte-teken, A-Z of a-z, gevolgd door base85-data), geen Base64, en een gewone decoder stikt erin. Het juiste tool is de eigenaar van het formaat:
git diff --binary | grep -a -A2 'GIT binary patch'
Geef de diff aan git apply of git am en laat ze het uitpakken doen.
Data-URIs. Een in HTML of CSS ingebed beeld heeft de vorm data:image/png;base64,iVBOR.... Hak alles weg tot en met de komma, was de regeleinden weg, decodeer, en je hebt het bestand in handen:
cut -d, -f2- icon.uri | tr -d '\r\n' | base64 -d > icon.png
PEM-omhulling. Certificaten en privésleutels wikkelen hun Base64 in omkadringsregels die helemaal geen Base64 zijn. Selecteer het omwikkelde blok, gooi de twee omkadringsregels weg, en decodeer naar de ruwe DER-binair:
awk '/BEGIN CERTIFICATE/{f=1;next} /END CERTIFICATE/{f=0} f' cert.pem | tr -d '\r\n' | base64 -d > cert.der
Vervang CERTIFICATE door PRIVATE KEY of welke label je bestand ook draagt; de vorm is dezelfde.
MIME-e-mail. Elk e-mailgedeelte met Content-Transfer-Encoding: base64 is omgebroken op 76 tekens met CRLF-regelafsluitingen, want zo schrijft RFC 2045 het voor. De portabele combinatie is de wasbeurt plus de decode:
tr -d '\r\n' < attachment.b64 | base64 -d > attachment.bin
Plakken uit het klembord. Tekst die je kopieert uit een browser, een chatvenster of een document, arriveert met losse spaties en een afsluitende nieuwe regel. Spaties zijn geen alfabettekens, dus de strenge decoders wijzen de geplakte tekst af; de standaardredding is ze eerst weg te gooien:
tr -d ' \r\n' < pasted.b64 | base64 -d
Bytes eerst: charsets en Unicode
De meest herhaalde fout in Base64-werk is denken in tekens terwijl het formaat alleen bytes kent. base64 -d geeft je ruwe bytes, en of ze leesbare tekst worden is een beslissing van wie ze daarna leest. Die beslissing is een charset, en die gebeurt na de decode, nooit daarin.
UTF-8 is de standaardveronderstelling en meestal de juiste. Het woord café is in UTF-8 vijf bytes, en de decode is een genot:
printf 'Y2Fmw6k=' | base64 -d | xxd -p
Die is 636166c3a9: caf plus de twee UTF-8-bytes c3 a9 voor de é. Oudere systemen leveren dan weer Latin-1- (ISO-8859-1) bytes, waar dezelfde letter de ene byte e9 is. Zo'n blob decoderen en rechtstreeks naar een UTF-8-terminal printen geeft een misvormd teken; de oplossing is de bytes met iconv opnieuw te interpreteren vóór er iets anders ze te zien krijgt:
base64 -d latin1.b64 | iconv -f ISO-8859-1 -t UTF-8 > utf8.txt
Enkele feiten op byte-niveau die echte debugtijd besparen:
- Een UTF-8-BOM is de drie bytes
ef bb bf, die coderen naar77u/. Kijk naar de/, een voor URLs vijandig teken: precies het soort ding waarvoor base64url bestaat. Als een gedecodeerd bestand "onzichtbare rommel voorin heeft", controleer dan de eerste drie bytes metxxd. - Een emoji als 😀 is vier UTF-8-bytes en wordt acht Base64-tekens,
8J+YgA==. De+daarin is het standaardalfabet dat precies werkt zoals ontworpen; in base64url-kledij wordt het8J-YgA. - Ongeldige UTF-8-reeksen decoderen perfect goed als bytes en worden daarna weergegeven als rommel of vervangingstekens. Dat is geen Base64-fout; de decode deed haar werk.
xxdofhexdump -Claat zien wat de bytes eigenlijk zijn. - De locale van je terminal beslist hoe die bytes weergeeft. "De terminal toont rommel" is een uitspraak over weergave, niet over data. De bytes veranderden niet onderweg.
Grote blobs: streams, splits en snelheid
Base64 is een streamformaat, en de decoder werkt als een echte streaming pipe: een bestand van 10 GB zit nooit in het geheugen; het gaat gewoon doorheen. Dat maakt de decode-kant op schaal bijna saai, en dat is precies wat je wilt.
Als een grote blob voor transport in chunks werd gesplitst (een e-maillimiet, een ticket-bijlagengrootte, een IM-bericht), is het herassembleren gewoon een cat in de juiste volgorde, gevolgd door de gebruikelijke wasbeurt:
cat part_* | tr -d '\r\n' | base64 -d > big.bin
Houd één mentaal model aan voor maten: de gecodeerde vorm is altijd ongeveer een derde groter dan het origineel, vier tekens per drie bytes. Als iemand je dus vertelt dat het .b64-bestand net zo groot zou moeten zijn als het ding dat het verbergt, heeft die het mis, en je kunt nu zeggen met hoeveel: een payload van 300 MB komt aan als zo'n 400 MB tekst.
Snelheid is in de praktijk geen probleem. Dit zijn tabelgestuurde loops over gewoon geheugen, en op een moderne machine decodeert een blob van 200 MB in ongeveer een tiende seconde met GNU, en zelfs de traagste van de gangbare implementaties (BusyBox) is slechts een paar keer trager dan dat, ruim onder de twee seconden. Het geheugengebruik blijft vlak, hoe groot de invoer ook wordt, want er wordt niets gebufferd.
Als de machine geen base64 heeft
De meeste systemen hebben minstens een van de tools hierboven, en de meeste hebben er meerdere. Als je platform helemaal geen coreutils heeft (een container met alles eruit gehaald, een ongewone appliance), zien de installatiewegen er zo uit:
| Platform | Haal het binnen | Notities |
|---|---|---|
| Debian / Ubuntu | voorgeïnstalleerd; apt install coreutils als er wat ontbreekt |
De actuele releases van Ubuntu gebruiken standaard de uutils-familie (sinds 25.10), met de GNU-tweeling bereikbaar als gnubase64; Debian 13 levert nog steeds GNU coreutils als standaard |
| RHEL / Fedora | dnf install coreutils |
voorgeïnstalleerd op feitelijk elke image |
| Alpine | apk add busybox (meestal al aanwezig) |
busybox base64 -d; geen -i-flag |
| macOS | ingebouwd; brew install coreutils voor de GNU-versie |
BSD-flags: -D om te decoderen, -b voor de regelbreedte; brew geeft je gbase64 |
En als er helemaal geen pakketbeheerder is, lezen de universele terugvalopties hieronder allemaal uit de standaardinvoer en schrijven bytes naar de standaarduitvoer, dus ze passen in dezelfde pipelines:
openssl base64 -d -A -a < in.b64 > out.bin
perl -MMIME::Base64 -0777 -ne 'print decode_base64($_)' < in.b64 > out.bin
python3 -c 'import base64, sys; sys.stdout.buffer.write(base64.b64decode(sys.stdin.buffer.read()))' < in.b64 > out.bin
Op een BusyBox-systeem waar het base64-applet uit het binaire is gecompileerd, werkt de oude bewaker nog steeds: uuencode -m produceert MIME-Base64, en zijn broer leest het weer terug:
busybox uudecode -o out.bin in.uu
Valkuilen die hele middagen opeten
Alles hieronder is gedrag dat je in het veld zult tegenkomen, op één plek verzameld:
| Valkuil | Wat er gebeurt | De oplossing |
|---|---|---|
openssl base64 -d op zichzelf |
doet stilletjes niets en sluit af met 0, want -d betekent daar ontcijferen |
openssl base64 -d -A -a, en behandel een resultaat van nul bytes als een fout |
Decoderen op macOS met -d |
De BSD-base64 wijst de flag af (of behandelt hem als debug) | -D, of installeer coreutils voor gbase64 |
| CRLF-regelafsluitingen in de invoer | GNU print een deels resultaat en faalt daarna; uutils en BusyBox accepteren het | tr -d '\r\n' eerst, altijd bij buitenlandse invoer |
| Staarten zonder padding | BusyBox wijst elke rest af; GNU en uutils accepteren alleen canonieke staarten | vul het ontbrekende =-padding aan vóór het decoderen |
Rommel na het padding, bijv. TQ==junk |
uutils en GNU blijven decoderen en sluiten af met 0; BusyBox geeft een fout | valideer de vorm van de invoer vóór je de uitvoer vertrouwt |
Niet-canonieke restbits, bijv. SGV= |
uutils en GNU wijzen af (GNU na deels uitvoer); BusyBox decodeert toch | de invoer is beschadigd; genereer de codering opnieuw stroomopwaarts |
| Gedecodeerde uitvoer in een variabele lezen | $(...) haalt afsluitende nieuwe regels weg en kan helemaal geen NUL-bytes bevatten |
decodeer naar een bestand, inspecteer met xxd |
-i op onbetrouwbare invoer |
beschadiging wordt een stille succes; genegeerde tekens kunnen verborgen data dragen | decodeer strikt, lees de fout, repareer de bron |
| De twee alfabetten verwisselen | base64url als standaard decoderen (of omgekeerd) levert verkeerde bytes of een fout op | ken het formaat vóór je decodeert; de ruil is tr '_-' '/+' |
| Een gedecodeerd secret als een secret behandelen | één commando maakt het ongedaan; de RFC registreert echte incidenten van gelekte inloggegevens | echte versleuteling, geen codering |
De OpenSSL-rij verdient een eigen alinea, omdat de fout zo stil is. Bij OpenSSL 3.x is de -d van de op zichzelf staande app zijn algemene "ontcijferen"-optie, en Base64-verwerking is een apart modus dat je selecteert met -a. Dus openssl base64 -d leest je invoer, doet niets, print niets en sluit af met 0. De werkende decode is openssl base64 -d -A -a, waarbij -A aangeeft dat de invoer één doorlopende regel is. Moet je OpenSSL voor decoderen gebruiken, behandel dan een uitvoer van nul bytes als een fout, elke keer opnieuw.
Een strakke routine
De gewoontes die ervoor zorgen dat Base64 het van je nooit wint:
- Faal hardop. Draai scripts met
set -euo pipefailen controleer exitcodes. Alle gangbare decoders sluiten af met 1 bij slechte invoer; OpenSSL is de luide die stil is geworden, dus controleer bij die ook of de uitvoer niet leeg is. - Bewijs de rondreis.
cmpofsha256sumtussen verwachte en herstelde bytes is het enige bewijs dat ertoe doet. Kijk nooit met je ogen naar binair. - Binair naar bestanden, nooit naar variabelen. Afsluitende nieuwe regels en NUL-bytes zijn allebei slachtoffers van commandosubstitutie.
- Was buitenlandse invoer.
tr -d ' \r\n'vóór je iets decodeert dat een platformgrens is gepasseerd. - Blijf standaard strikt. Houd
-iuit tot je hebt gezien wat er precies mis was; een strikte fout geeft je de locatie, een soepele geeft je niets. - Noem het alfabet. Standaard Base64 en base64url zijn verschillende coderingen conform RFC 4648. Decodeer een JWT als base64url, een e-mailbijlage als standaard, en wissel nooit tekens om zonder te weten waarom.
- Print nooit wat je zojuist hebt gedecodeerd. Het hele punt van het formaat in de secrets-wereld is onzichtbaarheid; het hele punt van een log is zichtbaarheid. Die twee doelen mixen niet.
Een klein portabel shim voor de vraag "welke flag wil deze machine":
case "$(base64 --version 2>&1 | head -2)" in
*uutils*|*GNU*|*BusyBox*) b64d () { base64 -d; } ;;
*) b64d () { base64 -D; } ;;
esac
b64d < in.b64 > out.bin
De case-statement vangt de GNU-, uutils- en BusyBox-families af via hun versiebanner - alle drie accepteren de flag met kleine letters - en valt terug op de BSD-flag voor alles anders, wat precies de vierledige indeling van de echte wereld is: GNU, uutils, BusyBox, BSD.
Hoe de shell leerde decoderen
Het formaat is oud; het commando dat je typt is dat niet. Een korte tijdlijn van de shell-kant van het verhaal:
- 1980, Berkeley. Mary Ann Horton schrijft
uuencodeenuudecodeaan de Universiteit van Californië, Berkeley, om binaire bestanden (meestal gecomprimeerde) door e-mail te smokkelen. De naam betekent "Unix-naar-Unix-codering", een veilige codering om bestanden te verplaatsen tussen Unix-systemen die geen charset delen hoeven te hebben. Decennialang is dit, niet Base64, waar shellgebruikers naar grijpen. - Het dial-up-tijdperk. De allereerste base-coderingen draaien om hetzelfde probleem: uuencode op UNIX, BinHex op de TRS-80 en de Apple II met de Macintosh een stap achteraan, elk ervan uitgaande dat alleen de tekens gelden die de eigen terminal kan afdrukken.
- 1993. MIME standardiseert Base64 voor e-mail (RFC 1521, later RFC 2045), met de regelomwikkeling op 76 tekens die nog steeds de
base64-standaard van vandaag definieert. - 2003 en oktober 2006. RFC 3548 brengt orde in de familie, en RFC 4648 vervangt hem door de alfabetten en de strikt-decoderingsregel die dit artikel blijft citeren, base64url inbegrepen.
- 15 augustus 2006. coreutils 6.0 voegt het
base64-commando zelf toe, en het NEWS-bestand vermeldt het er simpelweg als "base64-codering en -decodering (RFC 3548) functionaliteit". Vóór die datum grepen shellgebruikers op Linux naaropenssl base64,uuencode -m, Perl of Python, en daarom gaan zoveel oude scripts er vanuit dat OpenSSL de enige optie in de buurt is. - OS X 10.7. macOS levert zijn eigen
base64, de BSD-variant met de-D-flag en geen standaard regelomwikkeling, en vandaar komt de split tussen-den-D. - Maart 2024. coreutils 9.5 ontspandt de decoder: padding is bij het decoderen niet langer verplicht, en coderingen met niet-nul restbits worden nu gediagnosticeerd als beschadiging in plaats van stilletjes geaccepteerd.
- 2025. De herschrijving van coreutils in Rust (uutils) wordt de standaard in recente Ubuntu-releases. Zelfde commandonaam, zelfde flags, een nieuwe motor met eigen randmeningen, zoals
-Dals alias accepteren.
Kinkies die de bewaarsing waard zijn
- Het commando is jonger dan het formaat. Base64 zit sinds 1993 in e-mail, maar het
base64-commando verscheen pas in 2006. Dertien jaar lang deden shellscripts dit werk met andere tools, en hun vingerafdrukken vind je nog steeds overal. - Een fossiel in de helptekst. De uutils-
base64beschrijft in zijn hulp het alfabet nog steeds als "RFC 3548", de gepensioneerde voorganger van RFC 4648, terwijl zijn GNU-neef al de huidige standaard aanhaalt. Een klein fossiel, alleen zichtbaar als je de hulp leest. - De duurste stille nietsdoener in de gereedschapskist.
openssl base64 -ddoet niets en sluit af met 0. Een heel uur debuggen, weg, en de exitcode-test was geslaagd. - BusyBox houdt de restbits vast. Het decodeert
SGV=graag, waarvan de overgebleven bits niet nul zijn en dus niet canoniem, terwijl zijn GNU-neef in een razernij uitbarst over dezelfde invoer. Zelfde RFC, andere zenuwen. - De naam van het formaat is op elke machine waar.
printf 'base64' | base64geeftYmFzZTY0op GNU, uutils, BusyBox en OpenSSL, allemaal. Het is waar sinds 2006 en blijft het altijd. - Base85 is geen Base64. De "binary patch"-blokken van git lijken op Base64 voor de ongeoefende blik, maar de regels met een lengte vooraf zijn een base85-stijl-dialect op zich. De nepot die mensen een grep-en-decodeer-omweg kost.
- Elf tekens, zesenvier bits. Een YouTube-videoid is een base64url-tekenreeks van elf tekens, een 64-bits getal in URL-kledij, en daarom kan het in een URL verschijnen zonder één enkel procentteken.
- Decoders zijn het niet eens over één teken. Een enkele tekenreeks als
SGV=splitst het veld in drie kampen, zoals de gedragstabel hierboven laat zien. Als een decode faalt op "blijkbaar geldige" invoer, sta je waarschijnlijk op de restbits-lijn, en de codering was nooit canoniem om te beginnen.
Dus de volgende keer dat een tekenreeks van letters, cijfers, plus en slash in je terminal landt, ken je het hele verhaal: welke decoder kijkt toe, welk alfabet spreekt, waar de nieuwe regels zich verstoppen, en precies hoe je de bytes er weer bij krijgt, intact en byte-voor-byte bewezen, zonder een uur te verliezen aan een carriage return. En als het werk de andere kant op wijst, het inpakken van je eigen binair in een tekstomhulling voor de reis, dan dekt het gerelateerde Base64-coderingsartikel dat hieronder is gelinkt dat ritueel in dezelfde diepte.
Laatst bijgewerkt: 2026-10-06
Gerelateerd artikel: Base64-codering in Bash: een complete gids