# Salvare le password in PHP in sicurezza: usare bene password_hash

> Salvare le password in PHP in sicurezza con password_hash: bcrypt o Argon2id, fattore di costo, limite di 72 byte e passaggio dai vecchi hash MD5.

Fonte: https://phpsos.de/it/blog/passwoerter-in-php-speichern/  
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:** in PHP le password si salvano con `password_hash()` e si verificano con `password_verify()`. L'impostazione predefinita `PASSWORD_DEFAULT` usa attualmente bcrypt, da PHP 8.4 con fattore di costo 12; dove PHP è compilato con Argon2, `PASSWORD_ARGON2ID` è una buona alternativa. I vecchi hash MD5 o SHA-1 si sostituiscono incorporandoli subito in password_hash e rimpiazzandoli con un hash diretto al login successivo.

## Che cosa c'entra una fuga di dati dal database con i suoi hash

Dopo un'intrusione in un server web, il database è uno dei primi obiettivi. Chi, come possibile con PHP2Shell, può eseguire codice sul server legge anche le credenziali dell'applicazione e quindi la tabella con gli account utente. Se le password al suo interno diventino leggibili dopo ore o dopo anni non si decide il giorno dell'attacco, ma al momento della programmazione.

Sorprendentemente molte applicazioni salvano ancora le password con procedure mai pensate per questo scopo: MD5, SHA-1, un semplice SHA-256 o addirittura cifrate in modo reversibile. Queste procedure sono veloci, ed è proprio questo il problema. Gli aggressori possono così provare moltissimi candidati al secondo.

Da tempo PHP offre una soluzione che calcola lentamente, genera automaticamente un salt per ogni password e memorizza procedura e parametri nell'hash stesso. Basta usarla con coerenza.

## password_hash in PHP: bcrypt o Argon2id?

`password_hash()` conosce più algoritmi. La scelta secondo il manuale PHP:

| Costante | Algoritmo | Nota |
|---|---|---|
| `PASSWORD_DEFAULT` | attualmente bcrypt | Predefinito, può cambiare nelle versioni future |
| `PASSWORD_BCRYPT` | bcrypt | Fattore di costo predefinito 12 da PHP 8.4, in precedenza 10; contano solo i primi 72 byte |
| `PASSWORD_ARGON2I` | Argon2i | Da PHP 7.2, solo se PHP è compilato con il supporto Argon2 |
| `PASSWORD_ARGON2ID` | Argon2id | Da PHP 7.3, stesso presupposto |

OWASP raccomanda Argon2id come prima scelta, con almeno 19 MiB di memoria, due iterazioni e un thread, e bcrypt soprattutto per i sistemi esistenti. Per la maggior parte delle applicazioni PHP la differenza pratica è minore di quella tra uno qualunque di questi algoritmi e MD5. Più importante è usarne comunque uno e adeguare regolarmente i parametri.

Se Argon2id sia disponibile lo mostra un breve test:

```php
<?php
var_dump(defined('PASSWORD_ARGON2ID'));
var_dump(password_algos());
```

## Salvare e verificare: uno schema completo

L'esempio seguente mostra registrazione e login con aggiornamento automatico degli hash obsoleti. Il controllo della lunghezza protegge dal limite di 72 byte di bcrypt:

```php
<?php
declare(strict_types=1);

const PASSWORT_ALGO = PASSWORD_DEFAULT;

function passwortSpeichern(string $passwort): string
{
    if (strlen($passwort) > 72) {
        throw new InvalidArgumentException('Passwort zu lang für das gewählte Verfahren.');
    }
    return password_hash($passwort, PASSWORT_ALGO);
}

function passwortPruefen(string $eingabe, string $gespeicherterHash, callable $hashAktualisieren): bool
{
    if (!password_verify($eingabe, $gespeicherterHash)) {
        return false;
    }
    if (password_needs_rehash($gespeicherterHash, PASSWORT_ALGO)) {
        $hashAktualisieren(password_hash($eingabe, PASSWORT_ALGO));
    }
    return true;
}
```

`strlen()` conta byte, non caratteri. Qui è voluto, perché il limite di bcrypt si riferisce ai byte. Secondo il manuale la colonna nel database dovrebbe contenere più di 60 caratteri, 255 sono una buona scelta. Non imposti mai un salt proprio: l'opzione è obsoleta e dal PHP 8.0 viene ignorata.

Con il passaggio a PHP 8.4 il fattore di costo predefinito per bcrypt sale da 10 a 12. Grazie a `password_needs_rehash()` gli hash esistenti vengono innalzati automaticamente al login successivo. Sui sistemi molto carichi, prima del passaggio vale la pena fare un breve test di carico del login.

> **Consiglio:**
> Misuri il fattore di costo sul suo server di produzione. Una singola verifica dovrebbe richiedere un tempo di calcolo percepibile, ma restare nettamente sotto il secondo, affinché i login non diventino un collo di bottiglia sotto carico.

## Sostituire i vecchi hash MD5 e SHA-1

La vera sfida è di solito il patrimonio esistente. Le password in sé non le conosce, quindi non può semplicemente ricalcolarne l'hash. Aspettare che ogni utente acceda di nuovo lascia gli account inattivi privi di protezione. OWASP descrive perciò un procedimento in due fasi:

1. **Incorporare subito:** avvolgere ogni valore MD5 o SHA-1 esistente con `password_hash()` tramite script e salvarlo con un contrassegno, ad esempio in una colonna `hash_typ`.
2. **Verificare al login:** per gli account contrassegnati, calcolare prima l'immissione con il vecchio algoritmo e poi verificarla con `password_verify()` rispetto al valore avvolto.
3. **Sostituire direttamente:** dopo un login riuscito generare un normale hash dal testo in chiaro e rimuovere il contrassegno.
4. **Ripulire il residuo:** impostare il reset della password per gli account che dopo un termine stabilito hanno ancora hash avvolti.

```php
<?php
// Fase 1 (una tantum tramite script): $neu = password_hash($alterMd5Wert, PASSWORD_DEFAULT);
// Fasi 2 e 3 al login:
if ($konto['hash_typ'] === 'md5-umhuellt') {
    $ok = password_verify(md5($eingabe), $konto['passwort_hash']);
    if ($ok) {
        $speichere(password_hash($eingabe, PASSWORD_DEFAULT), 'direkt');
    }
}
```

Gli hash avvolti sono meglio dei valori MD5 nudi, ma più deboli degli hash diretti. Sono una soluzione transitoria, non un traguardo. Dopo un incidente in cui il database potrebbe essere stato coinvolto, questo passaggio non sostituisce l'invito agli utenti a cambiare le password. Quali tracce indichino un incidente del genere lo descrive [Sono già compromesso?](https://phpsos.de/it/php2shell/bin-ich-gehackt-anzeichen/).

## Pepper, cifratura e altre strade sbagliate

Alcune idee suonano come più sicurezza, ma portano poco o danneggiano:

- **Costruzioni di hash proprie**, come SHA-256 ripetuto con un salt generato da sé, sono più veloci di bcrypt e quindi più deboli.
- **Le password cifrate** sono reversibili. Se la chiave si trova sullo stesso server, dopo un'intrusione la cifratura protegge ben poco.
- **Un pepper**, cioè un valore segreto aggiuntivo per tutte le password, può essere un livello in più se conservato separatamente dal database. Non sostituisce però un buon algoritmo di hash e rende più difficili i cambi futuri.
- **L'hashing preliminare prima di bcrypt**, per aggirare il limite di 72 byte, comporta secondo OWASP rischi propri. Un limite di lunghezza chiaro o Argon2id è più semplice.

Anche per le regole sulle nuove password vale la pena guardare allo stato dell'arte: la lunghezza protegge più di prescrizioni complicate sui caratteri speciali, e le password molto comuni o già comparse in fughe di dati andrebbero rifiutate. I cambi forzati a intervalli fissi senza motivo portano, per esperienza, più a schemi prevedibili che a password più robuste.

Per i sistemi di login, inoltre, le password non stanno mai da sole: metodi a due fattori per gli admin e un freno contro i tentativi di accesso di massa completano il salvataggio. Entrambi sono trattati nella guida [Proteggere i login admin dal brute force](https://phpsos.de/it/ratgeber/brute-force-login-schutz/). Per i link di reset e i codici monouso valgono altre regole, vedi [Numeri casuali e token sicuri in PHP](https://phpsos.de/it/blog/sichere-tokens-php/). Una panoramica di altre funzioni la offre [Funzioni di sicurezza integrate di PHP](https://phpsos.de/it/php/eingebaute-sicherheitsfunktionen/).

## Come aiuta concretamente PHP SOS?

- Verifichiamo come la sua applicazione e i suoi plugin salvano le password e individuiamo algoritmi obsoleti anche nelle funzioni secondarie.
- Pianifichiamo il passaggio del patrimonio esistente con incorporamento, migrazione al login e termine per gli account residui.
- Adattiamo il fattore di costo o i parametri di Argon2 ai suoi server.
- Dopo un incidente assistiamo nella decisione sui reset delle password e nella loro attuazione tecnica.

Una verifica del software PHP proprio la offriamo come [audit di sicurezza e penetration test](https://phpsos.de/it/leistungen/sicherheitsaudit-penetrationstest/).


## Domande frequenti

**Quale algoritmo usa password_hash con PASSWORD_DEFAULT?**  
Attualmente bcrypt. Il manuale PHP segnala che l'impostazione predefinita può cambiare quando arrivano algoritmi più robusti. Per questo la colonna del database dovrebbe contenere più di 60 caratteri; il manuale indica 255 come buona scelta.

**Dovrei cifrare le password invece di calcolarne l'hash?**  
No. Le password cifrate si possono rileggere con la chiave, e questa chiave di solito si trova sullo stesso server. Un hash di password è a senso unico: l'applicazione può solo verificare se un'immissione corrisponde.

**A quanto dovrebbe essere impostato il fattore di costo di bcrypt?**  
Da PHP 8.4 il valore predefinito è 12, in precedenza 10. OWASP indica 10 come limite inferiore e raccomanda di salire quanto i suoi server tollerano. Misuri sul suo hardware quanto dura una verifica.

**Che cosa significa il limite di 72 byte di bcrypt?**  
bcrypt considera solo i primi 72 byte di una password; tutto ciò che segue viene troncato. Con le vocali accentate un carattere spesso vale due byte. Limiti quindi esplicitamente la lunghezza oppure usi Argon2id, se il suo PHP è compilato con tale supporto.

**Dopo un'intrusione gli utenti devono cambiare la password?**  
Se non si può escludere che il database sia stato sottratto, dovrebbe disporlo. Hash validi rallentano molto la decifrazione, ma le password deboli restano attaccabili. Se esista anche un obbligo di notifica lo chiarisca con il suo responsabile della protezione dei dati.

## Fonti

- [Manuale PHP: password_hash](https://www.php.net/manual/de/function.password-hash.php)
- [OWASP Password Storage Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html)
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
