Vai al contenuto
Allin

Generare correttamente una chiave segreta di applicazione

Pubblicato il 12/08/2026 · 13 min di lettura · Strumenti testo e lingua

Daniel Okonkwo

Daniel OkonkwoSviluppatore front-end e redattore Tech presso Allin

Performance web · Formati di file

Verificato su 4 fonti

Vedi il profilo
In breve

Tre domande risolvono la questione. Uno: quale fonte casuale chiama? Un generatore solido chiama la casualità crittografica della piattaforma — crypto.getRandomValues in un browser, secrets in Python, random_bytes in PHP. Uno dubbio chiama Math.random, documentato come inadatto alla sicurezza: V8 lo implementa con xorshift128+, e una manciata di uscite consecutive basta a ricostruire lo stato interno e prevedere il resto. Questo strumento supera la prova: il suo randStr chiama rngInt, che riempie un Uint32Array con crypto.getRandomValues; il ramo Math.random gira solo se manca crypto, cosa che nessun browser fa. Due: quanta entropia porta l'output? Moltiplica la lunghezza per il logaritmo in base due della dimensione dell'alfabeto. Il set Django è qui esattamente i cinquanta caratteri usati dal get_random_secret_key di Django — minuscole, cifre e !@#$%^&*(-_=+) — quindi una chiave di 50 caratteri porta 50 × log2(50) = 282 bit, contro i circa 128 bit presi di solito come soglia. L'opzione alfanumerica dà 297 bit. Il lato WordPress genera le otto costanti di wp-config su 64 caratteri da un set di 92, che è byte per byte wp_generate_password con entrambi i flag dei caratteri speciali: 418 bit ciascuna. Tre: dove va la chiave dopo? Nessuna delle due risposte serve una volta che è stata incollata in una chat, in un ticket o in un commit: un segreto condiviso è un segreto speso, e l'unico rimedio è generarne uno nuovo e ruotarlo.

Come distinguere un generatore di chiavi segrete solido da uno dubbio, con questo come esempio svolto: quale fonte casuale chiama, come calcolare l'entropia da soli e che cosa si rompe davvero il giorno in cui si ruota la chiave.

Prima domanda: quale fonte casuale

Ogni linguaggio ha due generatori di numeri casuali e non sono intercambiabili. Uno è veloce, deterministico a partire da un seme, e pensato per mescolare una playlist o sfalsare un nuovo tentativo. L'altro è più lento, attinge alla riserva di entropia del sistema operativo ed è destinato a tutto ciò che un attaccante vorrebbe indovinare. In JavaScript sono Math.random e crypto.getRandomValues; in Python, random e secrets; in PHP, mt_rand e random_bytes. Usare il primo dove tocca il secondo è il modo più comune di sbagliare un generatore di chiavi.

Ciò che squalifica Math.random non è che l'output sembri regolare — non lo sembra. È che il generatore è una piccola macchina deterministica il cui stato si può recuperare. V8, il motore di Chrome e di Node, usa xorshift128+, uno stato da 128 bit fatto avanzare da scorrimenti e or-esclusivi. Da una breve sequenza di uscite consecutive quello stato si ricostruisce, e da lì ogni valore futuro è noto e ogni valore passato è riproducibile. Il motore non ha nemmeno l'obbligo di inizializzarlo in modo imprevedibile, e i browser lo hanno storicamente inizializzato da cose che un attaccante può osservare o influenzare.

Questo strumento chiama quello giusto. Leggendo il codice dal pulsante verso l'interno: il gestore di generazione costruisce una stringa carattere per carattere con randStr, randStr sceglie ogni indice con rngInt, e rngInt alloca un Uint32Array da un elemento e lo riempie con crypto.getRandomValues. Esiste un ripiego su Math.random, ma sta dietro un test sull'esistenza di crypto e di crypto.getRandomValues, e getRandomValues è disponibile in ogni contesto di navigazione, anche non sicuro — a differenza di crypto.subtle. In pratica quel ripiego è codice irraggiungibile.

Seconda domanda: quanti bit, e come contarli da soli

L'aritmetica sta su una riga. Se ogni carattere è estratto in modo indipendente e uniforme da un alfabeto di n caratteri, ogni carattere porta log2(n) bit, e una chiave di lunghezza L porta L × log2(n). Tutto qui. Non c'è correzione per «contiene simboli» o «sembra complicata»: la dimensione dell'alfabeto e la lunghezza sono i soli ingressi, ed è per questo che una chiave lunga in minuscole batte una corta piena di punteggiatura.

Applicala all'impostazione predefinita. Il set Django è formato dalle ventisei minuscole, dalle dieci cifre e dai quattordici caratteri !@#$%^&*(-_=+): cinquanta in tutto, senza duplicati, cosa che conta perché un carattere ripetuto restringerebbe in silenzio l'alfabeto effettivo. log2(50) vale 5,644 bit per carattere, quindi la chiave da 50 caratteri predefinita porta 282,2 bit. Passare all'opzione alfanumerica alza l'alfabeto a sessantadue e la chiave a 297,7 bit; l'opzione completa lo alza a settantasei e 312,4 bit. Tutte e tre sono enormi accanto ai 128 bit normalmente citati come sufficienti: ventitré caratteri del set Django li supererebbero già.

Il lato WordPress si misura allo stesso modo. Emette le otto costanti di wp-config — le quattro chiavi e i quattro sali — da 64 caratteri ciascuna, tratte da un set di 92: le sessantadue lettere e cifre, poi !@#$%^&*() e infine -_ []{}<>~`+=,.;:/?|. È carattere per carattere ciò che produce il wp_generate_password di WordPress con entrambi gli argomenti dei caratteri speciali, e 64 × log2(92) dà 417,5 bit per costante. Due dettagli di quel set meritano attenzione: contiene uno spazio, cosa legale e un po' sorprendente, e non contiene né apostrofo né barra rovesciata, ed è per questo che il valore generato entra in define('AUTH_KEY', '…'); senza alcun escape.

Il bias del modulo, detto onestamente

C'è un difetto nell'implementazione e vale la pena enunciarlo con precisione, perché una formulazione vaga sarebbe più allarmante del difetto stesso. Per trasformare un numero casuale a 32 bit in un indice dentro un alfabeto di 50 caratteri, il codice prende il resto: indice uguale a valore modulo 50. Due elevato a trentadue è 4 294 967 296, e non si divide per cinquanta. Il resto è quarantasei, quindi quarantasei dei cinquanta caratteri possono uscire da 85 899 346 dei valori possibili e gli altri quattro da 85 899 345.

I caratteri favoriti sono dunque più probabili degli altri in un rapporto di una parte su 85 899 345, circa 1,2 su cento milioni. Per il set WordPress da 92 caratteri il resto è dodici e il rapporto circa 2,1 su cento milioni. Un generatore che correggesse questo userebbe il campionamento per rifiuto — riestrarre ogni volta che il valore cade nella coda frastagliata — e guadagnerebbe una differenza che nessuna misura su una chiave di 50 caratteri potrebbe rilevare. È una nota a piè di pagina, non un avvertimento.

Il bias sparisce del tutto quando la dimensione dell'alfabeto è una potenza di due, perché allora la divisione è esatta: un alfabeto di 64 caratteri o di 256 valori non ne ha alcuno. È la ragione elegante per cui gran parte del materiale crittografico si esprime in esadecimale o in base64: l'aritmetica è esatta e non resta nulla da discutere.

A cosa serve la chiave, il che decide cosa rompe ruotarla

La documentazione di Django elenca esattamente ciò che dipende da SECRET_KEY: ogni sessione, salvo che si usi il backend di sessione a cache e si sia sostituito l'hash di autenticazione di sessione predefinito; ogni messaggio conservato in un cookie; ogni token di reimpostazione della password; e ogni uso del framework di firma che non passi una chiave propria. La conseguenza è scritta nella stessa pagina: quando una chiave smette di essere SECRET_KEY e non figura in SECRET_KEY_FALLBACKS, tutto questo viene invalidato in un colpo solo.

In pratica significa che tutti vengono disconnessi e che ogni collegamento di reimpostazione già in una casella di posta smette di funzionare: esattamente ciò che si vuole dopo una fuga, ed esattamente ciò che non si vuole un martedì pomeriggio senza motivo. SECRET_KEY_FALLBACKS è il modo documentato per avere entrambe le cose: metti la chiave nuova in SECRET_KEY, tieni la vecchia nella lista di ripiego abbastanza a lungo perché sessioni e collegamenti pendenti scadano, poi rimuovila. Django è esplicito sul fatto che le password degli utenti non derivano dalla chiave e non ne sono toccate in nessun caso.

WordPress non offre alcun periodo di grazia equivalente, e la sua stessa documentazione lo dice in una frase: puoi cambiare le chiavi in qualsiasi momento per invalidare tutti i cookie esistenti, e questo significa che ogni utente dovrà rientrare. Non c'è lista di ripiego; il cambiamento è immediato e totale. La stessa pagina nota che le quattro chiavi sono obbligatorie e i quattro sali solo consigliati, perché WordPress genera da sé i sali se nessuno è definito: una ragione in più per incollarle tutte e otto e non metà.

Dove una chiave ben generata si brucia lo stesso

Il generatore è la parte facile. Quasi tutti gli incidenti reali cominciano dopo. Una chiave finisce in un file di configurazione che viene committato, in uno screenshot incollato in un ticket, in un messaggio a un collega, in un log di build, in una variabile d'ambiente stampata da un endpoint di debug, o in uno strato di immagine di container che sopravvive al ramo da cui proviene. Nessuno di questi è un fallimento crittografico; tutti pongono fine all'utilità della chiave con la stessa completezza.

Il caso del controllo di versione merita una frase a sé, perché l'istinto è sbagliato. Cancellare la riga e committare la cancellazione non toglie la chiave: resta nella cronologia, resta in ogni clone che qualcuno abbia fatto e resta in ogni fork e ogni mirror del repository. Riscrivere la cronologia aiuta solo se invalidi anche ogni copia già scaricata, cosa che non puoi. L'unica risposta solida è considerare la chiave pubblica e ruotarla.

Generare nel browser va benissimo, e meglio di quanto sembri. La chiave è prodotta dalla pagina che hai già aperta, a partire dalla casualità del sistema operativo stesso, ed esiste solo in quella scheda finché non la copi. Nulla viene inviato da nessuna parte, perché nulla ha bisogno di esserlo: estrarre cinquanta indici e unire cinquanta caratteri sta in poche righe, e un server aggiungerebbe soltanto un posto, non ispezionabile, in cui la chiave sarebbe esistita.

Tutte le opzioni dei due generatori, con l'entropia calcolata come lunghezza × log2(alfabeto)
ImpostazioneAlfabetoEntropia
Django predefinito, 50 caratteri50 — minuscole, cifre, !@#$%^&*(-_=+)282,2 bit — byte per byte il get_random_secret_key di Django
Django, alfanumerico, 50 caratteri62 — lettere di entrambi i casi e cifre297,7 bit — sicuro in una variabile di shell, nulla da proteggere
Django, set completo, 50 caratteri76 — il set alfanumerico più i quattordici simboli312,4 bit — 30 bit più del predefinito, senza guadagno pratico
Django, 100 caratteri50 — lo stesso set, il doppio della lunghezza564,4 bit — l'impostazione esiste, il bisogno no
WordPress set completo, 64 caratteri, otto costanti92 — uguale a wp_generate_password con entrambi i flag417,5 bit ciascuna; contiene uno spazio, né apostrofo né barra rovesciata
WordPress solo alfanumerico, 64 caratteri62 — lettere e cifre381,1 bit — 36 bit in meno, e nulla da fare escape da nessuna parte
Qualsiasi generatore che chiami Math.randomQualunque cosa dichiariEffettivamente nulla — lo stato si ricava da poche uscite
Generatore di chiave segreta DjangoGenera una SECRET_KEY Django crittograficamente sicura, con lunghezza e set di caratteri a scelta.Prova lo strumento

Domande frequenti

Una chiave generata nel mio browser è sicura quanto una generata su un server?
Semmai più sicura. crypto.getRandomValues attinge alla stessa entropia del sistema operativo che userebbe un server, quindi la casualità è della stessa qualità. La differenza è il tragitto della chiave: generata nella pagina, esiste in una scheda e non va da nessuna parte finché non la copi, mentre un generatore lato server significa che la chiave è esistita su una macchina che non controlli, in un processo che non puoi ispezionare, forse in una riga di log che non vedrai mai. Ricarica la pagina o chiudi la scheda e il valore è sparito. L'unica cosa da cui un browser non può proteggerti sono gli appunti, e ciò in cui incollerai poi.
Quanto deve essere lunga davvero una chiave segreta?
Prendi il valore predefinito del framework e smetti di pensarci. La funzione di Django restituisce 50 caratteri, che dal suo alfabeto di 50 fanno 282 bit; WordPress emette 64 caratteri da 92, che fanno 418. La soglia solitamente citata per un segreto simmetrico si aggira sui 128 bit, e 23 caratteri dell'alfabeto Django la superano già. La lunghezza in più non costa nulla e non porta nulla, e le impostazioni da 80 o 100 caratteri ci sono per completezza, non perché servano a qualcuno. La lunghezza diventa la leva sbagliata solo quando l'alfabeto è minuscolo: una chiave di 20 caratteri fatta di sole cifre porta 66 bit, meno della metà di quanto porti una di 20 caratteri dal set Django.
Il bias del modulo significa che non dovrei usare questo generatore?
No. Il bias è reale e vale 1,2 su cento milioni sull'alfabeto Django: quarantasei dei cinquanta caratteri sono raggiungibili da 85 899 346 dei quattro miliardi di valori a 32 bit possibili, e gli altri quattro da 85 899 345. Distribuito su cinquanta estrazioni indipendenti, sposta l'entropia di una chiave di una quantità ben sotto qualsiasi unità che si stamperebbe. Ciò per cui il bias serve davvero è come indicatore quando leggi il generatore di qualcun altro: vedere un modulo nudo ti dice che nessuno ha pensato al campionamento per rifiuto, il che è un indizio sul resto del codice, e vedere il campionamento per rifiuto ti dice che qualcuno ci ha pensato. Nessuna delle due cose è un motivo per rifiutare una chiave già profonda 282 bit.
Che cosa si rompe nel momento in cui cambio la chiave?
In Django: ogni sessione che non stia nel backend a cache, ogni messaggio conservato in un cookie e ogni token di reimpostazione della password, compresi i collegamenti già presenti nelle caselle di posta. La documentazione lo dice chiaramente e indica la mitigazione: sposta la vecchia chiave in SECRET_KEY_FALLBACKS, lasciacela abbastanza a lungo perché sessioni e collegamenti pendenti scadano da soli, poi cancellala. Le password degli utenti non derivano dalla chiave e restano intatte. In WordPress non c'è lista di ripiego: cambiare una qualsiasi delle otto costanti invalida subito ogni cookie esistente, e la sua documentazione dice a chiare lettere che tutti gli utenti dovranno rientrare. Programma un cambio di chiave in un'ora tranquilla, a meno che tu non stia rispondendo a una fuga, nel qual caso fallo adesso e accetta le disconnessioni.
Ho committato la mia chiave su git. Basta cancellare la riga?
No. Un commit che cancella una riga lascia intatto il commit precedente, quindi la chiave resta nella cronologia e resta in ogni clone, fork e mirror già esistente. Riscrivere la cronologia la toglie dalla tua copia e da chi scarica dopo, ma non da chi ha scaricato prima — e su un repository pubblico va dato per scontato che ciò includa scanner automatici, che sorvegliano i nuovi commit proprio per questo. Considera la chiave pubblicata: generane una nuova, metti la vecchia nella lista di ripiego se il tuo framework ne ha una, e rimuovila passata la finestra di transizione. Poi rendi la fuga strutturalmente impossibile tenendo il valore in una variabile d'ambiente o in un archivio di segreti e committando solo un file che nomini la variabile.

Articoli che potrebbero interessarti

Tutte le guide

Strumenti correlati

Tutto quanto segue descrive ciò che questi quattro strumenti fanno oggi, verificato eseguendo il loro stesso codice sugli input esatti riportati in ogni articolo, e non ciò che una norma imponga loro. Dove uno strumento sbaglia un caso, la cosa è scritta chiaramente anziché aggirata, e nulla è stato modificato perché un articolo si leggesse meglio. Due conseguenze. Esegui ogni trasformazione prima su una copia e confronta i due estremi: uno strumento di testo che cancella qualcosa non lo dice. E considera condiviso un segreto dal momento in cui lascia la pagina: incollarlo in una chat, in un ticket o in un repository lo brucia, per quanto bene sia stato generato.

Fonti

Hai notato un errore in questo articolo?