# Numeri casuali e token sicuri in PHP con random_bytes

> Generare token sicuri in PHP con random_bytes e random_int: lunghezza, codifica, salvataggio come hash, scadenza e funzioni da evitare.

Fonte: https://phpsos.de/it/blog/sichere-tokens-php/  
Stato: 25/10/2026  
Fornitore: PHP SOS, Assistenza di emergenza PHP e sicurezza dei server. Assistenza immediata su WhatsApp: https://wa.me/491736494914?text=Buongiorno%20PHP%20SOS%2C%20abbiamo%20bisogno%20di%20aiuto%20con%20il%20nostro%20server%20PHP.%20Azienda%3A%20%E2%80%A6%20Sito%20web%3A%20%E2%80%A6

**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()`.

## Un link troppo facile da indovinare

«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
<?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
<?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](https://phpsos.de/it/blog/passwoerter-in-php-speichern/).

> **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](https://phpsos.de/it/ratgeber/session-sicherheit-php/). Per i token dei moduli contro le richieste falsificate vedi [Protezione CSRF nelle applicazioni PHP](https://phpsos.de/it/blog/csrf-schutz-php/). Altri strumenti integrati li presenta [Funzioni di sicurezza integrate di PHP](https://phpsos.de/it/php/eingebaute-sicherheitsfunktionen/).

## 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](https://phpsos.de/it/leistungen/sicherheitsaudit-penetrationstest/).


## 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

- [Manuale PHP: random_bytes](https://www.php.net/manual/de/function.random-bytes.php)
- [Manuale PHP: Random\Randomizer::getBytesFromString](https://www.php.net/manual/de/random-randomizer.getbytesfromstring.php)
- [Manuale PHP: hash_equals](https://www.php.net/manual/de/function.hash-equals.php)
- [OWASP Cheat Sheet Series](https://cheatsheetseries.owasp.org/)
