Situazione attuale: PHP2Shell. Due vulnerabilità critiche di PHP (CVSS 9,2) consentono codice dannoso senza autenticazione. Patch dal 21/10/2026.Che cosa fare ora →

Numeri casuali e token sicuri in PHP con random_bytes

Come gli sviluppatori generano in PHP token per link di password, chiavi API e codici monouso: fonti crittograficamente sicure, lunghezza adeguata, salvataggio come hash e validità limitata.

Stato 6 min di letturaSviluppo sicuro

In breve: i token sicuri in PHP si generano con random_bytes(), ad esempio 32 byte, che si convertono in testo con bin2hex() o Base64-URL. Per i numeri casuali in un intervallo esiste random_int(), per i codici a partire da un insieme fisso di caratteri, da PHP 8.3, Random\Randomizer::getBytesFromString(). I token si salvano come hash, se ne limita la validità, si usano una sola volta e si confrontano con hash_equals().

«Password dimenticata?» Un clic, e il sistema invia un link con un lungo codice. Chi conosce il link può impostare la password. Proprio per questo il codice deve essere imprevedibile. Se non lo è, a un aggressore non servono né la casella di posta né la vecchia password, solo abbastanza tentativi.

Nelle verifiche delle applicazioni PHP si trovano sorprendentemente spesso token costruiti con l'ora, l'ID utente o un semplice generatore di numeri casuali. Sembrano lunghi e casuali, ma non lo sono. Lo stesso vale per chiavi API, link di invito, link di condivisione di documenti, codici di conferma e token CSRF.

Il rischio è concreto: un link di reset prevedibile consente di prendere il controllo di qualsiasi account il cui indirizzo e-mail sia noto, senza che il titolare si accorga di nulla, finché non riesce più ad accedere.

PHP offre dalla versione 7.0 una soluzione semplice e sicura. Basta usarla con coerenza.

random_bytes in PHP: da dove provengono i valori casuali sicuri

Secondo il manuale PHP random_bytes() fornisce byte casuali crittograficamente sicuri, adatti anche a segreti di lunga durata come le chiavi. La funzione usa la fonte di casualità del sistema operativo, ad esempio getrandom() su Linux e l'interfaccia CNG su Windows. Se non trova una fonte adatta, da PHP 8.2 genera una Random\RandomException.

La panoramica mostra quali funzioni sono adatte ai segreti:

Funzione Adatta ai token? Nota
random_bytes() sì Byte sicuri, da PHP 7.0
random_int() sì Numero sicuro in un intervallo, da PHP 7.0
Random\Randomizer con motore Secure sì Secondo il manuale Secure è il motore predefinito
rand(), mt_rand() no Prevedibili, solo per scopi non rilevanti per la sicurezza
uniqid() no Basata sul tempo, pensata per l'unicità
md5(time()), sha1($id) no Input indovinabile, l'hash non cambia nulla
str_shuffle(), array_rand() no Non usano un generatore sicuro

Generare i token: lunghezza e codifica

32 byte casuali equivalgono a 256 bit e per quasi ogni scopo sono più che sufficienti. Per la trasmissione in link o header il token va convertito in testo. Due varianti sono comuni:

<?php
declare(strict_types=1);

// Hex: 64 caratteri, solo 0-9 e a-f
$tokenHex = bin2hex(random_bytes(32));

// Base64-URL: 43 caratteri, sicuro negli URL senza ulteriore codifica
$tokenUrl = rtrim(strtr(base64_encode(random_bytes(32)), '+/', '-_'), '=');

// Numero casuale in un intervallo, ad esempio per una selezione
$index = random_int(0, 9);

// Da PHP 8.3: codice numerico di sei cifre, ad es. per una conferma via SMS
$randomizer = new \Random\Randomizer();
$code = $randomizer->getBytesFromString('0123456789', 6);

I codici numerici brevi sono accettabili solo se il numero di tentativi è rigorosamente limitato e il codice scade dopo pochi minuti. Per i link inviati per e-mail usi sempre il token lungo.

Per le chiavi API che partner o sistemi propri usano in modo duraturo si sono rivelate valide due integrazioni. Un breve prefisso non segreto davanti alla parte casuale, ad esempio per ambiente o scopo, facilita il riconoscimento nei log e nei ritrovamenti nei repository di codice. E la chiave viene mostrata una sola volta al momento della creazione; dopo, il sistema ne conosce solo l'hash. Se una chiave va persa, la si sostituisce, non la si ripristina.

Attenzione ai token costruiti in proprio che contengono informazioni, ad esempio ID utente e scadenza in Base64. La codifica non è una protezione: chiunque può leggere e modificare tali token. Se un token deve trasportare dati, deve essere firmato con una chiave segreta, ad esempio con hash_hmac('sha256', ...), e la firma va verificata con hash_equals(). Per i casi più complessi sono preferibili librerie collaudate a costruzioni proprie.

Salvare, verificare e far scadere i token

Un token generato in modo sicuro è solo metà dell'opera. Altrettanto importante è come l'applicazione lo gestisce. Un procedimento collaudato per i link di reset e di invito:

  1. Salvare solo l'hash: nel database c'è hash('sha256', $token), il token stesso va all'utente solo tramite il link.
  2. Limitare la validità: salvare anche la scadenza e verificarla all'utilizzo.
  3. Usare una sola volta: dopo l'uso riuscito cancellare la voce o contrassegnarla come consumata.
  4. Invalidare in caso di modifiche: dopo un cambio di password cancellare tutti i token di reset aperti dell'account.
  5. Confrontare in tempo costante: dove un token viene confrontato direttamente con un valore salvato, usare hash_equals(), con il valore salvato come primo parametro.
<?php
// Generare
$token = bin2hex(random_bytes(32));
$eintrag = [
    'konto_id'   => $kontoId,
    'token_hash' => hash('sha256', $token),
    'gueltig_bis'=> (new DateTimeImmutable('+30 minutes'))->format('Y-m-d H:i:s'),
];
// inviare $token solo nel link, salvare $eintrag

// Riscattare
$gesucht = hash('sha256', (string) ($_GET['token'] ?? ''));
// cercare la voce tramite token_hash con prepared statement,
// verificare la scadenza, poi cancellare subito la voce

Limiti inoltre il numero di richieste di reset per account e periodo, affinché la funzione non possa essere usata in modo improprio per l'invio massivo di e-mail. La risposta a una richiesta di reset non dovrebbe poi rivelare se l'indirizzo indicato abbia davvero un account.

Un hash veloce come SHA-256 qui basta, perché il token stesso è già lungo e casuale. Per le password scelte dalle persone non vale lo stesso; lì servono algoritmi lenti, come spiega Salvare le password in PHP in sicurezza.

Consiglio

I token non appartengono ai file di log. Verifichi se il suo server web registra URL completi con parametri di query e se i rapporti di errore inviano con sé i dati della richiesta.

Dopo un incidente: rinnovare token e chiavi

Chi ha potuto eseguire codice su un server di norma ha avuto accesso anche a token, chiavi API e dati di sessione salvati. Se dopo PHP2Shell o un altro incidente c'è il sospetto di un'intrusione, dovrebbe quindi rinnovare non solo le password, ma anche token e chiavi di lunga durata: accessi API, chiavi di firma per cookie o link, accessi a servizi di pagamento e spedizione.

Le sessioni sono un caso particolare: PHP genera da sé gli ID di sessione. Come mettere in sicurezza le relative impostazioni e terminare le sessioni dopo un incidente lo descrive la guida Mettere in sicurezza le sessioni in PHP. Per i token dei moduli contro le richieste falsificate vedi Protezione CSRF nelle applicazioni PHP. Altri strumenti integrati li presenta Funzioni di sicurezza integrate di PHP.

Come aiuta concretamente PHP SOS?

  • Troviamo nella sua applicazione tutti i punti in cui vengono generati token, codici e chiavi e sostituiamo le fonti insicure.
  • Verifichiamo salvataggio, scadenza e uso singolo dei link di reset, invito e condivisione.
  • Facciamo in modo che i token non finiscano in log, rapporti di errore o strumenti di analisi.
  • Dopo un incidente pianifichiamo con Lei la sostituzione ordinata di token e chiavi.

La verifica del software PHP proprio la offriamo come audit di sicurezza e penetration test.

Domande frequenti

Perché mt_rand non è adatto ai token?

mt_rand e rand sono pensati per statistica e giochi, non per i segreti. I loro output in certi casi si possono prevedere. Per tutto ciò che un aggressore non deve indovinare usi random_bytes o random_int.

Quanto dovrebbe essere lungo un token?

Per i link di reimpostazione delle password, gli inviti o le chiavi API, 32 byte casuali sono una scelta solida, in testo esadecimale 64 caratteri. 16 byte, cioè 128 bit, sono un limite inferiore diffuso.

uniqid è un token sicuro?

No. uniqid si basa sull'ora corrente ed è pensato per l'unicità, non per la riservatezza. Anche combinazioni come md5 di ora o ID utente sono prevedibili.

Devo cifrare nel database i token di reset?

Salvi al posto del token un suo hash, ad esempio con SHA-256. Chi legge il database non può così costruire link validi. Poiché il token stesso è già lungo e casuale, non serve un algoritmo lento come per le password.

Che cosa succede se il sistema non trova alcuna fonte di casualità?

random_bytes e random_int da PHP 8.2 generano una Random\RandomException. Intercetti questo caso e interrompa l'operazione, anziché ripiegare su una fonte più debole.

Fonti

Contatto immediato

Ogni ora conta. Ci scriva adesso.

Descriva brevemente sistema, versione di PHP e quanto osservato. Ci faremo sentire, chiariremo la situazione e lavoreremo da remoto fino alla chiusura e alla documentazione dell'incidente.

Aiuto immediato