Devi lavorare con il formato Base64? Allora questo sito è perfetto per te! Usa il nostro praticissimo strumento online per codificare o decodificare i tuoi dati.

Codifica Base64 in Ruby: una guida completa

I tuoi dati hanno una destinazione che non accetta l'aspetto che hanno. Un'immagine che deve vivere dentro un documento JSON. Un token che deve attraversare un URL. Un allegato che deve sopravvivere a un protocollo progettato per testo a sette bit. Un segreto che deve stare in una variabile d'ambiente senza rompere le virgolette. In ciascuno di questi posti, qualcosa tra qui e là sta per distruggere il tuo binario - e la soluzione ha un nome: Base64.

In Ruby, l'intero lavoro sta in un modulo che viene spedito col linguaggio. Tre encoder, niente da installare, e un output che puoi prevedere fino al carattere esatto prima ancora di eseguire il codice. Questa prevedibilità è la metà della storia che la maggior parte delle guide salta, perché è nella codifica che le sorprese esigono il loro pedaggio: un a capo finale si infiltra nel tuo JSON, un a capo non richiesto spacca un token, e una singola scelta sbagliata di alfabeto rovina un URL. Questa guida percorre tutti e tre gli encoder, la matematica dell'output e ogni payload che uno sviluppatore Ruby codifica davvero, così le sorprese smettono di essere sorprese.

Un rapido richiamo prima di cominciare: Base64 riscrive i dati tre byte alla volta, emettendo quattro caratteri da un alfabeto di 64 simboli, con uno o due caratteri = di riempimento quando l'input non è divisibile esattamente per tre - che è anche il motivo per cui l'output finisce approssimativamente un terzo più grande dell'input. La home page di questo sito copre il formato a fondo, quindi questo articolo tiene il discorso sul formato in un solo respiro e va dritto al lavoro.

Quale encoder ti serve?

Ruby ti dà tre encoder, e la scelta tra loro è un quiz a tre domande: l'output può contenere a capo? Può contenere + o /? Può contenere riempimento? Ecco il cast completo:

Encoder Forma dell'output A capo Riempimento Quando usarlo
Base64.strict_encode64(bin) una riga, alfabeto standard mai sempre presente JSON, token, API, file - il default sicuro
Base64.encode64(bin) più righe, alfabeto standard dopo ogni 60 caratteri, più uno finale sempre presente corpi email e altri protocolli di testo basati su righe
Base64.urlsafe_encode64(bin, padding: true) una riga, alfabeto trattino-trattino basso mai a tua scelta, attivo di default tutto ciò che finisce in un URL, un cookie o un identificatore

Se stai decidendo sotto pressione di tempo, la risposta breve è: strict_encode64 di default, urlsafe_encode64 quando il risultato dovrà viaggiare dentro un URL, e encode64 solo quando il lato ricevente è un protocollo di testo che vuole righe corte. Tutto ciò che segue spiega perché, e dove ogni scelta ti costa in silenzio.

strict_encode64: il cavallo da lavoro

Base64.strict_encode64 è l'encoder che userai davvero nella grande maggioranza del tuo codice. Produce esattamente una riga di output, sempre con il riempimento corretto, dall'alfabeto standard:

require "base64"
Base64.strict_encode64("hello world")
# => "aGVsbG8gd29ybGQ="
Base64.strict_encode64("s")
# => "cw=="

E dato che l'algoritmo è deterministico, puoi prevedere la lunghezza esatta dell'output dall'input - niente congetture, nessun bug di off-by-one nelle tue colonne di database. La tabella sotto è l'intera aritmetica:

Lunghezza dell'input Lunghezza dell'output Riempimento in coda
3n byte (divisibile esattamente) 4n caratteri nessuno
3n + 1 byte 4n + 4 caratteri due =
3n + 2 byte 4n + 4 caratteri un =

Quindi 11 byte diventano 16 caratteri, 100 byte diventano 136, e un file di 1 megabyte diventa circa 1,33 megabyte di testo. Questa crescita di un terzo è il prezzo d'ingresso di ogni payload Base64 che spedisci, ed è il numero da tenere in tasca ogni volta che una colonna, una cache o un limite di frequenza di un'API comincia a stringersi.

Base64.strict_encode64("123")
# => "MTIz"        3 byte dentro, 4 caratteri fuori
Base64.strict_encode64("1234")
# => "MTIzNA=="   4 byte dentro, 8 caratteri fuori, due caratteri di riempimento
Base64.strict_encode64("12345")
# => "MTIzNDU="   5 byte dentro, 8 caratteri fuori, un carattere di riempimento

encode64: quello che aggiunge a capo

Base64.encode64 è il classico, e ha un comportamento che ha rovinato più di un pomeriggio: avvolge il suo output. Ogni 60 caratteri, inizia una nuova riga, e finisce sempre con un a capo finale:

Base64.encode64("hello world")
# => "aGVsbG8gd29ybGQ=\n"
Base64.encode64("*" * 46)
# => "KioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioq\nKg==\n"

L'avvolgimento non è un bug - è una funzionalità ereditata dal mondo MIME, dimora originale del metodo, dove le righe lunghe erano una violazione di protocollo. La gem mail di Ruby ci si appoggia deliberatamente - il suo encoder Base64 porta addirittura un commento che dice che l'avvolgimento di righe di Ruby tiene l'output entro i limiti di lunghezza riga di SMTP. Se stai codificando corpi email, encode64 ti sta facendo un favore.

Ma in ogni altro contesto, l'avvolgimento è una tassa. L'incidente più comune è un documento JSON dove un valore Base64 si trova tutto a un tratto su due righe:

payload = { "logo" => Base64.encode64(File.binread("logo.png")) }
puts payload.to_json
# il valore del logo porta a capo che nessuno ha chiesto

E il gemello più piccolo dello stesso bug è l'a capo finale sulle stringhe corte: Base64.encode64("s") restituisce "cw==\n", quindi un token che incolli in un URL o che confronti con un valore atteso fallisce per motivi che insegui per venti minuti. Il rimedio è un strip - ma il rimedio migliore è strict_encode64, che non aggiunge mai un singolo carattere che non ti sei guadagnato. C'è anche un'asimmetria affascinante da conoscere: un input vuoto produce una stringa vuota senza a capo finale, quindi Base64.encode64("") è semplicemente "".

urlsafe_encode64: l'alfabeto sicuro per i link

Due caratteri dell'alfabeto standard causano guai in ogni posto dove un parser di URL sta a guardare: + (uno spazio, nei parametri di query) e / (un separatore di percorso). L'RFC 4648 ha risolto con uno scambio - - prende il posto di +, _ prende il posto di / - e Ruby lo implementa in Base64.urlsafe_encode64:

Base64.urlsafe_encode64("\xfb\xef\xbe".b)
# => "----"
Base64.urlsafe_encode64("\xff\xff\xff".b)
# => "____"

Quei due esempi sono l'alfabeto in mostra: gli stessi byte che l'encoder standard rende come ++++ o //// escono come ---- e ____, caratteri che sopravvivono a URL, percorsi, nomi di file e campi di modulo senza alcuna codifica percentuale. L'output è una riga, come in strict_encode64.

L'unica opzione del metodo è la parola chiave padding:, aggiunta in Ruby 2.3, ed è quella da conoscere. La specifica dei JSON Web Token esige il base64url senza riempimento, e lo stesso fanno molti altri schemi di token:

Base64.urlsafe_encode64("*")
# => "Kg=="
Base64.urlsafe_encode64("*", padding: false)
# => "Kg"

Con il riempimento spento, la matematica delle lunghezze si sposta: 3n + 1 byte ora producono 4n + 2 caratteri e 3n + 2 byte producono 4n + 3. Il lato decoder ci sta - l'urlsafe_decode64 di Ruby aggiunge il riempimento mancante da solo - quindi l'output senza riempimento è sicuro da emettere, ma l'output con riempimento è il default più amichevole quando l'altra parte è un lettore rigoroso di RFC 2045. Una cautela: spegni il riempimento solo quando una specifica lo richiede. Risparmia uno o due caratteri e ti regala una categoria di lamentele dai decoder.

Cosa codifica davvero Ruby: le stringhe sono byte

Prima dei casi d'uso, un fatto specifico di Ruby che plasma tutto: una stringa Ruby è una sequenza di byte che indossa un'etichetta di codifica, e gli encoder guardano solo i byte. L'etichetta dice a Ruby come visualizzare e confrontare la stringa; non cambia quello che viene codificato:

require "base64"
s = "h\u{e9}llo"
puts s.encoding
# => UTF-8
puts s.bytes.length
# => 6   la e accentata è due byte
Base64.strict_encode64(s)
# => "aMOpbGxv"

Questa è la trappola dietro "perché il mio output è più lungo di quanto mi aspettassi": la stringa che hai digitato è di solito più corta in caratteri che in byte, e Base64 fa pagare per byte. La direzione opposta è altrettanto silenziosa - una stringa UTF-8 non valida viene codificata senza alcuna lamentela, perché l'encoder non ha nulla da validare:

broken = "h\u{e9}llo".b.force_encoding("UTF-8")
broken.setbyte(1, 0xFF)
puts broken.valid_encoding?
# => false
Base64.strict_encode64(broken)
# => del base64, nessun errore, i byte sono byte

Per il binario vero e proprio, salta del tutto la macchina del testo e costruisci i byte con pack o leggili con File.binread. Un esempio soddisfacente è la firma PNG - gli otto byte che aprono ogni file PNG sulla Terra:

png_magic = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A].pack("C*")
Base64.strict_encode64(png_magic)
# => "iVBORw0KGgo="

JWT: firmare dati che sono anche leggibili

I JSON Web Token sono il consumatore di maggior profilo dell'encoder URL-safe di Ruby. Un token è tre segmenti base64url uniti da punti - header, payload, firma - e la specifica è esplicita: l'alfabeto deve essere quello URL-safe, e il riempimento deve essere spento. La gem jwt se ne occupa per intero:

# Nel Gemfile: gem "jwt"
require "jwt"
token = JWT.encode(
  { sub: "1234567890", name: "Alice", exp: Time.now.to_i + 3600 },
  "my-secret-key",
  "HS256"
)
puts token
# => eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIi...
payload, header = JWT.decode(token, "my-secret-key", true, algorithm: "HS256")
puts header
# => {"alg"=>"HS256"}

Puoi anche guardare lo strato Base64 fare il lavoro dentro il token, perché i segmenti sono solo base64url di JSON:

require "base64"
require "json"
payload_json = JSON.generate({ "sub" => "1234567890", "name" => "Alice" })
segment = Base64.urlsafe_encode64(payload_json, padding: false)
puts segment
# => eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIn0

Due regole appartengono a questo caso d'uso. Non costruire mai i tuoi JWT a mano in produzione - è la firma a rendere un token qualcosa di più di una confessione - e quando decodifichi con la gem, fissa l'algoritmo nell'hash delle opzioni come mostrato sopra, così l'header stesso del token non può scegliere per te il metodo di verifica.

HTTP Basic auth: costruire l'header

Il modo più antico per dire "chi sono" in HTTP è ancora il più semplice: codifica in Base64 le credenziali, mettile dopo la parola Basic e invia l'header. Costruirlo in Ruby è una riga sola:

require "base64"
credentials = Base64.strict_encode64("alice:s3cr3t!")
puts "Basic #{credentials}"
# => Basic YWxpY2U6czNjcjN0IQ==

La libreria standard di Ruby fa esattamente questo per te in Net::HTTP, chiamando direttamente il template pack del core - ["user:pass"].pack("m0") è quello in cui basic_auth si riduce sotto la superficie:

require "net/http"
request = Net::HTTP::Get.new("https://example.org/api")
request.basic_auth("alice", "s3cr3t!")
puts request["Authorization"]
# => Basic YWxpY2U6czNjcjN0IQ==

E la cautela di sicurezza, detta una volta per lasciarla in archivio: il Base64 è un traduttore, non un lucchetto. Le credenziali in un header Basic auth sono leggibili da chiunque sappia leggere il pacchetto. Questo header è accettabile solo su HTTPS, dove è il trasporto a fare la protezione vera.

Data URI: immagini e font inline

Una data URI è la risposta del web a "voglio questa immagine senza un file a parte": un tipo media, la parola base64, una virgola e i byte. È così che le demo HTML a file singolo spediscono i loro loghi, così che le favicon si nascondono nel CSS, e così che un'immagine generata può vivere interamente in una stringa di template:

require "base64"
png = File.binread("logo.png")
data_uri = "data:image/png;base64,#{Base64.strict_encode64(png)}"
css = "background-image: url(#{data_uri});"
puts css.length
# => il tuo foglio di stile, meno una richiesta HTTP

Usa strict_encode64 qui - il payload è una singola riga pulita, senza avvolgimento, senza a capo. E tieni d'occhio le dimensioni: l'immagine che inlinedi cresce di circa un terzo, quindi le data URI brillano per gli asset piccoli (favicon, loghi, font per icone) e gonfiano per quelli grandi. Una foto di testata di due megabyte diventa 2,7 megabyte del tuo documento HTML, e i tuoi utenti se ne accorgeranno al primo scorrimento in 4G.

Email: da dove viene il Base64

Ogni altro caso d'uso di questo articolo è un discendente di questo. SMTP è stato progettato negli anni '80 per righe corte di testo a sette bit, che vuol dire che non poteva portare un JPEG. La soluzione - il Privacy-Enhanced Mail, poi il MIME nel 1993 - è stata riscrivere il binario come testo con un alfabeto di 64 simboli, ed è esattamente il formato che stai usando oggi. Le cicatrici sono ancora visibili nell'output di Ruby: encode64 avvolge a 60 caratteri - una larghezza che non serve a nessun protocollo in particolare, come vedrai più avanti, ma abbastanza corta da tenere l'email educata.

Nella pratica lascerai alla gem mail il lavoro MIME. Allega un file binario e la gem sceglie l'encoder Base64, avvolge le righe e scrive gli header:

# Nel Gemfile: gem "mail"
require "mail"
message = Mail.new do |m|
  m.from = "dev@example.org"
  m.to = "ops@example.org"
  m.subject = "Binary report"
  m.add_file("report.bin")
end
puts message.encoded
# la parte dell'allegato porta Content-Transfer-Encoding: base64

Il testo non in ASCII negli header riceve lo stesso trattamento in un vestito leggermente diverso: le parole codificate RFC 2047, che avvolgono il Base64 in un tag charset tra punti interrogativi, come =?UTF-8?B?w7wgc2VjcmV0cw==?=. Se dovessi mai costruirle o analizzarle a mano, il Base64 dentro è quello ordinario, decodificato con decode64 e poi ri-etichettato con il charset che la parola dichiara.

Armatura PEM per chiavi e certificati

Chiavi e certificati indossano l'armatura PEM, e l'armatura è Base64 con una cornice: una riga BEGIN, i byte codificati in righe da 64 caratteri e una riga END. Se dovessi mai dover produrre un file PEM da byte DER grezzi, la costruzione è un doppio avvolgimento:

require "base64"
der_bytes = File.binread("server.der")
body_lines = Base64.strict_encode64(der_bytes).scan(/.{1,64}/)
pem = (["-----BEGIN PRIVATE KEY-----"] + body_lines +
  ["-----END PRIVATE KEY-----"]).join("\n") + "\n"
File.write("server.key", pem)

Due note. Per primo, quasi non ti servirà mai, perché la gem openssl scrive il PEM per te (key.to_pem), e l'etichetta tra le righe BEGIN e END deve corrispondere a ciò che c'è dentro - sbagliarla produce un file che ogni strumento su internet rifiuta. Per secondo, la lunghezza di riga qui è 64, la larghezza PEM classica; l'encode64 di Ruby avvolge a 60 invece, e ogni parser PEM decente ignora del tutto le lunghezze di riga, quindi entrambe le larghezze si decodificano senza problemi.

File: la convenzione .b64

Il formato di file più comune nel mondo del Base64 è un semplice file di testo con estensione .b64 (o .base64) che contiene un payload codificato - pensalo come "il file, ma sicuro da incollare ovunque". Produrlo da Ruby è un one-liner:

require "base64"
File.write("payload.b64", Base64.strict_encode64(File.binread("payload.bin")))
puts File.size("payload.b64")
# => circa 1,33 volte le dimensioni originali

Usa strict_encode64 così il file contiene una singola riga pulita - la convenzione che la maggior parte degli strumenti di decodifica (e il decoder rigoroso di Ruby) si aspettano. Rileggerlo è l'immagine speculare: leggi, decodifica e scrivi i byte in modalità binaria, così che nulla li muti in uscita:

encoded = File.read("payload.b64")
bytes = Base64.strict_decode64(encoded)
File.binwrite("restored.bin", bytes)

Se i tuoi file .b64 vengono da strumenti che avvolgono le righe - alcune varianti della CLI base64 lo fanno - rimuovi gli a capo prima di una decodifica rigorosa, o usa il decoder tollerante, che li salta gratis.

File di configurazione, variabili d'ambiente e database

Ogni volta che i dati binari devono vivere dentro un documento di testo, il Base64 è il ponte. Il pattern si ripete in tre posti con piccole variazioni.

Le variabili d'ambiente e i file .env non possono contenere byte grezzi, quindi i byte vengono codificati prima di lasciare la macchina che li ha:

require "base64"
# in qualche punto dove configuri l'app
ENV["APP_LOGO"] = Base64.strict_encode64(File.binread("logo.png"))
# in qualche punto dove l'app parte
b64 = ENV.fetch("APP_LOGO")
File.binwrite("logo.png", Base64.decode64(b64))

YAML ha un tipo binario nativo, e Psych si occupa del Base64 per te - una stringa BINARY serializzata in YAML esce come scalare !binary, e si ricarica identica byte per byte:

require "yaml"
yaml_text = YAML.dump({ "logo" => File.binread("logo.png") })
puts yaml_text.lines.first(2)
# => "---"
# => "logo: !binary |-"
data = YAML.load(yaml_text)
puts data["logo"].encoding
# => ASCII-8BIT

Nei database la domanda è il tipo di archiviazione, non la codifica. Se il tuo database ha una vera colonna binaria - BLOB, BYTEA, VARBINARY - usala, e lascia al driver il trasporto dei byte. Il Base64 in una colonna TEXT è il pattern per quando il layer di archiviazione parla solo di stringhe: alcuni document store, API a forma di JSON, o uno schema legacy che non puoi cambiare. Il prezzo è la tassa di un terzo sulle dimensioni della colonna, e la disciplina di codificare in entrata e decodificare in uscita a ogni confine, senza eccezioni.

Checksum che viaggiano come testo

I hash sono binari, ma i checksum viaggiano per lo più come testo: liste di integrità dei file, chiavi di cache, impronte digitali, righe di log. Le classi digest di Ruby hanno ciascuna un metodo base64digest che fa la codifica in una chiamata sola:

require "digest"
Digest::SHA256.base64digest("hello")
# => "LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ="

L'output è Base64 standard con riempimento - la stessa cosa che otterresti da Base64.strict_encode64(Digest::SHA256.digest("hello")) - quindi è sicuro da conservare, confrontare e incollare. L'unica decisione è la coerenza: una lista di checksum generata con Base64 deve essere verificata contro output Base64, e le rappresentazioni esadecimali e Base64 dello stesso hash sono stringhe diverse, quindi scegline una e atteniti ad essa.

Codificare cose grandi in piccoli blocchi

Come i decoder, gli encoder sono basati su buffer: leggono l'intero input ed emettono l'intero output. Non c'è un encoder in streaming nella libreria standard, quindi per i payload grandi la pianificazione è la memoria, e c'è una simmetria piacevole nella matematica. La codifica fa crescere i tuoi dati di un terzo, quindi è l'output - non l'input - la tua più grande allocazione, e per un file di 1 gigabyte dovresti aspettarti circa 1,33 gigabyte di testo davanti a te.

Se è troppo da tenere in un colpo solo, puoi codificare a fette, perché l'alfabeto Base64 è auto-sincronizzante sui confini di tre byte: codifica ogni fetta di 3 byte in modo indipendente e la concatenazione è identica alla codifica dell'intero:

require "base64"
require "securerandom"
bin = SecureRandom.random_bytes(10_001)
whole = Base64.strict_encode64(bin)
chunked = bin.scan(/.{1,3}/m).map { |slice| Base64.strict_encode64(slice) }.join
puts chunked == whole
# => true

Lo stesso trucco ti dà un avvolgimento di righe fatto a mano che corrisponde esattamente a encode64: 45 byte codificano sempre in esattamente 60 caratteri, quindi tagliare l'input a 45 byte e unire i pezzi con a capo riproduce l'output MIME classico, una riga alla volta, con in memoria una sola fetta alla volta:

def wrap_like_encode64(bin)
  lines = bin.scan(/.{1,45}/m).map { |slice| Base64.strict_encode64(slice) }
  lines.join("\n") + "\n"
end
bin = SecureRandom.random_bytes(10_001)
puts wrap_like_encode64(bin) == Base64.encode64(bin)
# => true

Dalla riga di comando

La codifica non ha bisogno di un file di script neppure lei. La forma one-liner legge un file e scrive il suo Base64 su stdout:

ruby -rbase64 -e 'print Base64.strict_encode64(File.binread(ARGV[0]))' payload.bin > payload.b64

E la forma pipe legge da stdin, ed è così che avvolgeresti un flusso di byte di qualsiasi altro comando:

some_command | ruby -rbase64 -e 'print Base64.strict_encode64(STDIN.read)'

Tieni print in entrambi - un puts di troppo aggiungerebbe un a capo al tuo Base64, e per l'output di strict_encode64 questo trasforma un token pulito in uno rotto. La stessa regola pratica del lato decodifica: se il prossimo consumatore del tuo output è rigoroso, nulla oltre il Base64 stesso può viaggiare insieme.

Le trappole che costano byte extra agli sviluppatori Ruby

  • L'a capo finale nel JSON. Base64.encode64 termina ogni risultato non vuoto con un a capo, quindi un valore che dovrebbe essere un token pulito arriva nel tuo JSON con una sorpresa di \n in coda. Usa strict_encode64 per tutto ciò che verrà conservato, confrontato o inviato in una singola riga.
  • L'avvolgimento a 60 caratteri nei token e negli URL. Lo stesso metodo avvolge gli output lunghi in più righe. Una stringa avvolta in un URL sono due URL, e un token avvolto è un token rotto. Di nuovo: strict_encode64, oppure strip/delete gli a capo se sei bloccato con l'output di encode64.
  • Più e barra negli URL. Il Base64 standard in un parametro di query significa codifica percentuale di %2B, %2F e %3D in uscita e sperare che l'altra parte li decodifichi. urlsafe_encode64 rimuove il problema alla fonte.
  • Riempimento nel posto sbagliato. I JWT e altri schemi di token vogliono il riempimento spento; i lettori MIME potrebbero non farcela col riempimento mancante. Emetti padding: false solo dove una specifica lo richiede, e sappi su quale lato della staccionata siede ciascuno dei tuoi consumatori.
  • I caratteri non sono byte. Una stringa di cinque caratteri con una lettera accentata è sei byte in UTF-8, e la matematica della lunghezza dell'output gira sui byte. Quando il risultato codificato è "troppo lungo", conta i byte, non i caratteri.
  • La tassa di un terzo nel design degli schemi. Un BLOB da 16 KB diventa una stringa Base64 di circa 22 KB in una colonna TEXT. Dimensiona le tue colonne, le cache e i payload delle API per la forma codificata, non per quella binaria.
  • Due alfabeti, due stringhe diverse. Gli stessi byte si codificano in modo diverso negli alfabeti standard e URL-safe, quindi un valore codificato è confrontabile solo con un altro valore dello stesso alfabeto. Non confrontarli mai e non mescolarli.
  • Il Base64 non è un lucchetto. Codificare un segreto non lo rende segreto. Chiunque abbia la stringa ha i tuoi dati; il Base64 controlla solo come appaiono i byte, non chi può leggerli.

Abitudini che salvano byte e bug

  • Fai di strict_encode64 il tuo default. Passa a urlsafe_encode64 nel momento in cui l'output vivrà in un URL, un cookie o un identificatore, e a encode64 solo quando la destinazione è un protocollo di testo basato su righe come l'email.
  • Tieni l'alfabeto coerente tra encoder e decoder alle due estremità del cavo. Il bug "Base64 rotto" più comune in assoluto è un produttore di alfabeto standard che incontra un consumatore URL-safe, o viceversa.
  • Dai agli encoder i byte che intendi codificare: File.binread per i file, pack per il binario costruito, e una stringa UTF-8 quando la stringa è il dato. L'encoder non metterà in discussione le tue scelte - conta solo i byte.
  • Fa i conti sulla crescita. Ogni volta che una stringa Base64 attraversa un confine dentro un contenitore di dimensioni fisse, moltiplica per 4/3 e aggiungi un po' di margine per il riempimento.
  • Usa il Base64 per la portabilità, mai per la segretezza. Se l'obiettivo è tenere i dati privati, lo strumento è la cifratura, e il Base64 è solo quello che fai col testo cifrato dopo.

Com Base64 è diventato una gem

Per gran parte della sua vita, il modulo Base64 era solo un file nella libreria standard, come molti dei più vecchi helper di Ruby. I metodi strict e URL-safe si sono uniti alla coppia originale durante la linea di sviluppo 1.9 - l'intera libreria base64, con tutti e quattro i metodi, è stata aggiunta al trunk a settembre 2008 e spedita per la prima volta in 1.9.1 (2009), e la parola chiave padding: è arrivata con Ruby 2.3 nel 2015. Tutto ciò che oggi vedi dell'API si era già assestato allora - il resto della storia riguarda come il modulo viene spedito.

Nel 2020, con Ruby 3.0, il team core ha iniziato a estrarre le librerie standard nelle loro gem, e base64 è diventato una di queste: versione 0.1.0, mantenuta nel repository ruby/base64 dai contributori del core. È uscita come default gem - distribuita con Ruby e sempre disponibile, quindi require "base64" ha continuato a funzionare senza alcuna cerimonia. La versione 0.2.0 è seguita con Ruby 3.3 nel 2023, aggiungendo la costante Base64::VERSION e un set di documentazione molto più ricco.

Poi Ruby 3.4 di dicembre 2024 ha ridisegnato la linea: base64 è passato dalla lista delle default gem a quella delle bundled gem, lo stesso scaffale di csv e drb. Le bundled gem continuano a essere spedite col linguaggio, ma ci si aspetta che i progetti basati su Bundler le dichiarino, quindi se sei su Ruby 3.4 o successivo e la tua app è guidata da Bundler, aggiungi gem "base64" al tuo Gemfile (o esegui gem install base64) e sei coperto. Ruby 4.0 del 2025 ha portato la versione 0.3.0, con le firme di tipo RBS così i checker statici possono vedere il modulo come si deve.

Per tutto il viaggio l'implementazione è rimasta quella che è sempre stata: poche decine di righe di Ruby puro avvolte attorno ai template pack e unpack del core. Nessuna estensione C, nessuna dipendenza, e - con un conteggio di download nell'ordine delle centinaia di milioni su rubygems.org - una delle gem più installate della piattaforma.

Fatti divertenti su Ruby

  • L'intero modulo, encoder inclusi, è corto da poterlo leggere in una pausa caffè. encode64 è letteralmente [bin].pack("m"), strict_encode64 è [bin].pack("m0"), e urlsafe_encode64 è l'encoder rigoroso con uno scambio di due lettere in più, meno il riempimento quando lo chiedi.
  • L'avvolgimento a 60 caratteri di encode64 non coincide né col massimo di 76 caratteri del MIME né col 64 classico del PEM. È semplicemente quello che il template m di pack ha sempre fatto, e l'encoder Base64 della gem mail ne parla con favore: l'avvolgimento automatico di righe di Ruby tiene l'output entro i limiti di SMTP.
  • Il Net::HTTP di Ruby non si dà la briga di usare il modulo Base64 per la Basic auth - chiama direttamente il template pack, il che è un bel promemoria che il modulo è un layer di comodità sopra il core, non il contrario.
  • Ogni classe digest porta un metodo base64digest, quindi Digest::SHA256.base64digest è un cittadino di prima classe accanto a hexdigest - checksum nel testo senza una seconda chiamata.
  • Il tag !binary di YAML è Base64 travestito. Psych fa la codifica nel momento in cui fai il dump di una stringa BINARY, ed è per questo che i file di configurazione pieni di binari hanno l'aspetto che hanno.
  • Il modulo che stai usando non è sempre stato il modulo che ricordi. Il vecchio Ruby aveva b64encode (avvolgimento a una larghezza scelta) e decode_b (decodifica di header RFC 2047); entrambi sono spariti nella linea 1.9, quindi qualsiasi codice pre-2010 che erediti e li chiama muore con un NoMethodError.
  • Gli ID video di YouTube sono base64url senza riempimento - undici caratteri, senza più, senza barra, senza uguale - che è esattamente il genere di identificatore corto e sicuro per link per cui l'alfabeto URL-safe è stato progettato.

Il rovescio della medaglia

Ora hai il quadro completo della codifica: un cavallo da lavoro di default che non ti sorprende mai, un classico che avvolge le righe per i protocolli che lo pretendono, un alfabeto sicuro per i link con un interruttore di riempimento, e le regole a livello di byte che decidono esattamente che aspetto avrà il tuo output. La direzione opposta - smontare una stringa Base64, scegliere tra i tre decoder di Ruby e trasformare i byte risultanti in qualcosa di utilizzabile - ha le sue trappole silenziose, a partire da un decoder che non dice mai di no. Quel lato della strada è coperto in dettaglio nell'articolo sulla decodifica Base64, linkato qui sotto.

Ultimo aggiornamento: 2026-09-08

Articolo correlato: Decodifica Base64 in Ruby: una guida completa