Decodifica Base64 in Bash: una guida completa
Ogni tanto una stringa di lettere, cifre e, di tanto in tanto, +, / o = atterra nel tuo terminale, e ti serve di nuovo la cosa vera. Un JWT incollato in un ticket, un file .b64 allegato a una email di supporto, un segreto Kubernetes che si legge come una zuppa di lettere, un PNG nascosto in un tag data: HTML. Questa è la guida da campo per riportare i byte fuori, in Bash e nella shell, usando gli strumenti che quasi certamente hai già installato.
Il formato in un fiato: il Base64 riscrive ogni tre byte di dati grezzi in quattro caratteri presi da un alfabeto di 64 lettere (A-Z, a-z, 0-9, più + e /), e aggiunge uno o due segni = alla fine quando il numero di byte non è un multiplo di tre. La decodifica è la direzione che rimpicciolisce di quello scambio: quattro caratteri dentro, tre byte fuori. La home page di questo sito percorre il formato passo dopo passo, quindi questo articolo si prende il suo tempo dove compete, sul lato shell del lavoro.
Ecco il colpo di scena: non esiste un solo comando base64. Il nome è condiviso da un programma C di GNU, da una riscrittura in Rust, da un'applet BusyBox, da un residuo BSD e da una utility OpenSSL i cui flag si sovrappongono in modo davvero pericoloso. Sono tutti d'accordo sull'alfabeto, ma non sempre su come si presenta un input rotto, ed è proprio quella differenza il posto dove gli script vanno a morire. Quindi il primo passo è scoprire chi risponde quando digiti base64.
Conosci il decodificatore con cui parli
Un comando ti racconta la quasi intera storia:
base64 --version
A seconda della macchina, hai a che fare con una di queste:
| Quello che vedi | Quello che hai | Flag di decodifica |
|---|---|---|
base64 (GNU coreutils) 9.x |
L'implementazione C classica, ancora il predefinito sulla maggior parte delle distribuzioni Linux | -d o --decode |
base64 (uutils coreutils) 0.8.x |
La riscrittura in Rust dei coreutils, l'userland predefinito nelle release correnti di Ubuntu | -d (e, insolitamente, funziona anche -D) |
| Testo di utilizzo in stile BSD, nessun flag di versione | Il base64 BSD su macOS e sui sistemi BSD, discendente dello strumento bintrans originale |
-D (in questa famiglia, la minuscola -d significa debug, non decodifica) |
BusyBox v1.x |
Il binario tutto-in-uno di Alpine Linux e dei sistemi embedded | -d |
Noterai cosa manca in quella tabella: OpenSSL. openssl base64 è un'altra bestia del tutto, e il suo flag -d significa decifrare, non decodificare. Quel singolo flag è responsabile di più file di output silenziosamente vuoti di qualsiasi altra abitudine in questo articolo, quindi lo incontreremo come si deve nella sezione dei fallback.
Se la tua distribuzione distribuisce più di una famiglia l'una accanto all'altra (le Ubuntu correnti lo fanno), un paio di comandi in più ti mostrano il quadro completo:
command -v base64
base64 --version 2>&1 | head -1
Quattro comandi in riga singola che coprono la maggior parte delle giornate
Decodifica una stringa dallo standard input. È la mossa che farai mille volte, e il printf impedisce alla shell di addobbare il tuo carico utile:
printf '%s' "SGVsbG8sIFdvcmxkIQ==" | base64 -d
Decodifica un file. Ogni implementazione seria accetta un argomento FILE, ed è il modo più pulito per tenere i dati alla larga dal meccanismo di virgolettatura della shell:
base64 -d payload.b64 > payload.bin
Decodifica da una here-string. La here-string aggiunge un a capo finale, ma ogni decodificatore tratta gli a capo come spazi bianchi ignorabili, quindi è completamente sicuro per i blob piccoli:
base64 -d <<< "SGVsbG8sIFdvcmxkIQ=="
Decodifica un blob avvolto su più righe con un heredoc. Mettere tra virgolette singole il delimitatore impedisce alla shell di interpretare qualsiasi cosa dentro:
base64 -d <<'EOF'
SGVs
bG8s
IFdv
cmxk
IQ==
EOF
Tutti e quattro stampano Hello, World!. Su macOS e sui sistemi BSD, sostituisci -d con -D in ogni esempio qui sopra; il resto della sintassi è identico.
L'input disordinato è la norma
Il Base64 che incontri nel mondo reale è raramente una riga pulita. Arriva avvolto a 76 caratteri (convenzione MIME) o a 64 caratteri (convenzione PEM), esportato da Windows con terminazioni di riga CRLF, o copiato da una finestra di chat con spazi fuori posto in mezzo. La buona notizia: al decodificatore non importa dove stanno gli a capo, purché siano veri a capo.
La cura universale per ogni stile di avvolgimento è lavare via gli a capo prima di decodificare:
tr -d '\r\n' < blob.b64 | base64 -d
I carriage return sono il caso speciale. Un a capo è un input ammesso, ma un \r non è un a capo per i decodificatori rigorosi. Un blob che ha attraversato un sistema Windows fa inciampare il decodificatore GNU, che stampa un risultato parziale e poi fallisce; la correzione è togliere prima i carriage return:
printf 'SGVs\r\nbG8s\r\n' | tr -d '\r' | base64 -d
Se vedi frammenti come Hel seguiti da un errore, stai su un blob CRLF. La stessa lavata, tr -d '\r\n', prima di decodificare è l'abitudine portabile per ogni input che non hai prodotto tu.
Per l'input davvero corrotto, GNU e uutils offrono il flag -i (--ignore-garbage), che salta i caratteri fuori dall'alfabeto e decodifica quello che può:
printf 'SGVs!bG8' | base64 -di
Quello stampa Hello. Prima di fare di -i un'abitudine predefinita, sappi perché lo standard ne sconsiglia l'uso: la sezione 3.3 di RFC 4648 dice che le implementazioni devono rifiutare i dati contenenti caratteri fuori dall'alfabeto, perché i caratteri ignorati possono essere sfruttati come canale coperto che contrabbanda dati che non appaiono mai nell'output decodificato. Ricorri a -i quando un incollamento da un documento ha portato con sé della punteggiatura, non quando stai verificando dati di cui ti fidi.
Ecco come si comportano davvero ai bordi i tre decodificatori principali, sugli strumenti del 2026 (uutils 0.8.x, GNU coreutils 9.7, BusyBox 1.37):
| Input | uutils 0.8.x | GNU 9.7 | BusyBox 1.37 |
|---|---|---|---|
SGVsbG8sIFdvcmxkIQ==, pulito |
Hello, World!, exit 0 |
Hello, World!, exit 0 |
Hello, World!, exit 0 |
SGVsbG8s, otto caratteri, senza riempimento |
Hello,, exit 0 |
Hello,, exit 0 |
Hello,, exit 0 |
Zg, due caratteri, senza riempimento |
f, exit 0 |
f, exit 0 |
errore, "truncated input" |
SGV, tre caratteri, senza riempimento |
errore, nessun output | He stampato, poi errore |
errore, "truncated input" |
| righe avvolte CRLF | decodifica senza problemi, exit 0 | output parziale, poi errore | decodifica senza problemi, exit 0 |
SGVs!bG8, punteggiatura fuori posto |
errore (con -i: Hello) |
output parziale (con -i: Hello) |
output parziale (niente flag -i del tutto) |
SGV=, bit residui non canonici |
errore, nessun output | He stampato, poi errore |
He, exit 0 |
TQ==junk, spazzatura dopo il riempimento |
exit 0, continua a decodificare junk |
exit 0, continua a decodificare junk |
M stampato, poi "truncated input" (exit 1) |
Tre punti da portare a casa da quella tabella. Primo, non esiste una regola universale per le code senza riempimento: BusyBox vuole che la lunghezza sia un multiplo di quattro, mentre GNU e uutils accettano i resti legali ma solo quando i bit residui sono tutti zero (ecco perché Zg passa e SGV no). Secondo, GNU e BusyBox scrivono i byte che hanno già decodificato prima di fallire, quindi uno script che reindirizza in un file e controlla il codice di uscita dopo si ritroverà volentieri con un file decodificato a metà. Controlla sempre lo stato di uscita, e tratta ogni file lasciato da una decodifica fallita con sospetto. Terzo, quell'ultima riga: uutils e GNU continuano a decodificare junk dopo le == e terminano con 0, perché non c'è nulla che gli dica che lo stream doveva finire; BusyBox è l'eccezione, stampa il byte prima del riempimento e poi fallisce con un errore di input troncato. Se la spazzatura finale ti riguarda, valida la forma dell'input prima di fidarti dell'output.
base64url: l'alfabeto dei token e delle URL
La sezione 5 di RFC 4648 definisce un secondo dialetto: la stessa matematica a 6 bit, ma con - e _ al posto di + e /, e con il riempimento tolto, perché un URL raramente ha bisogno di dichiarare la lunghezza esatta in byte. L'RFC è tagliente al riguardo: questa codifica "non deve essere considerata identica alla codifica base64". Se hai mai dato un'occhiata a un JWT, hai già incontrato il dialetto, perché i suoi segmenti sono base64url con il riempimento tolto.
La ricetta della shell è uno scambio in due fasi: riporta i caratteri sicuri per URL ai loro parenti standard, poi decodifica:
printf '%s' "_k-C" | tr '_-' '/+' | base64 -d | xxd -p
Quello restituisce fe4f82, tre byte grezzi che per caso portavano un abito da URL. Lo scambio è posizionale, quindi la direzione conta: la codifica va tr '+/' '-_', la decodifica va tr '_-' '/+'. Confonderli non dà errore; produce solo silenziosamente byte diversi, che è il peggior genere di bug.
Ora la trappola che cattura chi tratta base64url come Base64 semplice. Il riempimento è opzionale nel dialetto, e un segmento la cui lunghezza è tre modulo quattro è esattamente la forma che i decodificatori rigorosi ispezionano. La mossa robusta è ripristinare prima i caratteri = mancanti, ed è una piccola funzione:
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"
L'ultima riga stampa {"sub":"homer"}, un oggetto JSON senza ornamenti che un server web ha firmato o sigillato un istante prima. I segmenti la cui lunghezza è uno modulo quattro sono malformati in partenza, e nessuna quantità di riempimento li salva, quindi il rifiuto di quel caso da parte della funzione è una caratteristica.
C'è anche un percorso nativo nei coreutils GNU: basenc, il fratello maggiore di base64, capisce il dialetto direttamente:
printf '%s' "Zg==" | basenc --base64url -d
Quello stampa f, il byte che si nasconde in due caratteri. Un'avvertenza prima di costruire pipeline su di esso: il basenc GNU di quest'epoca decodifica anche input base64url senza riempimento (un semplice Zg) senza lamentarsi, mentre il basenc di uutils vuole ancora che il riempimento venga ripristinato prima, come mostra l'esempio qui sopra. La piccola funzione qui sopra funziona ovunque, ed è per questo che è la scelta portatile.
Aprire un JWT
Un JSON Web Token è composto da tre segmenti base64url uniti da punti, secondo RFC 7515. I primi due sono JSON semplice (l'intestazione e i claim del carico utile), quindi si decodificano direttamente in testo leggibile. Il terzo è la firma, un digest binario grezzo, quindi lascialo stare: decodificarlo ti dà i byte della firma, non un messaggio.
token="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0MiJ9.abc123"
b64url_decode "$(printf '%s' "$token" | cut -d. -f2)"
Quello stampa {"sub":"42"}, il claim subject, senza alcun server di mezzo. Leggi i claim con la testa chiara su cosa la decodifica è e non è: rivela, non verifica. La firma non dice nulla finché il server che detiene la chiave non la ricalcola, e quello è un lavoro per openssl dgst (l'articolo sulla codifica mostra la completa coreografia del conio), non per questo. Un errore comune sul campo è trattare un carico utile decodificato come prova che un token è valido; un attaccante può coniare a mano token non firmati o firmati debolmente, e un decodificatore li leggerà tutti con allegria.
File, byte e il muro delle variabili
La decodifica ti consegna byte grezzi. Potranno comporre una frase in inglese, oppure essere la parte centrale di un PNG, di una libreria condivisa o di un archivio zip. Il file è l'unico posto in uno script di shell dove i byte sono completamente al sicuro, quindi il flusso di lavoro predefinito è decodifica-in-file e poi confronto:
base64 -d photo.b64 > photo.png
Quando l'originale è lì accanto a te, il confronto byte per byte è l'unica prova che conta:
cmp photo.png photo.png.orig && echo "byte-for-byte identical"
Quando l'originale è lontano, confronta invece i checksum:
sha256sum expected.bin
base64 -d blob.b64 | sha256sum
Due hash corrispondenti e la tua decodifica è esatta in modo dimostrabile, il che batte qualsiasi quantità di controllo a occhio in un visualizzatore di immagini o in un dump esadecimale.
Le variabili di shell sono un'altra storia, e sono un muro per due ragioni. La sostituzione di comando $(...) toglie ogni a capo finale dall'output, e non può contenere un byte NUL in assoluto: bash stampa un'avvertenza e li scarta silenziosamente. Un carico utile di tre byte, 41 00 42, mostra entrambi i problemi in una sola dimostrazione:
printf 'QQBC' | base64 -d > out.bin
xxd out.bin
Il file contiene tutti e tre i byte (41 00 42). Il percorso della variabile no:
v=$(printf 'QQBC' | base64 -d)
printf '%s' "$v" | wc -c
Ottieni 2, con un'avvertenza sullo standard error sul byte nullo ignorato. La lezione è breve: se i dati potrebbero essere binari, decodificali in un file, ispezionalo con xxd e non farlo mai passare da una variabile.
Dove il Base64 si nasconde nel lavoro reale
Una volta che la decodifica è familiare, cominci a notare il formato dappertutto. Ecco gli angoli del lavoro reale di shell dove si presenta, ognuno con la mossa esatta.
Segreti Kubernetes. Ogni campo sotto .data in un segreto è Base64, e la documentazione ufficiale insiste sul fatto che questa è codifica, non cifratura. Leggerne uno di nuovo è un classico comando in riga singola:
kubectl get secret db-creds -o jsonpath='{.data.password}' | base64 -d; echo
Patch binarie di Git. Quando un diff tocca file binari, git diff --binary emette un blocco GIT binary patch. Non ricorere a base64 -d qui: le righe in quel blocco sono la codifica in stile base85 di git (ogni riga inizia con un carattere di lunghezza, A-Z o a-z, seguito da dati base85), non Base64, e un decodificatore semplice ci si impiglierà. Lo strumento giusto è il proprietario del formato:
git diff --binary | grep -a -A2 'GIT binary patch'
Dai il diff a git apply o git am, e lascia che facciano l'estrazione.
Data URI. Un'immagine incorporata in HTML o CSS ha un aspetto come data:image/png;base64,iVBOR.... Togli tutto fino alla virgola inclusa, lava via gli a capo, decodifica, e il file è in mano tua:
cut -d, -f2- icon.uri | tr -d '\r\n' | base64 -d > icon.png
Armatura PEM. I certificati e le chiavi private avvolgono il loro Base64 in righe di delimitazione che non sono affatto Base64. Seleziona il blocco in armatura, togli le due righe di delimitazione e decodifica al binario DER grezzo:
awk '/BEGIN CERTIFICATE/{f=1;next} /END CERTIFICATE/{f=0} f' cert.pem | tr -d '\r\n' | base64 -d > cert.der
Sostituisci CERTIFICATE con PRIVATE KEY o con qualsiasi etichetta che il tuo file porti; la forma è la stessa.
Email MIME. Qualsiasi parte di una email con Content-Transfer-Encoding: base64 è avvolta a 76 caratteri con terminazioni di riga CRLF, perché è ciò che RFC 2045 prescrive. La combinazione portabile è la lavata più la decodifica:
tr -d '\r\n' < attachment.b64 | base64 -d > attachment.bin
Incollaggi dalla clipboard. Il testo copiato da un browser, da una finestra di chat o da un documento arriva con spazi fuori posto e un a capo finale. Gli spazi non sono caratteri dell'alfabeto, quindi i decodificatori rigorosi rifiutano l'incollaggio; il salvataggio standard è toglierli prima:
tr -d ' \r\n' < pasted.b64 | base64 -d
Prima i byte: codifiche dei caratteri e Unicode
L'errore più ripetuto nel lavoro con Base64 è pensare in caratteri quando il formato conosce solo byte. base64 -d ti consegna byte grezzi, e se diventano testo leggibile è una decisione presa da ciò che li legge dopo. Quella decisione è la codifica dei caratteri, e avviene dopo la decodifica, mai dentro di essa.
L'UTF-8 è l'ipotesi predefinita e di solito quella giusta. La parola café in UTF-8 sono cinque byte, e la decodifica è un piacere:
printf 'Y2Fmw6k=' | base64 -d | xxd -p
Quello è 636166c3a9: caf più i due byte UTF-8 c3 a9 per la é. I sistemi più vecchi, però, ti consegneranno byte Latin-1 (ISO-8859-1), dove la stessa lettera è il singolo byte e9. Decodificare un blob del genere e stamparlo dritto in un terminale UTF-8 ti dà un carattere rovinato; la correzione è reinterpretare i byte con iconv prima che qualsiasi altra cosa li veda:
base64 -d latin1.b64 | iconv -f ISO-8859-1 -t UTF-8 > utf8.txt
Qualche fatto a livello di byte che fa risparmiare tempo di debug vero:
- Un BOM UTF-8 è formato dai tre byte
ef bb bf, che si codificano in77u/. Noterai il/, un carattere ostile alle URL, che è esattamente il genere di cosa per cui base64url esiste. Se un file decodificato "ha spazzatura invisibile davanti", controlla i primi tre byte conxxd. - Un'emoji come 😀 è di quattro byte UTF-8 e diventa otto caratteri Base64,
8J+YgA==. Il+lì dentro è l'alfabeto standard che funziona esattamente come progettato; negli abiti base64url diventa8J-YgA. - Le sequenze UTF-8 non valide si decodificano perfettamente come byte e poi si mostrano come spazzatura o come caratteri di sostituzione. Non è un fallimento di Base64; la decodifica ha fatto il suo lavoro.
xxdohexdump -Cti mostra cosa sono davvero i byte. - L'impostazione locale del terminale decide come renderizza quei byte. "Il terminale mostra spazzatura" è un'affermazione sulla visualizzazione, non sui dati. I byte non sono cambiati nel viaggio.
Blob grossi: flussi, divisioni e velocità
Il Base64 è un formato a flusso, e il decodificatore funziona come un vero tubo in streaming: un file da 10 GB non sta mai in memoria; si limita a passare attraverso. Questo rende il lato decodifica quasi noioso su larga scala, ed è esattamente quello che vuoi.
Quando un blob grosso è stato diviso in frammenti per il trasferimento (un limite della email, una dimensione di allegato per i ticket, un messaggio IM), il riassemblaggio è solo un cat nell'ordine giusto, seguito dalla solita lavata:
cat part_* | tr -d '\r\n' | base64 -d > big.bin
Tieni un modello mentale per le dimensioni: la forma codificata è sempre circa un terzo più grande dell'originale, quattro caratteri per tre byte. Quindi quando qualcuno ti dice che il file .b64 dovrebbe essere delle stesse dimensioni della cosa che nasconde, ha sbagliato, e ora puoi dirgli di quanto: un carico utile da 300 MB arriva come circa 400 MB di testo.
La velocità non è un problema nella pratica. Questi sono loop guidati da tabelle su memoria semplice, e su una macchina moderna un blob da 200 MB si decodifica in circa un decimo di secondo con GNU, e persino la più lenta delle implementazioni comuni (BusyBox) è solo qualche volta più lenta, sempre ben sotto i due secondi. L'uso della memoria resta piatto qualunque sia la grandezza dell'input, perché nulla viene messo in buffer.
Quando la macchina non ha base64
La maggior parte dei sistemi ha almeno uno degli strumenti sopra, e molti ne hanno diversi. Se la tua piattaforma non ha coreutils del tutto (un container spogliato, un appliance insolito), i percorsi di installazione sono questi:
| Piattaforma | Come si ottiene | Note |
|---|---|---|
| Debian / Ubuntu | preinstallato; apt install coreutils se spogliato |
Le release correnti di Ubuntu hanno predefinita la famiglia uutils (dalla 25.10), con il gemello GNU raggiungibile come gnubase64; Debian 13 distribuisce ancora GNU coreutils di default |
| RHEL / Fedora | dnf install coreutils |
preinstallato in praticamente ogni immagine |
| Alpine | apk add busybox (di solito già presente) |
busybox base64 -d; nessun flag -i |
| macOS | integrato; brew install coreutils per la versione GNU |
flag BSD: -D per decodificare, -b per la larghezza di riga; brew ti dà gbase64 |
E se nessun gestore di pacchetti è un'opzione del tutto, i fallback universali qui sotto leggono tutti dallo standard input e scrivono byte sullo standard output, quindi si inseriscono nelle stesse pipeline:
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
Su un sistema BusyBox con l'applet base64 esclusa dalla compilazione, la vecchia guardia funziona ancora: uuencode -m produce Base64 MIME, e il suo fratello lo rilegge:
busybox uudecode -o out.bin in.uu
Insidie che mangiano pomeriggi interi
Tutto qui sotto è un comportamento che incontrerai sul campo, raccolto in un unico posto:
| Insidia | Cosa succede | La correzione |
|---|---|---|
openssl base64 -d da solo |
Non fa nulla in silenzio ed esce con 0, perché lì -d significa Decifrare |
openssl base64 -d -A -a, e tratta un risultato a zero byte come un errore |
Decodificare su macOS con -d |
Il base64 BSD rifiuta il flag (o lo tratta come debug) | -D, oppure installa coreutils per gbase64 |
| Terminazioni di riga CRLF nell'input | GNU stampa un risultato parziale poi fallisce; uutils e BusyBox lo accettano | prima tr -d '\r\n', sempre per input stranieri |
| Code senza riempimento | BusyBox rifiuta qualsiasi resto; GNU e uutils accettano solo code canoniche | ripristina il riempimento = mancante prima di decodificare |
Spazzatura dopo il riempimento, ad es. TQ==junk |
uutils e GNU continuano a decodificare ed escono con 0; BusyBox dà errore | valida la forma dell'input prima di fidarti dell'output |
Bit residui non canonici, ad es. SGV= |
uutils e GNU rifiutano (GNU dopo un output parziale); BusyBox decodifica lo stesso | l'input è corrotto; rigenera la codifica a monte |
| Leggere l'output decodificato in una variabile | $(...) toglie gli a capo finali e non può contenere byte NUL in assoluto |
decodifica in un file, ispeziona con xxd |
-i su input non fidato |
la corruzione diventa un successo silenzioso; i caratteri ignorati possono trasportare dati nascosti | decodifica rigorosamente, leggi l'errore, correggi la fonte |
| Confondere i due alfabeti | decodificare base64url come standard (o viceversa) produce byte sbagliati o un errore | conosci il formato prima di decodificare; lo scambio è tr '_-' '/+' |
| Trattare un segreto decodificato come un segreto | un solo comando lo annulla; l'RFC riporta incidenti reali di credenziali che fuoriescono | cifratura vera, non codifica |
La riga OpenSSL merita un paragrafo tutto suo, perché il fallimento è così silenzioso. Su OpenSSL 3.x il -d dell'app standalone è la sua opzione generale di "decifratura", e l'elaborazione Base64 è una modalità a sé selezionata con -a. Quindi openssl base64 -d legge il tuo input, non fa nulla, non stampa nulla ed esce con 0. La decodifica funzionante è openssl base64 -d -A -a, dove -A gli dice che l'input è una riga continua. Se devi usare OpenSSL per la decodifica, tratta un output a zero byte come un errore, ogni singola volta.
Una routine stretta
Le abitudini che impediscono al Base64 di prenderti mai la meglio:
- Falli fallire a voce alta. Esegui gli script con
set -euo pipefaile controlla i codici di uscita. Tutti i decodificatori comuni escono con 1 su input sbagliato; OpenSSL è quello rumoroso che si è zittito, quindi per lui controlla anche che l'output non sia vuoto. - Dimostra l'andata e il ritorno.
cmposha256sumtra i byte attesi e quelli ripristinati è l'unica prova che conta. Non giudicare mai il binario a occhio. - Binario in file, mai in variabili. Gli a capo finali e i byte NUL sono entrambi vittime della sostituzione di comando.
- Lava l'input straniero.
tr -d ' \r\n'prima di decodificare qualsiasi cosa che abbia attraversato un confine di piattaforma. - Resta rigoroso di default. Tieni
-ispento finché non hai visto cosa fosse esattamente sbagliato; un fallimento rigoroso ti dice la posizione, uno indulgente non ti dice nulla. - Dai un nome all'alfabeto. Il Base64 standard e base64url sono codifiche diverse secondo RFC 4648. Decodifica un JWT come base64url, un allegato email come standard, e non scambiare mai caratteri senza sapere perché.
- Non stampare mai quello che hai appena decodificato. Il senso intero del formato nel mondo dei segreti è l'invisibilità; il senso intero di un log è la visibilità. Questi due obiettivi non si mescolano.
Un piccolo strato di compatibilità portatile per la domanda "quale flag vuole questa macchina":
case "$(base64 --version 2>&1 | head -2)" in
*uutils*|*GNU*|*BusyBox*) b64d () { base64 -d; } ;;
*) b64d () { base64 -D; } ;;
esac
b64d < in.b64 > out.bin
L'istruzione case cattura le famiglie GNU, uutils e BusyBox dal loro banner di versione - tutte e tre accettano il flag minuscolo - e ricade sul flag BSD per qualsiasi altra cosa, che è esattamente la divisione in quattro del mondo reale: GNU, uutils, BusyBox, BSD.
Come la shell ha imparato a decodificare
Il formato è vecchio; il comando che stai digitando non lo è. Una breve cronologia del lato shell della storia:
- 1980, Berkeley. Mary Ann Horton scrive
uuencodeeuudecodeall'Università della California, Berkeley, per far passare file binari (di solito compressi) attraverso la email. Il nome significa "codifica Unix-to-Unix", una codifica sicura per spostare file tra sistemi Unix che potrebbero non condividere una codifica dei caratteri. Per decenni questa, non il Base64, è ciò a cui gli utenti della shell ricorrono. - L'era del modem. Le prime codifiche base cavalcano lo stesso problema: uuencode su UNIX, BinHex sul TRS-80 e sull'Apple II con il Macintosh un passo indietro, ognuna che assume solo i caratteri che il proprio terminale poteva stampare.
- 1993. MIME standardizza il Base64 per la email (RFC 1521, poi RFC 2045), con l'avvolgimento di riga a 76 caratteri che ancora oggi definisce il predefinito di
base64. - 2003 e ottobre 2006. RFC 3548 mette in ordine la famiglia, e RFC 4648 la sostituisce con gli alfabeti e la regola di decodifica rigorosa che questo articolo continua a citare, base64url inclusa.
- 15 agosto 2006. coreutils 6.0 aggiunge il comando
base64stesso, il suo file NEWS lo accredita apertamente come "funzionalità di codifica e decodifica Base64 (RFC 3548)". Prima di quella data, gli utenti della shell su Linux ricorrevano aopenssl base64,uuencode -m, Perl o Python, ed è per questo che tanti vecchi script danno per scontato che OpenSSL sia l'unica opzione in circolazione. - OS X 10.7. macOS distribuisce il suo
base64, il sapore BSD con il flag-De nessun avvolgimento di riga predefinito, ed è da lì che viene la divisione tra-de-D. - Marzo 2024. coreutils 9.5 allenta il decodificatore: il riempimento non è più obbligatorio alla decodifica, e le codifiche con bit residui non nulli vengono ora diagnosticate come corruzione invece di essere accettate silenziosamente.
- 2025. La riscrittura in Rust dei coreutils (uutils) diventa il predefinito nelle release correnti di Ubuntu. Stesso nome di comando, stessi flag, un nuovo motore con le sue opinioni ai bordi, come l'accettare
-Dcome alias.
Curiosità che vale la pena tenere
- Il comando è più giovane del formato. Il Base64 è nella email dal 1993, ma il comando
base64è apparso solo nel 2006. Tredici anni di script di shell hanno fatto questo lavoro con altri strumenti, e puoi ancora trovare le loro impronte dappertutto. - Un fossile nel testo di aiuto. Il
base64di uutils descrive ancora il suo alfabeto come "RFC 3548" nel suo aiuto, il predecessore ritirato di RFC 4648, mentre il suo gemello GNU cita già lo standard attuale. Un piccolo fossile, visibile solo se leggi l'aiuto. - Il no-op silenzioso più costoso della cassetta degli attrezzi.
openssl base64 -dnon fa nulla ed esce con 0. Un'ora intera di debug, sparita, e il controllo del codice di uscita è passato. - BusyBox tiene i bit residui. Decodificherà volentieri
SGV=, i cui bit residui sono non nulli e quindi non canonici, mentre il suo cugino GNU fa una scenata sullo stesso input. Stesso RFC, nervi diversi. - Il nome del formato è vero su ogni macchina.
printf 'base64' | base64dàYmFzZTY0su GNU, uutils, BusyBox e OpenSSL allo stesso modo. È vero dal 2006 e lo sarà sempre. - Base85 non è Base64. I blocchi "binary patch" di Git assomigliano al Base64 all'occhio inesperto, ma le righe con prefisso di lunghezza sono un dialetto in stile base85 a sé. L'impostore che costa a qualcuno una deviazione di grep-e-decodifica.
- Undici caratteri, sessantaquattro bit. L'ID di un video YouTube è una stringa base64url di 11 caratteri, un numero da 64 bit in abiti da URL, ed è per questo che può apparire in un URL senza un solo segno di percentuale.
- I decodificatori non concordano su un carattere. Una singola stringa come
SGV=divide il campo in tre accampamenti, come mostra la tabella dei comportamenti sopra. Se una decodifica fallisce su un input "evidentemente valido", stai probabilmente sulla linea dei bit residui, e la codifica non era mai canonica in partenza.
Allora la prossima volta che una stringa di lettere, cifre, più e slash atterra nel tuo terminale, conosci la storia intera: quale decodificatore sta guardando, quale alfabeto sta parlando, dove si nascondono gli a capo, e esattamente come riportare i byte indietro, integri e dimostrati byte per byte, senza perdere un'ora per un carriage return. E quando il lavoro punta dall'altra parte, impacchettare il tuo binario in una busta di testo per il viaggio, l'articolo correlato sulla codifica Base64 collegato qui sotto copre quel rituale nella stessa profondità.
Ultimo aggiornamento: 2026-09-08
Articolo correlato: Codifica Base64 in Bash: una guida completa