Decodifica Base64 in Perl: una guida completa
Ti è finita in mano una stringa di lettere, cifre e l'occasionale + o /, e nel profondo sai che non è quello che sembra. Magari è un token in viaggio in un header Authorization, un file .b64 scavato fuori da un ticket di assistenza, un certificato con la sua armatura -----BEGIN, oppure un blob parcheggiato in silenzio in un file di configurazione. Apri un terminale, digiti perl, e una domanda prende il sopravvento su tutto il resto: come faccio a riavere i dati veri?
La risposta è piccola e rassicurante. Perl porta un modulo Base64 dentro il linguaggio stesso dal 2002, e una singola chiamata di funzione, decode_base64, fa l'intero lavoro: niente da installare, niente da configurare. Un rapido richiamo mentre il caffè si prepara: Base64 riscrive ogni tre byte di dati come quattro caratteri presi da un alfabeto di 64 simboli, aggiungendo uno o due segni = in coda così il risultato atterra sempre su un multiplo di quattro, ed è per questo che la forma codificata finisce tipicamente circa il 33 percento più grande di quella da cui sei partito. La home page di questo sito spiega il formato in tutti i dettagli, quindi questa guida passa tutto il suo tempo dalla parte Perl della barricata: le regole del decoder, i dialetti e i formati del mondo reale che incontrerai davvero.
La cassetta degli attrezzi: cinque funzioni, zero installazioni
Ogni chiamata di cui hai bisogno vive in MIME::Base64, che fa parte della distribuzione core di Perl dal 5.8, quindi è presente su ogni installazione seria, da quella incorporata nel firmware di un router a quella su un server di database. Il controllo è una riga sola:
perl -MMIME::Base64 -e 'print $MIME::Base64::VERSION, "\n"'
# 3.16_01
Ecco il lato decodifica del modulo, tutto intero:
| Funzione | Cosa fa | Note |
|---|---|---|
decode_base64($str) |
la stella di questo articolo: trasforma un blob Base64 in byte grezzi | ignora in silenzio ogni carattere fuori dall'alfabeto, per sempre |
MIME::Base64::decode($str) |
lo stesso decoder, chiamato senza import | la forma che incontrerai in abbondanza negli script più vecchi |
decode_base64url($str) |
decodifica il dialetto URL-safe con - e _, con o senza riempimento |
aggiunto nella 3.11 nel 2010; quello che legge i JWT |
MIME::Base64::decoded_base64_length($str) |
ti dice quanto saranno grandi i dati decodificati, senza decodificare | non esportato di default, comodo per dimensionare in anticipo i buffer |
unpack("u", $data) |
decodifica i dati uuencoded, il formato che precede Base64 | integrato in Perl stesso, nessun modulo richiesto |
La mappa delle versioni di quelle funzioni, per chi mantiene una flotta di vecchie macchine:
| Funzionalità | Disponibile da |
|---|---|
decode_base64() con il percorso veloce in C |
Perl 5.8 nel 2002, quando il modulo è entrato nel core |
decoded_base64_length() |
modulo 3.10 nel 2010 |
decode_base64url() |
modulo 3.11 nel 2010 |
| Decodifica silenziosa, nessun avviso su input sospetto | modulo 3.11 nel 2010 |
| La corrente linea 3.16 | 2020, richiede Perl 5.6 o più recente |
Se il Perl di sistema non ha il modulo per qualche motivo, e non dovrebbe succedere, la correzione è una di due righe: il pacchetto di distro libmime-base64-perl su Debian e Ubuntu, oppure cpanm MIME::Base64 per prendere il rilascio corrente da CPAN, dove il modulo vive come pacchetto dual-life fin dai tempi del core. Per la rara macchina senza compilatore C, l'equivalente in puro Perl MIME::Base64::Perl su CPAN fornisce la stessa interfaccia di base, un po' più lento ma più che sufficiente per tutto tranne il lavoro in quantità. Questa è l'intera storia delle dipendenze: nient'altro.
Un decoder che non dice mai di no
Il contratto è lungo una riga. Gli passi una stringa, e ti restituisce i byte decodificati come una stringa Perl qualunque che contiene ottetti grezzi. Nessun oggetto, nessuna eccezione, nessun flag. La documentazione mette le due regole che ne definiscono la personalità in una singola frase: ogni carattere che non fa parte del sottoinsieme Base64 di 65 caratteri è ignorato in silenzio, e nessun carattere che compare dopo un carattere di riempimento = viene mai decodificato. Questa cortesia è la cosa più importante di questo articolo, quindi lasciala lavorare per te una volta:
use MIME::Base64 qw(decode_base64);
print decode_base64("TWFu!"), "\n"; # Man - l'esclamazione svanisce senza lasciare traccia
print decode_base64("TWFu=XX"), "\n"; # Man - tutto dopo = viene saltato
print decode_base64("TQ"), "\n"; # M - nessun avviso, niente da dire
print decode_base64("T"), "\n"; # la stringa vuota, e ancora nessuna lamentela
Le ultime due righe sono la tolleranza portata all'estremo. TQ contiene un byte intero più quattro bit in eccesso, e il decoder semplicemente tiene il byte e butta via il resto. T non contiene nemmeno un byte intero, quindi il risultato è vuoto. Non c'è una modalità rigorosa nel modulo, né un validatore che ripristini la pignoleria di una volta: dalla versione 3.11 del 2010, decode_base64 non avvisa nemmeno su un input troncato, e le versioni più vecchie emettevano un avviso Premature end of base64 data sotto -w. Se il blob è sbagliato, viene decodificato lo stesso, e questo fa di te la porta di controllo qualità.
Ecco la politica della tolleranza raccolta in un unico posto, così puoi vederla tutta d'un colpo d'occhio:
| Input | Risultato | Perché |
|---|---|---|
"TWFu" |
Man |
input pulito, la strada maestra |
"TWFu!" |
Man |
l'esclamazione non è nell'alfabeto, quindi viene saltata |
"TWFu=XX" |
Man |
niente dopo il riempimento viene mai decodificato |
"TWFuIFdvcmxkIQ==" |
Man World! |
gli spazi bianchi sono gratis, ovunque |
"TQ" |
M |
un byte intero ci sta, i bit in eccesso vengono buttati via in silenzio |
"T" |
la stringa vuota | non c'è nemmeno un byte intero, e nemmeno un avviso |
"ab-cd_efgh" |
byte sbagliati in silenzio | le lettere URL-safe vengono buttate via come rumore, la trappola classica |
Quella riga finale è quella da ricordare. Un segmento base64url passato al decoder standard non fallisce: si decodifica in spazzatura dall'aspetto plausibile, perché i caratteri - e _ vengono trattati come rumore forestiero mentre le lettere rimanenti formano ancora gruppi validi. Il decoder è un testimone, non un portiere, quindi se l'input non è di fiducia, lo validi tu. Basta un piccolo controllo rigoroso:
sub strict_base64 {
my ($blob) = @_;
$blob =~ s/[\r\n]//g; # il decoder li ignora, e facciamo lo stesso noi
return 0 unless length($blob) % 4 == 0;
return $blob =~ /\A[0-9A-Za-z+\/]+(?:={1,2})?\z/ ? 1 : 0;
}
print strict_base64("TWFu"), "\n"; # 1
print strict_base64("TQ="), "\n"; # 0 - numero di riempimento errato
print strict_base64("ab-cd"), "\n"; # 0 - alfabeto URL-safe
Una parola su quell'espressione regolare, tratta da una lezione pagata cara: se una sub finisce con un return $x =~ /.../ nudo e il risultato di un match fallito viene passato direttamente a printf, Perl solleva un avviso Missing argument in printf fuorviante invece di un pulito zero. Coerisci il match con ? 1 : 0 prima di restituire, come fa la funzione sopra, e la trappola sparisce del tutto.
Prima i byte, poi i caratteri
Non dimenticare cosa restituisce decode_base64: byte grezzi, una stringa qualunque senza il flag UTF-8 impostato. Cosa significano quei byte è una decisione che solo tu puoi prendere, ed è il passo in cui l'Unicode fa inciampare la gente. Perl tiene traccia del fatto che una stringa contenga caratteri o byte, e length(), substr() e la maggior parte delle espressioni regolari si comportano in modo diverso a seconda della risposta. La correzione è dichiarare la codifica con intenzione, con il modulo Encode che si trova in ogni installazione di Perl:
use MIME::Base64 qw(decode_base64);
use Encode qw(decode);
my $raw = decode_base64("SMOrbGxvIFdvcmxkIQ==");
my $text = decode("UTF-8", $raw);
print $text, "\n"; # Hëllo World!
print length($text), " chars\n"; # 12
print length($raw), " bytes\n"; # 13
Quella coppia di numeri è tutta la lezione. Il blob è di 13 byte ma solo 12 caratteri, perché la lettera accentata occupa due byte in UTF-8. Salta il passo del set di caratteri e i byte verranno comunque stampati senza problemi su un terminale UTF-8, ed è esattamente per questo che l'errore resta nascosto finché una funzione di stringa non li conta, o i byte non attraversano un canale che si aspetta caratteri. In caso di dubbio, decodifica con un set di caratteri rigoroso e lascia che l'eccezione ti dica la verità sui byte: passa Encode::FB_CROAK e decode() muore sulle sequenze non valide invece della silenziosa sostituzione con U+FFFD del comportamento predefinito, il che è una bella cosa.
La lista corta dei set di caratteri che userai davvero:
| Set di caratteri | Quando usarlo | A cui stare attenti |
|---|---|---|
UTF-8 |
il presupposto di default: API, JSON, contenuto web, testo moderno | le sequenze non valide diventano U+FFFD di default; con Encode::FB_CROAK muoiono pulitamente, ed è esattamente quello che vuoi |
Latin-1 |
testo occidentale legacy, un byte per carattere, non può mai fallire | trasformerà volentieri l'UTF-8 in mojibake doppiamente codificato |
ASCII |
dati di cui sei certo che siano testo 7-bit semplice | ogni byte sopra 127 diventa U+FFFD di default (muore sotto Encode::FB_CROAK) |
UTF-16 |
testo Windows, dove la byte order mark decide l'endianness | il BOM è l'unico indizio di endianness, quindi tienilo nei byte |
Ed ecco la trappola da cui il passo del set di caratteri ti protegge. Se i byte che hai decodificato sono già UTF-8 e li fai passare attraverso encode("UTF-8", ...) in uscita, non ottieni una copia: ottieni una doppia codifica, dove ogni carattere accentato si gonfia in due caratteri suoi. Il sintomo classico è un testo che prima si leggeva Hëllo e ora si legge Hëllo, e chi riceve dall'altra parte del filo lo decoderà fedelmente. Byte in, byte fuori, una sola conversione nominata in mezzo.
base64url: l'alfabeto per URL e token
Metà del Base64 che attraversa l'internet moderno non è affatto l'alfabeto standard. Il carattere + è il modo in cui un browser codifica uno spazio in una query string, e / è un separatore di percorso, quindi le lettere standard sono un disastro negli URL. RFC 4648, sezione 5, definisce la correzione: un secondo alfabeto che sostituisce + e / con - e _, e che per convenzione elimina anche il riempimento = e gli a capo. La RFC è esplicita nel dire che questa codifica non va considerata la stessa cosa della codifica base64, e Perl ha una coppia dedicata dal 2010, versione 3.11:
use MIME::Base64 qw(decode_base64url);
my $raw = decode_base64url("c3Vuc2V0LTQy");
print $raw, "\n"; # sunset-42
Due cose da sapere. Prima, decode_base64url va d'accordo con l'input senza riempimento, che è la forma che incontrerai davvero in giro, quindi il rituale del ripristinare prima il riempimento che richiedono altri linguaggi non si applica qui; l'input con riempimento funziona lo stesso. Secondo, il decoder standard è un'altra bestia: dagli un segmento base64url e otterrai byte sbagliati in silenzio, perché i caratteri - e _ vengono buttati via come rumore e il resto si decodifica comunque. Usa il decoder giusto, o normalizza a mano quando sei bloccato su un percorso di codice legacy:
my $seg = "ab-cd_efgh";
$seg =~ tr{-_}{+/}; # le lettere URL-safe, riportate a casa
$seg .= "=" x (-length($seg) % 4); # riempimento ripristinato per il decoder standard
my $raw = decode_base64($seg);
print unpack("H*", $raw), "\n"; # 69bf9c77f79f82 - sette byte, di nuovo nell'alfabeto standard
base64url lo incontrerai subito nei JWT, i token che ogni API moderna distribuisce, e in ogni ID opaco che vive in un URL: gli ID video di undici caratteri, gli UUID salvati nell'alfabeto URL-safe (CPAN ha Data::UUID::Base64URLSafe esattamente per questo), e le chiavi di database che devono sopravvivere a una barra degli indirizzi. E se sei su un Perl vecchio che precede le funzioni core, il modulo standalone MIME::Base64::URLSafe del 2006, un porting del codec urlsafe di Python, fornisce urlsafe_b64encode e urlsafe_b64decode; in tutto ciò che è 3.11 o più recente, le funzioni integrate sono la scelta migliore.
JWT: leggere il header e il carico utile
Un JSON Web Token è, strutturalmente, due pezzi di JSON travestiti più una ricevuta criptografica. La forma compatta di RFC 7515 è fatta di tre segmenti base64url uniti da punti: il header protetto, il carico utile e la firma. Dividere e leggerne uno è questione di tre righe:
use MIME::Base64 qw(decode_base64url);
use JSON::PP;
my $jwt = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJob21lciJ9.uzM6l0c4...";
my ($head_b64, $claims_b64, $sig_b64) = split /\./, $jwt, 3;
my $head = decode_json(decode_base64url($head_b64));
my $claims = decode_json(decode_base64url($claims_b64));
print $claims->{sub}, " (", $head->{alg}, ")\n"; # homer (HS256)
Notare la divisione del lavoro: decode_base64url trasforma ogni segmento in byte, e decode_json del modulo core JSON::PP, presente da Perl 5.14, trasforma i byte di header e carico utile in strutture di dati Perl. Il segmento della firma è anch'esso base64url, ma è un digest criptografico, quindi decodifichi solo i primi due segmenti e lasci a una vera libreria il terzo.
La trappola è quella che tutti dimenticano: leggibile non significa valido. Il header e il carico utile sono leggibili di proposito, il che significa anche che chiunque può riscriverli; la firma è l'unica prova. Per qualsiasi cosa seria, verifica, non limitarti a decodificare. Il modulo CPAN Crypt::JWT, che si basa su CryptX, fa l'intero lavoro:
use Crypt::JWT qw(decode_jwt);
my $claims = decode_jwt(
token => $jwt,
key => $secret,
accepted_alg => "HS256",
);
Fa croak su una firma sbagliata, e fissare accepted_alg chiude il buco della confusione di algoritmi dove un attaccante gira il token su una variante più debole. Un flusso decodifica-e-stampa va bene per ispezionare un token durante una chiamata di assistenza; non è autenticazione.
File: dal .b64 di nuovo all'originale
I file sono il punto in cui la cultura degli one-liner di Perl brilla davvero, e l'intero lavoro sta in un solo comando. Il flag -0777 è l'ingrediente segreto, perché legge l'intero file d'un fiato, in una singola stringa, invece di dare in pasto al decoder il file riga per riga:
perl -MMIME::Base64 -0777 -ne 'print decode_base64($_)' < in.b64 > out
La forma riga per riga è sicura per una classe specifica di file: quelli in cui ogni riga contiene un multiplo di quattro caratteri Base64, ed è così per ogni corpo MIME-wrapped fatto a regola d'arte, dato che 76 è un multiplo di 4. Nel momento in cui i punti di avvolgimento diventano irregolari - e nei file avvolgiti a mano di solito lo sono - la decodifica riga per riga inizia a produrre riempimento in mezzo ai dati. La modalità slurp non ha alcuna condizione del genere, ed è per questo che è la scelta di default:
perl -MMIME::Base64 -ne 'print decode_base64($_)' < in.b64 > out
Dentro uno script, il modello è la solita danza sui file di Perl, con un dettaglio silenzioso ma importante: i layer :raw su entrambi gli handle, così Perl non cerca mai di interpretare i byte come testo di piattaforma in entrata o in uscita:
use MIME::Base64 qw(decode_base64);
use Digest::SHA qw(sha256_hex);
open my $in, "<:raw", $ARGV[0] or die $!;
local $/;
my $blob = <$in>;
close $in;
my $decoded = decode_base64($blob);
print sha256_hex($decoded), "\n"; # confronta con il checksum del mittente
open my $out, ">:raw", $ARGV[1] or die $!;
print {$out} $decoded;
close $out;
La riga dell'hash fa più che fare la brava. Poiché il decoder accetta quasi qualsiasi cosa, un checksum corrispondente a quello pubblicato dal mittente è l'unica prova che il viaggio è stato byte-per-byte. Per file davvero giganteschi, il loop riga per riga è l'alternativa a bassa memoria, a condizione che gli avvolgimenti stiano su confini di quattro caratteri, e MIME::Base64::decoded_base64_length ti dice quanto sarà grande l'output prima di impegnarti su un buffer.
Armatura PEM: togli il mantello, tieni il DER
I file .pem di ogni stack di sicurezza sono lo stesso Base64 con l'armatura: una riga di header, una riga di footer e un corpo avvolto a 64 caratteri secondo la vecchia convenzione Privacy Enhanced Mail. L'involucro è l'unica parte interessante, perché il decoder del modulo non si preoccupa minimamente della lunghezza delle righe:
use MIME::Base64 qw(decode_base64);
open my $fh, "<:raw", "cert.pem" or die $!;
local $/;
my $blob = <$fh>;
close $fh;
my @body = grep { !/^-----/ && /\S/ } split /\n/, $blob;
my $der = decode_base64(join "", @body);
print length($der), " bytes of DER\n";
Le righe BEGIN e END vengono rimosse, il resto viene unito in una singola stringa, e ogni a capo viene ignorato lungo il percorso. Per il lavoro quotidiano sui certificati gli strumenti OpenSSL lo fanno già per te; le otto righe sopra sono il modello da ricordare quando hai bisogno dei byte DER grezzi da solo, per un hash, un'impronta digitale o un confronto.
Data URI: immagini che portano il proprio indirizzo
Lo scheme data: di RFC 2397 inserisce un carico utile direttamente in un URL: data:, un media type facoltativo, un flag facoltativo ;base64, una virgola e i dati. I media binari come le immagini usano il flag, quindi il carico utile è l'alfabeto standard con il riempimento, e il decoder ordinario lo gestisce dopo un piccolo taglio:
use MIME::Base64 qw(decode_base64);
my $uri = "data:image/png;base64,iVBORw0KGgo...";
$uri =~ s/^data:[^,]+,// or die "not a data URI";
my $raw = decode_base64($uri);
print unpack("H8", $raw), "\n"; # 89504e47: i byte magici del PNG
Controllare i byte magici è la mossa giusta. Se i primi otto caratteri esadecimali non sono 89504e47, l'immagine non è un PNG per quanto il media type sostenga, e un decoder che non si lamenta mai rende possibile esattamente quel tipo di bugia silenziosa.
Cugini e fossili: uuencode e gli altri alfabeti
Prima che Base64 vincesse, il modo classico UNIX per spedire un binario per posta era uuencode, e lo incontrerai ancora in vecchie mailing list e vecchi strumenti. La buona notizia: Perl ha un decoder integrato per questo, nessun modulo richiesto, grazie al template u di pack e unpack:
my $uu = pack("u", "Hello, World!");
print $uu, "\n"; # -2&5L;&\L(%=O<FQD(0`` più un a capo
my $back = unpack("u", $uu);
print $back, "\n"; # Hello, World!
Le due chiamate sono inverse esatte, ed è tutta la storia che ti serve, e il comando classico uuencode della catena di strumenti UNIX semplicemente avvolge le righe grezze in un header begin e un footer end, quindi il carico utile che stai decodificando è la parte tra i due.
Base64 ha anche cugini di dialetto, e sapere quale decoder mangia quale ti risparmia una sessione di debugging:
| Dialetto | Avvolgimento | Dove lo incontri | Cosa fa decode_base64 |
|---|---|---|---|
| MIME (RFC 2045) | 76 caratteri | corpi email | lo decodifica così com'è: a capo e CRLF vengono ignorati |
| PEM (RFC 1421) | 64 caratteri | certificati e chiavi | lo decodifica così com'è |
| PKIX (RFC 7468) | 64 caratteri | strutture testuali X.509 | lo decodifica così com'è |
| Armatura OpenPGP (RFC 9580) | 76 caratteri più una riga CRC24 | chiavi e firme PGP | lo decodifica così com'è, la riga di checksum viene semplicemente ignorata |
| IMAP (RFC 3501) | nessuno | nomi di mailbox | non è questo alfabeto: la barra diventa una virgola, traduci prima le lettere |
Il da portarsi a casa: per ogni variante dell'alfabeto standard che differisce solo nell'avvolgimento, un decoder permissivo copre tutte. Solo quando cambia l'alfabeto stesso devi tradurre prima i caratteri.
Configurazioni, database e variabili d'ambiente
Le piattaforme container, le console cloud e un numero sorprendente di file di configurazione salvano credenziali e piccoli documenti come stringhe Base64 opache, perché un blob di lettere e cifre sembra meno pericoloso della password che è. La decodifica segue sempre le stesse due mosse: decode_base64 più una decisione sul set di caratteri:
use MIME::Base64 qw(decode_base64);
use Encode qw(decode);
my $secret = decode("UTF-8", decode_base64($config->{api_key}));
Il motivo per cui il formato è così popolare in questo punto è esattamente quello di cui RFC 4648 mette in guardia: gli umani smettono di accorgersi che i dati sono leggibili. Quindi tratta l'output decodificato come riservato dal momento in cui viene restituito, e tieni sia il blob sia il suo risultato fuori dai file di log, dagli alert e dai dump di debug.
La stessa forma compare nei database, dove i dati binari spesso viaggiano in una colonna TEXT come Base64, perché la colonna non può promettere di far passare byte arbitrari intatti:
use MIME::Base64 qw(decode_base64);
my $icon = decode_base64($row->{icon_data});
open my $fh, ">:raw", "icon.png" or die $!;
print {$fh} $icon;
close $fh;
Email: parti MIME e allegati
L'email è il posto dove Base64 ha preso il nome, e la tolleranza del modulo è progettata esattamente per questo traffico. Una parte MIME con Content-Transfer-Encoding: base64 arriva come righe di 76 caratteri di testo terminato da CRLF, e il decoder si mangia l'intero involucro così com'è, a capo inclusi:
use MIME::Base64 qw(decode_base64);
my $part_body = "SGVsbG8sIHF1ZXJ5IQpUaGlzIE1JTUUgcGFydCB0cmF2ZWxsZWQgYXMgYmFzZTY0LCB3cmFwcGVk";
$part_body .= "\r\nIGF0IDc2IGNoYXJhY3RlcnMsIENSTEYgYmV0d2VlbiBsaW5lcy4=";
my $text = decode_base64($part_body);
print $text; # il corpo del messaggio originale su due righe
Se crei o analizzi email con un framework, non fai niente di questo a mano: MIME::Lite codifica in base64 un allegato per te quando passi Encoding => "base64" a attach, e Email::MIME fa la stessa cosa automaticamente. La versione fatta a mano qui sopra è per la posta che arriva come testo grezzo in un log, un ticket o un messaggio inoltrato, e nella pratica è molta.
Trappole, raccolte e classificate
Il modulo è abbastanza piccolo da poterlo memorizzare, quindi ecco l'intera lista delle trappole in un unico posto, ordinata più o meno per quanto spesso morde:
| Trappola | Cosa succede | Correzione |
|---|---|---|
| Passare un segmento base64url al decoder standard | i - e _ vengono buttati via come rumore, e il resto si decodifica in byte sbagliati in silenzio |
usa decode_base64url, o traduci l'alfabeto e ripristina il riempimento prima |
| Fidarsi del silenzio su input corrotto | caratteri forestieri, troncature e alfabeto sbagliato si decodificano tutti senza un singolo avviso | fai prima il controllo rigoroso, e verifica con un hash quando l'originale è disponibile |
| Caratteri non ASCII dentro il blob | una lettera accentata fuori posto o uno spazio Unicode incollato vengono ignorati in silenzio, rimpicciolendo il risultato senza commento | lo stesso controllo rigoroso rifiuta tutto ciò che è fuori dall'alfabeto 7-bit |
| Trattare il risultato come testo | i byte non portano il flag UTF-8, quindi length() conta byte e le funzioni di stringa vedono il quadro sbagliato |
incatena decode("UTF-8", $raw) o il set di caratteri scelto prima di qualsiasi elaborazione del testo |
| Doppia codifica in uscita | far passare byte già UTF-8 attraverso encode("UTF-8", ...) trasforma Hëllo in Hëllo |
codifica caratteri, mai byte grezzi, e controlla il flag con utf8::is_utf8() in caso di dubbio |
| Decodificare un file riga per riga con avvolgimenti irregolari | righe che non finiscono su confini di quattro caratteri producono riempimento in mezzo all'output | leggi d'un fiato con -0777, o garantisci punti di avvolgimento su quattro caratteri |
| Restituire un match fallito in un contesto numerico | una sub che finisce con return $x =~ /.../ e lo passa a printf solleva l'avviso Missing argument in printf fuorviante |
coerisci il match: return $x =~ /.../ ? 1 : 0 |
| Codice vecchio che si aspetta il vecchio avviso | gli script di prima del 3.11 che contavano sul mormorio Premature end of base64 data sotto -w non vedono più niente |
aggiungi il tuo controllo rigoroso; l'avviso è andato per sempre |
| Dare per scontato che decodificare sia verificare | il decoder accetta quasi qualsiasi cosa e non dice niente | un checksum corrispondente o una firma verificata sono l'unica prova che conti |
| Loggare quello che decodifichi | il formato non nasconde niente, e il file di log è esattamente dove il prossimo lo troverà | tieni i segreti decodificati fuori dai log, dagli alert e dai dump di debug |
Buone abitudini
Le abitudini che impediscono a Base64 di avere mai la meglio sui tuoi script:
- Aspettati byte, sempre. Scrivi codice che sappia che
decode_base64restituisce ottetti grezzi, e incatena ildecodedel set di caratteri esplicitamente invece di sperare che il terminale faccia la cosa giusta. - Dai un nome al tuo set di caratteri. Di default usa
UTF-8e cambia solo quando i dati dicono il contrario. Il fallimento rigoroso didecode("UTF-8", ..., Encode::FB_CROAK)è una feature: ti dice che i byte non sono quelli che assumevi. - Allinea l'alfabeto alla fonte.
decode_base64urlper URL, token e ID;decode_base64per tutto il resto. I due alfabeti non sono intercambiabili, e il decoder non ti dirà quando hai sbagliato a scegliere. - Valida prima di decodificare. Non c'è un flag di modalità rigorosa in questo modulo, quindi un piccolo controllo è il buttafuori.
- Leggi i file d'un fiato, di default.
-0777olocal $/ = undefelimina un'intera classe di bug sui punti di avvolgimento, e il costo in memoria non è un problema per i file che decodifichi davvero. - Usa
:rawsu ogni file handle. Binari dentro, binari fuori. I layer di testo sono per gli umani, non per i byte. - Verifica con un hash. Quando l'originale è disponibile, un checksum corrispondente è l'unica prova di una decodifica byte-per-byte.
- Non loggare mai quello che decodifichi. Il formato non nasconde niente.
Una breve storia, raccontata dal changelog
Il formato è vecchio, e il rapporto di Perl con lui è più vecchio di quanto sembri. Qualche data verificata, in ordine:
- Il codice C precede Perl 5. Il decoder veloce dentro il modulo discende da codice in metamail, il programma di posta di Bellcore, con copyright del 1991, tre anni prima del primo rilascio di Perl 5. Quando chiami
decode_base64oggi, un pezzo degli anni Novanta è quello che fa il lavoro. - Nato negli strumenti web. Il modulo è partito come
LWP::Base64dentro libwww-perl nella metà degli anni Novanta, scritto da Martijn Koster e Joerg Reichelt, e si è laureato nella propria distribuzione CPAN,MIME::Base64, nell'aprile 1997, versione 2.00, con la voce di changelog che recita basato su libwww-perl-5.08. - L'era degli avvisi. Dalla 2.03 del 1997, un input troncato produceva un avviso Premature end of base64 data sotto
-winvece di un croak, e la 2.11 del 1999 ha corretto i build che avvisavano su dati che stavano bene. Era un decennio più nervoso per i decoder. - Core dal 2002. Perl 5.8 ha portato il modulo nella distribuzione core, e la sincronizzazione 2.13 con il core di quello stesso dicembre ha portato con sé il supporto EBCDIC, ed è per questo che encoder e decoder funzionano ancora sui mainframe.
- Il dialetto URL-safe è arrivato nel core di Perl nel 2010. La versione 3.11 ha aggiunto
decode_base64urle il suo gemello, quattro anni dopo che il modulo standaloneMIME::Base64::URLSafeera atterrato su CPAN nel 2006, lo stesso anno in cui RFC 4648 ha codificato il dialetto. - Il silenzio. Quel stesso rilascio 3.11 ha rimosso anche il vecchio avviso di troncamento, per l'eventualità che l'input sospetto fosse deliberato - e ogni rilascio successivo, inclusa la corrente linea 3.16 del 2020, ha tenuto il decoder cortese e silenzioso.
Fatti divertenti, specificamente Perl
Per chiudere il giro, le curiosità che rendono questa storia buona:
- Decodifica il nome del formato stesso.
decode_base64("YmFzZTY0")restituiscebase64. È vero dal 1997 e sarà vero per sempre. - Il decoder è un fantasma cortese. Nella sua storia 3.x non ha mai sollevato un'eccezione su input cattivo. Corrotto, troncato, alfabeto sbagliato: decodifica tutto e non si lamenta di niente, un comportamento che il changelog ha cementato deliberatamente nel 2010.
- L'avvolgimento MIME è un multiplo di quattro apposta. Il limite di 76 caratteri è diciannove gruppi di tre byte, 57 byte in totale, moltiplicato per quattro caratteri, ed è per questo che la decodifica riga per riga è sicura su qualsiasi corpo MIME avvolto a regola d'arte e insicura su tutto il resto.
- uuencode non stampa mai una minuscola. Il suo alfabeto si ferma all'underscore, ed è per questo che i vecchi file uuencoded sembrano digitati da una macchina tutta in maiuscolo, e per questo Perl porta ancora un decoder integrato per un formato più vecchio di internet.
- Perl ha un tempo spedito il suo comando decode-base64. I rilasci dalla 2.14 del 2003 alla 3.05 del 2004 includevano
encode-base64,decode-base64e i loro gemelli quoted-printable come script; la 3.06 del 2005 li ha spostati nella distribuzione separata MIME-Base64-Scripts. Se trovi una vecchia installazione con quel comando nel PATH, ora sai da dove viene. - Gli ID video di YouTube sono base64url travestiti. L'ID di undici caratteri nella tua barra degli indirizzi è un numero di 64 bit nell'alfabeto URL-safe con il riempimento rimosso, quindi ogni video che hai mai guardato ha una stringa Base64 nel suo URL, e
decode_base64urlsa leggerne uno. - La tolleranza è uno standard, non un bug. La regola MIME di essere liberali su quello che si accetta è il motivo per cui questo decoder sopravvive a tre decenni di dati disordinati, e il motivo per cui RFC 4648 mette in guardia che la stessa tolleranza può essere trasformata in un canale coperto se ti fidi di input non affidabili.
Quindi la prossima volta che una stringa di lettere, cifre, più e barra atterra nel tuo terminale, conosci l'intera storia. Una chiamata di funzione fa il lavoro, il decoder è un fantasma cortese che non ti rifiuterà mai, base64url ha il suo decoder, il set di caratteri è una decisione che prendi con intenzione, i file arrivano grezzi e partono grezzi, e un hash è l'unica prova che conta. E se un giorno devi fare il viaggio nella direzione opposta, avvolgendo i tuoi dati grezzi in un involucro di testo e spedendoli nel mondo, l'articolo correlato sulla codifica Base64 in Perl, linkato qui sotto, copre quel rito con la stessa profondità.
Ultimo aggiornamento: 2026-09-08
Articolo correlato: Codifica Base64 in Perl: una guida completa