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 Rust: una guida completa

Stai per mandare dei byte attraverso una porta solo-testo, e il prezzo d'ingresso è una stringa di lettere, cifre, segni più e barre, circa un terzo più lunga di quella da cui sei partito. Benvenuto nel Base64, il casello di internet. La home page di questo sito spiega il formato in tutta la sua profondità, qui serve solo ribadire la forma: il Base64 scrive tre byte in ingresso come quattro caratteri tratti da un alfabeto di 64 simboli, e una coda di uno o due caratteri = dice al lettore dove finivano i dati veri. Quello scambio di quattro per tre è l'intera economia del formato, e questa guida è su come farlo bene in Rust.

La prima cosa da sapere è che la libreria standard di Rust non lo fa al posto tuo. Non c'è un base64_encode() nascosto in std, e nessun use std::... che ti faccia cambiare idea. L'ecosistema si è assestato su una singola crate semplicemente chiamata base64, ed è diventata portante: la versione 0.23.1 è uscita il 4 agosto 2026, la crate ha pubblicato 45 versioni da dicembre 2015, e il suo contatore di download è vicino a 1,5 miliardi. Ogni esempio di codifica qui sotto usa quella singola crate, più due piccoli compagni per l'avvolgimento delle righe e l'armatura PEM.

La toolchain e la crate

Prima la toolchain, un comando per mondo:

# Debian / Ubuntu
sudo apt install rustc cargo
# o l'installer ufficiale, che configura rustup e cargo
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Poi la crate, dentro qualsiasi progetto cargo:

cargo new my-app
cd my-app
cargo add base64

Quella singola riga è l'intera installazione, e non tira in alcuna dipendenza. La crate arriva con tre feature opzionali che conviene conoscere: std (attiva di default; ti dà lo streaming di std::io, le implementazioni standard di Error e l'allocazione sull'heap), alloc (le API con allocazione per le build embedded no_std) e simd-unsafe (attiva di default; i motori SIMD, che compaiono qualche sezione più avanti). La versione minima di Rust supportata è 1.71.0, quindi qualsiasi versione recente la esegue. Intorno ad essa stanno i compagni, per i lavori che il nucleo non fa, deliberatamente:

  • line-wrap (versione 0.2) inserisce gli a capo da 76 o 64 caratteri che MIME e PEM pretendono; la crate base64 in sé rifiuta di avvolgere, di proposito, come vedrai.
  • pem (versione 4) costruisce e analizza i blocchi -----BEGIN ...----- per certificati e chiavi; dipende da base64 internamente e aggiunge l'armatura e l'avvolgimento.
  • base64ct (versione 1.8) è il decoder in tempo costante del progetto RustCrypto, per quando la metà di lettura di un giro di andata e ritorno è quella delicata.
  • base64-turbo (versione 0.3) è un codec più recente ad alta spinta che arriva a punte oltre 100 GiB/s sull'hardware moderno.

Una codifica, quattro caratteri

La cerimonia più piccola che esiste ha questo aspetto, e dimostra già il giro di andata e ritorno per intero:

use base64::prelude::*;
fn main() {
  let packed = BASE64_STANDARD.encode("Hello, world!");
  println!("{packed}");
  // SGVsbG8sIHdvcmxkIQ==
  let back = BASE64_STANDARD.decode(packed).unwrap();
  println!("{}", String::from_utf8(back).unwrap());
  // Hello, world!
}

Due cose meritano un'occhiata. Il modulo prelude ti consegna in silenzio due cose insieme: il motore BASE64_STANDARD e il trait Engine i cui metodi stai chiamando, ed è per questo che un semplice use base64::prelude::*; è tutto ciò che questo esempio ha bisogno. E encode() accetta qualsiasi cosa che si possa leggere come byte, grazie al bound AsRef<[u8]>: un &str, un letterale &[u8], un Vec<u8>, quello che vuoi. La metà di decodifica dell'esempio c'è solo per tenere d'occhio l'encoder, perché la direzione opposta ha la sua guida completa sul sito gemello. Se vuoi lo smoke test della crate in prima persona, la sua documentazione codifica asdf e ottiene YXNkZg==; stesso alfabeto, stessa matematica.

Il prezzo esatto di ogni byte

Ogni encoder Base64 in circolazione applica la stessa tassa, e una volta che vedi la matematica, puoi fare un preventivo. Ogni carattere in uscita porta 6 bit, ogni byte in ingresso ne porta 8, e il mucchio più piccolo che è entrambi è 24 bit: esattamente 3 byte in, esattamente 4 caratteri fuori. Quel rapporto è lo spettacolo intero, così un file di 3 kilobyte diventa 4 kilobyte e un upload di 10 megabyte diventa 13,3. Il padding è l'errore di arrotondamento reso visibile: quando l'input non è un multiplo di 3 byte, l'ultimo gruppo ha capacità in più, e l'encoder la riempie con = così la lunghezza dell'output resta un multiplo di 4. Ecco la tavola della verità dalla RFC 4648, che il motore standard riproduce esattamente:

Input Lunghezza mod 3 Codificato Lunghezza dell'output
"" (vuoto) 0 "" (vuoto) 0
f 1 Zg== 4
fo 2 Zm8= 4
foo 0 Zm9v 4
foobar 0 Zm9vYmFy 8
use base64::prelude::*;
let words: [&[u8]; 4] = [b"", b"f", b"fo", b"foo"];
for input in words {
  println!("{:?} -> {:?}", String::from_utf8_lossy(input), BASE64_STANDARD.encode(input));
}
// "" -> ""
// "f" -> "Zg=="
// "fo" -> "Zm8="
// "foo" -> "Zm9v"

Leggi due volte quella prima riga, perché è quella che tutti sbagliano nella testa: l'input vuoto si codifica nella stringa vuota, non in AA==. La stringa AA== è la codifica di esattamente un byte, un NUL, che è un payload davvero diverso. E quando devi dimensionare un buffer prima di codificare, la crate ti consegna la matematica come const fn, così puoi dimensionare array addirittura in fase di compilazione:

let padded = base64::encoded_len(15, true).unwrap();
let slim = base64::encoded_len(15, false).unwrap();
println!("{padded} / {slim}");   // 20 / 20
println!("{:?}", base64::encoded_len(13, true));  // Some(20)
println!("{:?}", base64::encoded_len(13, false)); // Some(18)
println!("{:?}", base64::encoded_len(14, false)); // Some(19)
println!("{:?}", base64::encoded_len(100, false)); // Some(134)

Tieni d'occhio le righe da 13 e 14 byte, perché sono quelle che fanno inciampare la matematica a mano: 13 byte richiedono 18 caratteri senza padding ma 20 con, mentre 14 byte ne richiedono 19 e 20. La funzione restituisce un Option, che è None solo quando la matematica delle lunghezze finirebbe in overflow, così un unwrap() è sicuro per qualsiasi input che potrebbe davvero esistere in memoria. Per il mondo a forma di email la tassa ha un supplemento: il MIME fa a capo a 76 caratteri, e la vecchia regola pratica è che il Base64 avvolto costa circa 1,37 volte la dimensione originale, più l'overhead degli header. La stessa FAQ della crate ha un'opinione meno educata sul padding in sé: i byte = "do not affect decoding other than to provide an opportunity to say 'that padding is incorrect'", e "exabytes of storage and transfer have no doubt been wasted on pointless = bytes".

Padding: una decisione sul lettore

Nella base64 0.23 le funzioni di codifica nude sono deprecate - il modo attuale è chiamare un metodo su un Engine, e un motore è una politica: quale alfabeto scrivere, e quale padding aggiungere. I preset vivono in base64::engine::general_purpose, con i quattro popolari riesportati nel prelude:

Motore Alfabeto Aggiunge padding Ottimo per
STANDARD / BASE64_STANDARD + / sì tutto, il default
STANDARD_NO_PAD / BASE64_STANDARD_NO_PAD + / no payload snelli che consumi anche tu
URL_SAFE / BASE64_URL_SAFE - _ sì contenuto per URL che vuole ancora il padding
URL_SAFE_NO_PAD / BASE64_URL_SAFE_NO_PAD - _ no JWT, URL, ID di oggetti

Codificare senza padding non è un hack che la crate tollera; è una posizione di prima classe, con le costanti di configurazione NO_PAD e PAD preconfigurate accanto ai fratelli *_INDIFFERENT aggiunti nella 0.23.0. Se i preset non bastano, costruisci il tuo motore da un Alphabet e un GeneralPurposeConfig con una sola manopola, e poiché i motori sono economici da costruire, metti il risultato in una const invece di ricostuirlo a ogni richiesta:

use base64::engine::general_purpose::{GeneralPurpose, GeneralPurposeConfig};
use base64::prelude::*;
const SLIM: GeneralPurpose = GeneralPurpose::new(
  &base64::alphabet::STANDARD,
  GeneralPurposeConfig::new().with_encode_padding(false),
);
fn main() {
  println!("{}", SLIM.encode("fo"));   // Zm8
  println!("{}", BASE64_STANDARD.encode("fo")); // Zm8=
}

Ora la decisione diventa una decisione sui decoder degli altri, che non è mai puramente estetica. Le regole di severità sul lato decodifica vengono da DecodePaddingMode, e la tabella qui sotto risponde a "il lato opposto legge quello che ho scritto?":

Codifichi con Un decoder STANDARD rigoroso Un decoder STANDARD_NO_PAD Un decoder INDIFFERENT
STANDARD (con padding) lo legge rifiuta il = lo legge
STANDARD_NO_PAD rifiuta: padding mancante lo legge lo legge
URL_SAFE_NO_PAD rifiuta: alfabeto sbagliato rifiuta: alfabeto sbagliato lo legge solo con l'alfabeto URL

Le regole pratiche escono da quella tabella. Se controlli entrambe le estremità, scegli un motore e usalo ovunque, e preferisci il no padding per risparmiare byte. Se consumi dati dal mondo esterno, il tuo decoder ha un voto su quale motore dovresti emettere: un decoder STANDARD stock ha bisogno del tuo padding, mentre un decoder STANDARD_PAD_INDIFFERENT accetta entrambi. E la scelta ha anche un sapore di sicurezza. Permettere sia la forma con padding sia quella senza dello stesso payload rende il Base64 malleabile; il paper del 2022 "La malleabilità del Base64 in pratica" (Chatzigiannis e Chalkias, ePrint 2022/361), a cui rimanda la stessa documentazione della crate, mostra perché. Un protocollo in cui gli stessi dati possono essere scritti in due modi diversi ha l'abitudine di colpire di sorpresa il codice che tratta la stringa codificata come un'identità, quindi quando il tuo formato definisce una sola forma canonica, falla rispettare al confine.

Base64url per token e link

Le ultime due lettere dell'alfabeto del Base64 standard sono + e /, e in un URL sono due dei caratteri più costosi del linguaggio: il più diventa %2B, la barra diventa %2F, e il padding diventa %3D. La sezione 5 della RFC 4648 lo risolve con l'alfabeto sicuro per URL e nomi di file, che scambia i due guai per - e _ e di solito salta anche il padding. I motori rendono la distinzione impossibile da perdere:

use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use base64::Engine;
let packed = URL_SAFE_NO_PAD.encode(b"\xfb\xef\xbe");
println!("{packed}");          // ----
let back = URL_SAFE_NO_PAD.decode(packed).unwrap();
println!("{back:02x?}");       // [fb, ef, be]

Tre byte dell'input più cattivo possibile diventano una stringa di quattro caratteri che puoi incollare in un URL, un nome di file, un cookie o una chiave del database senza una singola escape con percento. Questo è l'alfabeto in cui vivono i JSON Web Token: un JWT è tre parti base64url unite da punti, e coniare uno con la crate jsonwebtoken (versione 11 nel 2026) si presenta così:

use serde::Serialize;
use jsonwebtoken::{EncodingKey, Header, encode};
#[derive(Debug, Serialize)]
struct Claims {
  sub: String,
  company: String,
  exp: u64,
}
let key = b"secret";
let my_claims = Claims {
  sub: "b@b.com".to_owned(),
  company: "ACME".to_owned(),
  exp: 19_000_000_000,  // ben nel futuro
};
let token = encode(&Header::default(), &my_claims, &EncodingKey::from_secret(key)).unwrap();
println!("{token}");
// eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJiQGIuY29t...

La versione 11 ha un requisito di configurazione che morde i nuovi arrivati: la crate serve esattamente una delle feature rust_crypto o aws_lc_rs abilitata in Cargo.toml, e se nessuna è accesa va in panico la prima volta che firmi o verifichi un token. Nota la claim exp nella struct: la validazione della crate la tratta come obbligatoria di default, quindi i token veri ce l'hanno comunque, e l'alfabeto base64url nel token è affare interamente della libreria. Se stai solo ispezionando token invece di coniarli, l'articolo gemello mostra l'occhiatina di cinque righe. E un promemoria della regola d'oro, che si applica con forza speciale ai token: le tre parti di un JWT sono tutte leggibili senza chiave. Il Base64 è un sedile vicino al finestrino, non un lucchetto.

Testo in, byte fuori

Gli encoder non leggono nel pensiero, quindi "codifica questa stringa" significa sempre "codifica i byte UTF-8 di questa stringa" in Rust, perché è quello che consegna str::as_bytes(). La buona notizia è che il web moderno è quasi interamente UTF-8, quindi la strada onesta è corta e felice:

use base64::prelude::*;
let text = "café";
let packed = BASE64_STANDARD.encode(text.as_bytes());
println!("{packed}");   // Y2Fmw6k=

I casi multibyte si comportano tutti:

Testo originale Base64 Andata e ritorno
café Y2Fmw6k= pulito
日本語 5pel5pys6Kqe pulito
😀 8J+YgA== pulito
π ≈ 3.14159 z4Ag4omIIDMuMTQxNTk= pulito

L'unica vera decisione è da quali byte parti. Se i dati arrivano come byte e non come testo, un file letto dal disco o un buffer da una chiamata di rete, salta la stringa per intero e codifica direttamente il Vec<u8>; è anche l'unica risposta corretta per i payload non-UTF-8 come un PNG o un protobuf. Metti un'immagine qualsiasi accanto al tuo codice e punta la lettura su di essa:

use base64::prelude::*;
let file_bytes = std::fs::read("sprite.png").unwrap();
let size = file_bytes.len();
let packed = BASE64_STANDARD.encode(file_bytes);
println!("{size} bytes -> {} base64 chars", packed.len());
// ogni PNG codificato inizia con iVBORw0K
assert!(packed.starts_with("iVBORw0K"));

Quell'ultima asserzione è un controllo di sanità gratis e uno dei prefissi più riconoscibili di internet. E se mai codifichi lo stesso testo logico attraverso due charset diversi, o codifichi byte che hai letto male come un altro charset, il giro di andata e ritorno tornerà come mojibake con la faccia seria. L'encoder non mente mai; codifica semplicemente qualunque byte tu gli dia, ed è al contempo la sua forza più grande e la sua unica trappola.

Quando un formato vuole righe

La crate base64 non inserisce a capo deliberatamente, e non è la prima volta che prende quella posizione. La versione 0.5.0 è uscita con l'avvolgimento MIME delle righe integrato e gli a capo configurabili, e la versione 0.10.0 l'ha rimosso, la libreria che decideva che l'avvolgimento era troppo opinato per una crate generale e complicava la storia no_std. Se un formato pretende righe, la crate line-wrap esiste esattamente per quello. La sua unica funzione, line_wrap(), prende il tuo buffer pre-allocato, la lunghezza dell'input, il limite di colonna e l'a capo, e restituisce quanti byte di a capo ha inserito:

use base64::prelude::*;
let data = BASE64_STANDARD.encode(vec![b'a'; 300]);  // 400 caratteri
let mut buf = vec![0u8; data.len() + 16];
buf[..data.len()].copy_from_slice(data.as_bytes());
let endings = line_wrap::line_wrap(&mut buf, data.len(), 76, &line_wrap::crlf());
buf.truncate(data.len() + endings);
let wrapped = String::from_utf8(buf).unwrap();
println!("{} chars in, {} bytes out, {} line endings", data.len(), wrapped.len(), endings);
// 400 chars in, 410 bytes out, 10 line endings (cinque coppie CRLF)

Dimensiona il buffer in anticipo con lo spazio per gli a capo, chiama la funzione, e tronca al totale riferito; le cinque coppie CRLF sono il prezzo della regola delle 76 colonne del MIME. Per il PEM, scambia il limite e l'a capo, 64 colonne e line_wrap::lf(), e hai il corpo del testo dell'armatura. Poi la crate pem aggiunge i banner in una chiamata:

let pem_block = pem::encode(&pem::Pem::new("CERTIFICATE", b"0123456789abcdef"));
println!("{pem_block}");
// -----BEGIN CERTIFICATE-----
// MDEyMzQ1Njc4OWFiY2RlZg==
// -----END CERTIFICATE-----
let back = pem::parse(pem_block).unwrap();
println!("{}: {} bytes", back.tag(), back.contents().len());
// CERTIFICATE: 16 bytes
// e a capo in stile Unix, se il consumatore è schizzinoso
let lf_block = pem::encode_config(
  &pem::Pem::new("KEY", b"0123456789abcdef"),
  pem::EncodeConfig::new().set_line_ending(pem::LineEnding::LF),
);

Di default, pem::encode usa CRLF, la convenzione storica del PEM; il builder set_line_ending passa a LF per gli strumenti che se l'aspettano. Nota cosa la crate pem non fa: non chiama mai una funzione base64 che tu possa vedere, perché la codifica è affare suo. Quando un formato vuole righe, l'architettura è una crate per lavoro.

Streaming in spazio costante

Per i dati troppo grandi per stare in una variabile, la crate risponde con la stessa filosofia di streaming del resto dell'io di Rust: il write::EncoderWriter avvolge qualsiasi writer e codifica in base64 tutto quello che ci scrivi, in spazio costante. Il rituale completo per un buffer ha questo aspetto, e la star della sezione è la chiamata finish():

use std::io::Write;
use base64::prelude::*;
use base64::write::EncoderWriter;
fn main() {
  let mut encoder = EncoderWriter::new(Vec::new(), &BASE64_STANDARD);
  encoder.write_all(b"the quick brown fox jumps over the lazy dog").unwrap();
  let packed = encoder.finish().unwrap();
  println!("{}", String::from_utf8(packed).unwrap());
  // dGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw==
}

Perché finish() è la star? Perché è l'unica chiamata che scarica l'ultimo gruppo parziale e aggiunge il padding, e l'encoder ha un metodo fratello che non lo fa. La stessa documentazione della crate lo dice piano: finish() "encodes any leftover input bytes and adds padding if appropriate. It's called automatically when deallocated (see the Drop implementation), but any error that occurs when invoking the underlying writer will be suppressed. If you want to handle such errors, call finish() yourself." L'implementazione di Drop si comporta come BufWriter: scarica, ma ignora gli errori durante il drop. Così l'ultimo gruppo parziale non va perso, ma "probabilmente ha funzionato" non è una strategia di rilascio, perché l'errore di scrittura che ti avrebbe detto è andato via.

Lo stesso flusso funziona un livello più in là attraverso io::copy quando vuoi l'intera pipeline in una chiamata, e c'è un wrapper in regalo per i momenti "mi serve solo questo dentro una stringa di formato":

use std::io;
use base64::prelude::*;
use base64::write::EncoderWriter;
let file = b"the quick brown fox jumps over the lazy dog".to_vec();
let mut cursor = io::Cursor::new(file);
let mut encoder = EncoderWriter::new(Vec::new(), &BASE64_STANDARD);
io::copy(&mut cursor, &mut encoder).unwrap();
let packed = encoder.finish().unwrap();
println!("{}", String::from_utf8(packed).unwrap());
use base64::display::Base64Display;
use base64::prelude::*;
let value = Base64Display::new(b"\0\x01\x02\x03", &BASE64_STANDARD);
println!("base64: {value}");   // base64: AAECAw==

Quel wrapper Base64Display è un piccolo gioiello: formatta byte come Base64 dentro qualsiasi stringa di formato senza una singola allocazione sull'heap, il che rende le righe di log e l'output di debug all'improvviso piacevoli.

Allocazione, e la sua assenza

Il metodo comodo alloca, e per la maggior parte della tua vita quello è lo scambio giusto. Ma il trait Engine espone tre sapori di encode, e la tabella qui sotto è la matrice di decisione per intero:

Metodo Output Alloca
encode() un nuovo String sempre
encode_string() aggiunge al tuo String solo se deve crescere
encode_slice() scrive nel tuo &[u8] mai
use base64::prelude::*;
let input = b"Hello, world!";
let mut buf = vec![0u8; base64::encoded_len(input.len(), true).unwrap()];
let written = BASE64_STANDARD.encode_slice(input, &mut buf).unwrap();
buf.truncate(written);
println!("{}", std::str::from_utf8(&buf).unwrap());   // SGVsbG8sIHdvcmxkIQ==
// oppure tieni il buffer per intero sullo stack
let mut stack = [0u8; 24];
let n = BASE64_STANDARD.encode_slice(b"abc 123", &mut stack).unwrap();
println!("{}", String::from_utf8(stack[..n].to_vec()).unwrap());  // YWJjIDEyMw==
// e se l'hai dimensionato male, ottieni un errore, non un buffer overflow
let mut tiny = [0u8; 5];
println!("{:?}", BASE64_STANDARD.encode_slice(input, &mut tiny));
// Err(OutputSliceTooSmall)

Dimensiona il buffer con encoded_len(), scrivi con encode_slice(), e se sbagli la dimensione ottieni un pulito EncodeSliceError::OutputSliceTooSmall invece di un comportamento indefinito, che in un linguaggio di sistemi è la differenza tra un pomeriggio noioso e uno lungo. Per il lavoro embedded le stesse funzioni esistono dietro la feature alloc, così puoi tenere l'API e buttar via l'heap.

Velocità: i motori SIMD

La versione 0.23.0, quella uscita nel luglio 2026, ha portato la feature da titolo: motori accelerati SIMD per gli alfabeti standard e URL-safe. Ne esistono tre, e si dividono per quanto si fidano del tuo hardware:

Motore Rileva a runtime Funziona in no_std
Simd sì, sceglie AVX2 o NEON, torna al motore scalare no, serve std per il rilevamento
Avx2 no, dà per scontato che la CPU abbia AVX2 sì, su target x86_64
Neon no, dà per scontato che la CPU abbia NEON sì, su target aarch64
use base64::engine::general_purpose::GeneralPurposeConfig;
use base64::engine::{Avx2, Simd};
use base64::Engine;
let turbo = Simd::standard(GeneralPurposeConfig::new());
println!("{}", turbo.encode("simd works!"));
// c2ltZCB3b3JrcyE=
if let Some(fixed) = Avx2::standard(GeneralPurposeConfig::new()) {
  println!("{}", fixed.encode("hello avx2"));  // aGVsbG8gYXZ4Mg==
}

Il costruttore Simd fa il suo rilevamento della CPU una volta e restituisce il kernel migliore che trova, o il motore scalare se nessuno si applica, così costruiscilo una volta in una const o all'avvio e riusalo; su hardware capace è diverse volte più veloce del percorso scalare sia per la codifica sia per la decodifica. Una nota a piè di pagina onesta: il percorso SIMD è l'unico posto nella crate che tocca unsafe, ed è per questo che la feature si chiama simd-unsafe. Spegni la feature e l'intera crate torna #![forbid(unsafe_code)], con il motore scalare che continua a lavorare onestamente. Se la spinta grezza è l'unico obiettivo, la crate base64-turbo spinge oltre, arrivando a punte oltre 100 GiB/s con kernel AVX512, AVX2 e NEON dietro il rilevamento a runtime, e un fallback scalare al 100% sicuro su tutto il resto. La crate base64 ha doppia licenza MIT/Apache-2.0, quindi tutto questo è gratis, inclusa la velocità.

Quattro alfabeti in più

L'alfabeto della RFC è il default, ma la crate base64 ne include altri quattro, ognuno un piccolo monumento a qualche protocollo reale che aveva bisogno della sua svolta:

Alfabeto La svolta Chi lo usa abc 123 si codifica in
alphabet::CRYPT Prima ./, poi cifre e lettere, senza padding hash delle password Unix classiche di crypt(3) MK7X612mAk
alphabet::BCRYPT Prima ./, poi lettere, poi cifre hash delle password bcrypt WUHhGBCwKu
alphabet::IMAP_MUTF7 una virgola fa da sostituto della barra, senza padding nomi di mailbox UTF-7 modificato di IMAP YWJjIDEyMw
alphabet::BIN_HEX un alfabeto denso di punteggiatura che salta le lettere confondibili BinHex 4, il vecchio wrapper di file Macintosh B@*M)$%b-`
use base64::engine::general_purpose::{GeneralPurpose, NO_PAD};
use base64::Engine;
let crypt = GeneralPurpose::new(&base64::alphabet::CRYPT, NO_PAD);
println!("{}", crypt.encode(b"abc 123"));   // MK7X612mAk
let bcrypt = GeneralPurpose::new(&base64::alphabet::BCRYPT, NO_PAD);
println!("{}", bcrypt.encode(b"abc 123"));  // WUHhGBCwKu
let imap = GeneralPurpose::new(&base64::alphabet::IMAP_MUTF7, NO_PAD);
println!("{}", imap.encode(b"abc 123"));    // YWJjIDEyMw

Stesso input, tre output diversi, tutti Base64 valido nel proprio dialetto. L'alfabeto crypt è quello con un superpotere genuino: poiché i suoi simboli sono ordinati per combaciare con i pattern dei bit, ordinare le stringhe codificate ti dà lo stesso ordine di ordinare i byte originali, ed è per questo che GEDCOM 5.5 (1996) lo usava per i campi multimedia - la revisione 5.5.1 ha tolto la feature - e la crate ti include ancora l'alfabeto. E se il dialetto che ti serve non è nella crate, puoi definirlo con una stringa di 64 caratteri, perché Alphabet::new() costruisce per te le tabelle di codifica e decodifica:

use base64::alphabet::Alphabet;
use base64::engine::general_purpose::{GeneralPurpose, PAD};
use base64::Engine;
// un base64 da mondo bizarro: +/ all'inizio invece che alla fine
let alphabet = Alphabet::new(
  "+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
).expect("a valid 64 char alphabet");
let bizarro = GeneralPurpose::new(&alphabet, PAD);
println!("{}", bizarro.encode(b"hello 99"));  // YETqZE6eMRi=
// mentre il motore standard dice:
println!("{}", base64::prelude::BASE64_STANDARD.encode(b"hello 99"));
// aGVsbG8gOTk=

Un'avvertenza sulla strada degli alfabeti personalizzati: nel momento in cui inventi un dialetto, diventi l'unica persona sulla terra che può leggere i tuoi dati, quindi fallo solo quando un protocollo lo pretende, e scrivi un commento che dica quale.

Dove lavorano gli encoder

La codifica Base64 appare nei progetti Rust in un cast prevedibile di situazioni:

  • Upload di file in API JSON, dove il file è un campo di byte in costume da testo, l'uso più comune di gran lunga.
  • Data URI in HTML e CSS, la specie data:image/png;base64,..., meravigliose per icone minuscule, dubbie per immagini hero.
  • JWT e OAuth, dove base64url è il dialetto e la crate jsonwebtoken è lo strumento.
  • Blocchi PEM per certificati e chiavi, le sezioni -----BEGIN CERTIFICATE----- che avvolgono il Base64 a 64 caratteri per riga.
  • Binario in file XML e di configurazione, il pattern <data encoding="base64"> che trovi ancora nei bookmark esportati e nei dump delle impostazioni.
  • File LDAP e LDIF, che usano il Base64 per tenere i valori di attributo binari su una riga sola.
  • Payload di codici QR e passaggi di consegne negli appunti, dove il testo sopravvive al viaggio e il binario no.
  • Header di autenticazione Basic HTTP, dove Basic TWFuOnBhc3M= è una coppia di credenziali, e un promemoria che questo è un problema di imballaggio, non di nascondere.

E la regola d'oro che governa tutto: il Base64 è nastro imballaggio, non un lucchetto. Non è cifratura e non è compressione - è l'opposto della compressione, e chiunque con questo articolo può invertire tutto ciò che fa in una riga. Codifica liberamente, ma non codificare mai una password, una chiave API o un segreto e chiamarlo protetto. Se deve essere nascosto, usa cifratura vera, e se è grande, considera se un upload multipart sarebbe stato semplicemente più economico della tassa.

Un decennio di piccoli passi

Il formato è più vecchio del web. Nel 1987, il protocollo Privacy-Enhanced Mail (RFC 989) doveva portare dati binari sui canali di posta a 7 bit, e standardizzò questa codifica con righe da esattamente 64 caratteri. Ogni blocco -----BEGIN CERTIFICATE----- di internet è un discendente di quella decisione, ed è per questo che i file PEM ancora oggi fanno a capo a 64. Nel 1996 la specifica MIME (RFC 2045) adottò lo schema, lo chiamò "base64" dal suo alfabeto di 64 caratteri, e spostò l'avvolgimento a 76 caratteri. Prima di tutto questo, le macchine Unix uscivano con uuencode e i Mac con BinHex, ognuno con il proprio alfabeto, e entrambi emergono ancora in sistemi vecchi come fossili con header di file. Nel 2006, la RFC 4648 è diventata lo standard che tutti citano, con le tabelle degli alfabeti, la variante base64url, e le regole canoniche di codifica che ogni motore di questo articolo implementa. La sua sezione 3.5 richiede agli encoder di impostare a zero i bit di riempimento non usati, e la crate lo fa; se il tuo payload innesca più avanti il controllo InvalidLastSymbol di un decoder rigoroso, la corruzione è avvenuta a monte.

La storia della crate in sé fa rima. È apparsa su crates.io nel dicembre 2015, e la versione 0.5.0 ha aggiunto con fierezza l'avvolgimento MIME delle righe con a capo configurabili. Poi la versione 0.10.0 nel 2018 ha rimosso l'avvolgimento e la gestione degli spazi bianchi, la libreria che decideva che una crate generale doveva codificare e lasciare la poesia al livello di applicazione; la stessa release ha aggiunto l'EncoderWriter in streaming. La versione 0.20.0 nel 2022 ha introdotto l'astrazione dei motori e ha reso il padding canonico il default, e la 0.21.0 ha deprecato le vecchie funzioni libere a favore dei metodi dei motori, con la nota del compilatore "Use Engine::encode" (funzionano ancora, ed è per questo che tanto codice legacy compila contento). Nel 2024, la versione 0.22.0 ha affinato la semantica degli errori e ha accelerato la decodifica del 5 al 10 per cento. E nel luglio 2026, la versione 0.23.0 è arrivata con i motori SIMD, i simboli di padding personalizzati, un messaggio di errore più chiaro e l'innalzamento dell'MSRV a 1.71, con la patch 0.23.1 del 4 agosto che sistemava la suite di test per le architetture non-SIMD.

Cose su cui vale la pena sorridere

Perché una guida completa dovrebbe finire con un sorriso:

  • La parola "base64" si codifica in YmFzZTY0. Un formato che si descrive da solo è l'equivalente tecnico di uno specchio che parla in Morse.
  • La stringa vuota si codifica nella stringa vuota. Il nulla è l'unico input che non costa nulla, il che è una specie di esenzione fiscale.
  • AA== non è la codifica del nulla; è la codifica di un byte NUL. Nel Base64, "il nulla" e "uno zero" sono creature diverse, e i decoder li distinguono.
  • Ogni PNG codificato in Base64 inizia con iVBORw0K. È il magic number del PNG con il suo nastro imballaggio, uno dei prefissi più riconoscibili di internet.
  • In un URL, i caratteri Base64 standard hanno bisogno di costumi da escape: il più diventa %2B, la barra diventa %2F, e il padding diventa %3D. Il base64url esiste così i caratteri possono indossare le loro facce.
  • Gli ID dei video di YouTube sono base64url senza padding: otto byte di ID diventano la stringa di undici caratteri che puoi incollare ovunque. Uno degli usi più visibili della modalità senza padding su tutto internet.
  • L'alfabeto vecchio delle password di crypt(3) ordina correttamente: le stringhe codificate ordinate stanno nello stesso ordine del testo in chiaro ordinato. GEDCOM 5.5 (1996) usava quell'alfabeto per i suoi campi multimedia, la revisione 5.5.1 ha tolto la feature, e la crate te lo include ancora.
  • BinHex, il vecchio wrapper Macintosh, costruì il suo alfabeto per escludere caratteri confondibili a prima vista come 7, O, g e o. Un encoder progettato per gli occhi umani, in un mondo prima del controllo ortografico.
  • La stessa FAQ della crate è secca sul padding: exabyte di archiviazione e trasferimento sono senza dubbio stati sprecati su byte = inutili. Il casello pretende il pedaggio dal 1987.
  • Il Base64 non è cifratura. Se lo fosse, non potresti leggere l'output di nessun esempio di questo articolo. È un sedile vicino al finestrino, non una cassaforte.

La versione corta

Scegli il motore in base alla strada che i dati percorreranno: BASE64_STANDARD per tutto quello che decodifichi anche tu, i motori _NO_PAD quando controlli entrambe le estremità e vuoi i byte indietro, URL_SAFE_NO_PAD per token e URL, e un Alphabet personalizzato solo quando un protocollo ci tiene. Dimensiona i buffer con encoded_len(), fai streaming alle cose grandi attraverso EncoderWriter e chiudi sempre con finish(), avvolgi le righe con line-wrap e pem solo quando un formato lo pretende, lascia ai motori SIMD il lavoro pesante quando puoi, e ricorda che lo scambio di quattro per tre è il prezzo per passare attraverso la porta solo-testo. Codifica tutto, proteggi solo ciò che ha bisogno di un lucchetto vero. E quando devi andare nella direzione opposta, scompattando una stringa nei byte che hanno iniziato il viaggio, l'articolo gemello copre la decodifica in Rust, completa di tabellone dei risultati con i messaggi di errore esatti.

Ultimo aggiornamento: 2026-09-08

Articolo correlato: Decodifica Base64 in Rust: una guida completa