← Tutti gli articoli

Base64 non è crittografia: cosa nasconde e cosa no

Base64 è codifica, non crittografia. Perché password, payload JWT, chiavi API, header Basic Auth, URL e log restano leggibili dopo Base64.

"Ma tanto lo passiamo in Base64 prima di salvarlo." Se ti capita di rivedere abbastanza file di configurazione, dashboard interne, bundle del browser, handler di webhook o codice di autenticazione, prima o poi vedi quella frase. Suona plausibile perché l'output sembra rimescolato:

c2VjcmV0IHBhc3N3b3Jk

Ma Base64 non è crittografia. Non è protezione con password. Non è un modo sicuro per nascondere chiavi API, claim di JWT, ID utente, credenziali Basic Auth o dati privati in un URL. Lo stesso valore si decodifica in una sola chiamata:

atob("c2VjcmV0IHBhc3N3b3Jk");
// "secret password"

La regola pratica è netta: se il valore originale torna senza chiave segreta, Base64 non l'ha protetto. Ha solo cambiato la rappresentazione.

Il test di sicurezza veloce

Quando vedi Base64 nel codice, chiediti che lavoro sta facendo:

Base64 viene usato per... Buon uso? Cosa controllare
Infilare byte in JSON Tenere limiti di dimensione ragionevoli
Incorporare una piccola data: URI Non inlinare file grandi alla cieca
Salvare come testo ciphertext, nonce, salt o byte di firma Il passo crittografico deve stare prima di Base64
Portare un cursore di paginazione opaco Di solito Trattalo come un dettaglio di interfaccia, non come controllo di accesso
Costruire un header Basic Auth Solo con HTTPS TLS protegge la richiesta, Base64 no
Nascondere una chiave API nel frontend No Ogni browser riceve valore e decoder
Salvare una password No Usa hashing lento delle password
Nascondere claim di JWT No I payload di JWT firmati sono leggibili se non cifrati come JWE
Nascondere ID utente o ruoli negli URL No L'autorizzazione lato server deve comunque girare

Se la frase che ti passa per la testa è "così nessuno lo legge", non stai descrivendo Base64. Stai descrivendo crittografia, hashing, firma, controllo di accesso o gestione dei segreti.

Cosa fa davvero Base64

Base64 è uno schema di codifica da binario a testo. RFC 4648 definisce l'alfabeto Base64 tipico come:

A-Z a-z 0-9 + / =

Il carattere = è padding. Base64 raggruppa i byte di input in blocchi da 24 bit: 3 byte in ingresso diventano 4 caratteri stampabili. Ecco perché l'output di Base64 è circa il 33 % più grande dei byte grezzi prima di qualsiasi compressione.

btoa("hello");
// "aGVsbG8="

atob("aGVsbG8=");
// "hello"

Lo stesso round trip funziona da terminale:

printf '%s' 'secret password' | base64
# c2VjcmV0IHBhc3N3b3Jk

printf '%s' 'c2VjcmV0IHBhc3N3b3Jk' | base64 -d
# secret password

Non c'è prompt di password, chiave privata, passo di decifratura, salt, nonce, IV, tag né work factor. Chi ha la stringa codificata può invertirla.

Base64 vs Base64url

Il Base64 standard non è sempre URL-safe perché +, / e = sono scomodi in URL, nomi di file, cookie e segmenti di token. Base64url cambia l'alfabeto:

Caratteristica Base64 standard Base64url
Caratteri 62 e 63 + e / - e _
Padding Di solito = Spesso omesso
Posti tipici MIME, PEM, Basic Auth, campi binari JWT, token in URL, nomi di file
Differenza di sicurezza Nessuna Nessuna

Questo conta quando si debuggano i JWT. Un JWT usa Base64url, non Base64 standard con padding:

header.payload.signature

Se incolli un segmento di JWT in un decoder Base64 standard e fallisce, la stringa non è per forza segreta o cifrata. Può servire solo un trattamento Base64url:

function base64urlToBase64(part) {
  let out = part.replace(/-/g, "+").replace(/_/g, "/");
  while (out.length % 4 !== 0) out += "=";
  return out;
}

Quella conversione cambia l'alfabeto. Non aggiunge riservatezza.

UTF-8: la trappola del browser

btoa() e atob() del browser lavorano su stringhe binarie. Gli esempi in ASCII puro vanno bene:

btoa("hello");
// "aGVsbG8="

Ma il testo Unicode richiede prima un passo in byte. Questo sorprende i team che testano con hello e poi codificano nomi di cliente, testo in cinese, emoji o arabo:

btoa("你好");
// InvalidCharacterError in browsers

Usa byte UTF-8:

function utf8ToBase64(text) {
  const bytes = new TextEncoder().encode(text);
  const binary = Array.from(bytes, (b) => String.fromCharCode(b)).join("");
  return btoa(binary);
}

function base64ToUtf8(base64) {
  const binary = atob(base64);
  const bytes = Uint8Array.from(binary, (ch) => ch.charCodeAt(0));
  return new TextDecoder().decode(bytes);
}

const encoded = utf8ToBase64("你好");
base64ToUtf8(encoded);
// "你好"

Lo strumento Base64 Encode & Decode su questo sito fa il round trip UTF-8 nel browser, così il testo non ASCII torna come testo e non come mojibake.

Perché gli sviluppatori confondono Base64 con la crittografia

Base64 compare spesso vicino a sicurezza vera:

  • I certificati TLS sono salvati come testo PEM.
  • I JWT usano sezioni Base64url.
  • HTTP Basic Auth usa Base64 per username:password.
  • Le chiavi pubbliche SSH contengono materiale di chiave codificato in Base64.
  • Il ciphertext cifrato viene spesso codificato in Base64 prima di storage o trasporto.

Quest'ultimo punto è quello che confonde di più. Dati cifrati possono essere codificati in Base64, ma Base64 non è la cifratura. La sicurezza è arrivata da un'operazione crittografica come AES-GCM, XChaCha20-Poly1305, RSA-OAEP, JWE o TLS. Base64 ha solo reso i byte risultanti facili da salvare in JSON, email, header o colonna di database.

Usa parole precise nel codice e nei testi di prodotto:

encodedBackup      // OK se è solo Base64
encryptedBackup    // OK solo se c'è stata cifratura vera
signedState        // OK se un HMAC o una firma proteggono l'integrità
passwordHash       // OK se l'ha prodotto un algoritmo di hashing di password

Qui i nomi non sono cosmetica. Un nome fuorviante può far pensare a chi rileggerà il codice in futuro che ci sia un confine di sicurezza dove non c'è.

Codifica vs cifratura vs hashing vs firma

Queste operazioni risolvono problemi diversi:

Operazione Compito principale Serve un segreto? Reversibile? Esempio
Codifica Cambiare la rappresentazione No Base64, Base64url, URL encoding
Cifratura Nascondere il testo in chiaro Sì, con chiave AES-GCM, XChaCha20-Poly1305, JWE
Hashing Impronta a senso unico No No SHA-256 per checksum
Hashing di password Verifica lenta della password Salt più parametri di costo No Argon2id, bcrypt, scrypt
Firma / MAC Provare integrità e origine Sì, o coppia di chiavi No HMAC, JWS, firma Ed25519

Base64 risponde alla domanda "questa sequenza di byte può viaggiare su un canale testuale?". Non risponde a "chi può leggerlo?", "chi l'ha modificato?" o "questo utente può accedere a quel record?".

Errore reale di sicurezza 1: chiavi API nascoste nel codice frontend

Questo pattern spunta in app React, estensioni del browser, temi WordPress e strumenti di amministrazione interni:

// La chiave arriva comunque a ogni browser.
const apiKey = atob("c2stbGl2ZS1hYmMxMjM0NTY3ODk=");

fetch("https://api.example.com/report", {
  headers: { Authorization: `Bearer ${apiKey}` },
});

Base64 non aiuta perché il browser riceve sia il valore codificato che il decoder. Chiunque può aprire DevTools, ispezionare il bundle JavaScript, eseguire la stessa atob() e copiare la chiave.

Il fix è architetturale:

  • I segreti veri stanno sul server.
  • Alle chiavi lato browser dai scope stretti e restrizioni per dominio.
  • Fai passare le chiamate sensibili dal tuo backend.
  • Ruota ogni segreto che è già arrivato agli utenti.
  • Dai per scontato che gli scanner di segreti sappiano decodificare stringhe simili a Base64.

Codificare una chiave può rendere la code review più difficile. Non rende la chiave più sicura.

Errore reale di sicurezza 2: password salvate come Base64

Questo è dannoso perché può restare zitto in un database per anni:

// Storage reversibile. Non farlo.
const storedPassword = btoa(password);

// Dopo:
const password = atob(storedPassword);

Se il database perde dati, ogni password torna subito. Non c'è nessun costo di cracking perché non c'è nulla da crackare.

Lo storage delle password deve essere a senso unico:

// Forma del flow, con una libreria di hashing di password.
const hash = await argon2.hash(password);
const ok = await argon2.verify(hash, candidatePassword);

Un normale sistema di login non dovrebbe mai avere bisogno di recuperare la password dell'utente. Deve solo verificare una password candidata contro un hash salvato.

Errore reale di sicurezza 3: Basic Auth scambiato per cifratura

L'header sembra abbastanza codificato da ingannare la gente:

Authorization: Basic dXNlcjpwYXNzd29yZA==

Decodifica in:

atob("dXNlcjpwYXNzd29yZA==");
// "user:password"

HTTP Basic Auth è accettabile solo se la richiesta è protetta da HTTPS. TLS cifra lo scambio HTTP in transito. Base64 si limita a impacchettare username:password in una stringa ASCII adatta agli header.

Su HTTP semplice, un osservatore di rete può leggere le credenziali. In log, reverse proxy, tracce APM e dump di supporto, un header Basic Auth va trattato come una password in chiaro.

Errore reale di sicurezza 4: payload JWT trattati come privati

Un JWT firmato normale ha tre sezioni Base64url:

header.payload.signature

Le prime due si decodificano in JSON. La terza è la firma.

eyJzdWIiOiIxMjM0Iiwicm9sZSI6ImFkbWluIn0

Si decodifica in:

{
  "sub": "1234",
  "role": "admin"
}

Questo non vuol dire che il token sia falso. Un JWT firmato può essere a prova di manomissione e allo stesso tempo leggibile. La firma protegge l'integrità: dice al server se il token è stato emesso da una parte fidata e se header o payload sono cambiati. Non nasconde il payload.

Non mettere password, chiavi API, segreti di sessione, numeri di carta completi, dettagli medici o dati privati di profilo nel payload di un JWT firmato normale. Se i claim hanno bisogno di riservatezza, usa JWE o evita di mettere quei dati nel token.

Inoltre: decodificare non è verificare. Uno strumento per sviluppatori può mostrare i claim; un server deve comunque verificare firma, issuer, audience, scadenza, key ID, policy sull'algoritmo e regole di autorizzazione specifiche dell'applicazione prima di fidarsi.

Errore reale di sicurezza 5: parametri di URL che sembrano opachi

Le app legacy passano spesso lo stato così:

/profile?data=eyJ1c2VySWQiOjQyfQ==

Che decodifica in:

{
  "userId": 42
}

Ne nascono due problemi. Chiunque può leggerlo e chiunque può modificarlo e ricodificarlo. Se /profile?data=... è l'unica cosa che decide quale record utente caricare, il bug è l'autorizzazione mancante.

Pattern più sicuri:

  • Tratta i dati decodificati dall'URL come input non fidato.
  • Ricontrolla i permessi sul server.
  • Preferisci un riferimento corto e casuale lato server quando lo stato è sensibile.
  • Usa un HMAC o una firma se il client deve portare stato a prova di manomissione.
  • Tieni lo stato privato fuori dagli URL, perché gli URL finiscono copiati in log, cronologia del browser, analytics e screenshot di supporto.

Errore reale di sicurezza 6: export "cifrati" che sono solo codificati

Un altro odore da code review è una funzione così:

function exportEncryptedBackup(data) {
  return btoa(JSON.stringify(data));
}

Il nome della funzione dice cifrato. Il codice dice codificato.

Se l'export deve essere solo un blob di testo portabile, rinominalo:

function exportBase64Backup(data) {
  return btoa(JSON.stringify(data));
}

Se agli utenti viene detto che l'export è cifrato, usa vera cifratura autenticata e rendi esplicita la gestione delle chiavi. Vuol dire libreria crypto verificata, nonces/IV casuali, tag di autenticazione, generazione sicura delle chiavi e un piano per il loro storage. Base64 può ancora avvolgere il ciphertext dopo.

A cosa serve davvero Base64

Base64 è utile. L'errore è assegnargli un compito di sicurezza.

Usi buoni:

  • Dati binari dentro JSON, XML, HTML o email.
  • Allegati email MIME.
  • Piccole data: URI per immagini o font.
  • Formattazione dell'output crittografico per ciphertext, firme, nonces, salt e chiavi pubbliche.
  • Segmenti di serializzazione compatta JWT e JOSE.
  • Cursori di paginazione opachi dove la leggibilità è una questione di prodotto, non un confine di sicurezza.
  • Debug di campi API, payload JWT e blob di configurazione.

La formulazione sicura è "codificato in Base64", non "cifrato". Quella singola parola evita una quantità sorprendente di malintesi in produzione.

Cosa usare al suo posto

Se sei ricorso a Base64 perché volevi sicurezza, scegli la primitiva giusta per il lavoro:

Obiettivo Usa invece
Nascondere dati a utenti o attaccanti Cifratura autenticata come AES-GCM o XChaCha20-Poly1305
Proteggere i dati in transito HTTPS/TLS
Salvare password Argon2id, bcrypt o scrypt con salt per password e costo tarato
Salvare chiavi API Secrets manager, ambiente server, scope, log di audit e rotazione
Provare che un valore non è cambiato HMAC o firma digitale
Impedire agli utenti di editare lo stato in URL Autorizzazione lato server più stato firmato o riferimento di sessione lato server
Mantenere privati i claim di un JWT JWE, o evita di mettere claim privati nel token
Infilare binari in JSON, email o header Base64 va bene

La parte difficile raramente è il nome dell'algoritmo. Sono threat modeling, gestione delle chiavi, rotazione, controllo di accesso e decidere chi debba poter vedere il testo in chiaro in primo luogo.

Una checklist pratica per il code review

Cerca gli usi di Base64:

rg "btoa\\(|atob\\(|base64|Buffer\\.from\\(.*base64|toString\\('base64'\\)"

Classifica ogni occorrenza:

  1. Trasporto: conversione byte-testo per JSON, MIME, data URI, PEM o storage. Di solito va bene.
  2. Formattazione dopo la crypto: ciphertext, nonce, firma, salt o chiave pubblica. Di solito va bene se la crypto è corretta.
  3. Interfaccia opaca: cursore, codice di invito o stato portato dal client. Controlla autorizzazione e protezione contro la manomissione.
  4. Nascondere segreti: chiave API, password, token, credenziali, claim privato o dati utente. Non va bene.
  5. Nomenclatura fuorviante: variabili o testi UI dicono cifrato, sicuro, protetto, segreto, mascherato o nascosto. Sistema il nome o l'implementazione.

Poi chiediti:

  • Il valore codificato arriverebbe a un browser, un'app mobile, un utente, un raccoglitore di log, un proxy o uno strumento di supporto?
  • C'è un decoder proprio accanto?
  • Un server verifica i permessi dopo la decodifica?
  • Il valore è firmato se il client può modificarlo?
  • Se è un JWT, la firma viene verificata prima di qualsiasi decisione di fiducia?
  • Se è una password, perché è reversibile?

Questo tiene la review coi piedi per terra. Un PNG in una data URI è noioso. Una chiave API di produzione in un bundle JavaScript no.

Risposta agli incidenti: se trovi "segreti" in Base64

Se scopri un segreto codificato in Base64 nel codice sorgente, nei log, in un ticket, nell'analytics, in un URL o in un export di database:

  1. Decodificalo in locale per confermare cos'è.
  2. Tratta il valore decodificato come esposto.
  3. Ruota credenziali, token e chiavi che potrebbero essere state visibili.
  4. Rimuovi la copia codificata da codice, log, screenshot o ticket dove possibile.
  5. Sostituisci il design con un segreto lato server, una chiave pubblica scoped, stato firmato, cifratura vera o hash della password.
  6. Aggiungi una regola di review perché lo stesso pattern non torni.

Non perdere tempo a discutere se la stringa Base64 fosse "difficile da notare". Scanner automatici, utenti del browser e attaccanti la sanno decodificare tutti.

Quando ti serve davvero un token cifrato

Un JWT firmato standard usa JWS: payload leggibile, firma a prova di manomissione. Se ti serve davvero la riservatezza dei claim, usa JWE (JSON Web Encryption).

Un JWE compatto ha cinque sezioni Base64url:

protected-header.encrypted-key.iv.ciphertext.authentication-tag

Quelle sezioni restano testo Base64url. La riservatezza viene dall'algoritmo di cifratura JWE e dalla chiave del destinatario, non da Base64url in sé.

In molte web app, un design più semplice funziona meglio: tieni lo stato sensibile sul server e manda al browser un identificatore di sessione corto e casuale. Non ogni JWT leggibile deve diventare un JWE.

Domande frequenti

Base64 è crittografia?

No. Base64 è una codifica reversibile senza chiave segreta. Chiunque abbia la stringa può decodificarla con una funzione del browser, un comando da terminale, uno strumento online o poche righe di codice.

Base64 è almeno offuscamento?

Solo nel senso più debole. Può nascondere il valore a un'occhiata veloce, ma non fermerà uno sviluppatore, un attaccante, uno scanner, un'estensione del browser, un log reader o uno strumento di supporto.

Base64 è sicuro per password o chiavi API?

No. Le password vogliono hashing lento come Argon2id, bcrypt o scrypt. Le chiavi API devono restare lato server o in un secrets manager con scope, auditabilità e rotazione.

Chiunque può leggere il payload di un JWT?

Sì, per i JWT firmati ordinari. Header e payload sono JSON codificato in Base64url. La firma può dimostrare l'integrità dopo la verifica, ma non nasconde i claim.

Perché Basic Auth usa Base64 se non è sicuro?

Basic Auth usa Base64 per far entrare username:password in un header HTTP. HTTPS/TLS fornisce la cifratura in transito. Senza HTTPS, le credenziali Basic Auth sono di fatto in chiaro sulla rete.

Base64url è più sicuro di Base64?

No. Base64url sostituisce + e / con - e _ e spesso omette il padding perché il valore funzioni in URL e JWT. Resta comunque codifica reversibile.

Cosa dovrei usare al posto di Base64 per cifrare?

Un design di cifratura autenticata verificato come AES-GCM, XChaCha20-Poly1305 o JWE, più generazione di chiavi, storage delle chiavi, rotazione e controllo di accesso a regola d'arte. Non progettare un tuo formato crittografico.

A cosa serve davvero Base64?

Base64 rappresenta byte come testo: campi binari in JSON, allegati email, data URI, file PEM, firme, formattazione di ciphertext e segmenti di token URL-safe. È un wrapper, non un confine di sicurezza.

Codifica e decodifica nel browser

Devi ispezionare una stringa Base64 subito? Base64 Encode & Decode su fixjson.org ti fa codificare o decodificare testo localmente nel browser, incluso il contenuto UTF-8. Usalo per ispezionare payload JWT, campi di risposta API o data: URI, ma non trattare la decodifica come verifica o decifratura.

Strumenti e guide correlati

Fonti

Ultima revisione luglio 2026.