Situation actuelle : PHP2Shell. Deux failles PHP critiques (CVSS 9.2) permettent d'exécuter du code malveillant sans authentification. Correctifs disponibles depuis le 21 octobre 2026.Ce qu'il faut faire maintenant →

Stocker les mots de passe en PHP : bien utiliser password_hash

Comment les développeurs stockent les mots de passe en PHP avec password_hash et password_verify, choisissent le bon algorithme et remplacent d'anciens hachages MD5 ou SHA-1 sans réinitialisation de mot de passe pour tous les utilisateurs.

État 7 min de lectureDéveloppement sécurisé

En bref : en PHP, vous stockez les mots de passe avec password_hash() et les vérifiez avec password_verify(). La valeur par défaut PASSWORD_DEFAULT utilise actuellement bcrypt, avec un facteur de coût de 12 depuis PHP 8.4 ; lorsque PHP est compilé avec Argon2, PASSWORD_ARGON2ID est une bonne alternative. Pour migrer d'anciens hachages MD5 ou SHA-1, enveloppez-les immédiatement dans password_hash, puis remplacez-les par un hachage direct à la prochaine connexion.

Ce qu'une exfiltration de base de données a à voir avec vos hachages

Après l'intrusion dans un serveur web, la base de données est l'une des premières cibles. Quiconque peut exécuter du code sur le serveur, comme c'est possible avec PHP2Shell, lit aussi les identifiants de l'application et donc la table des comptes utilisateurs. Que les mots de passe qu'elle contient deviennent lisibles au bout de quelques heures ou de plusieurs années ne se décide pas le jour de l'attaque, mais lors de la programmation.

Un nombre étonnant d'applications stockent encore les mots de passe avec des procédés qui n'ont jamais été conçus pour cet usage : MD5, SHA-1, un simple SHA-256, voire un chiffrement réversible. Ces procédés sont rapides, et c'est précisément le problème. Les attaquants peuvent ainsi tester un très grand nombre de candidats par seconde.

PHP propose depuis longtemps une solution qui calcule lentement, génère automatiquement un sel pour chaque mot de passe et stocke l'algorithme et les paramètres dans le hachage lui-même. Il suffit de l'utiliser de façon cohérente.

password_hash en PHP : bcrypt ou Argon2id ?

password_hash() connaît plusieurs algorithmes. Le choix selon le manuel PHP :

Constante Algorithme Remarque
PASSWORD_DEFAULT actuellement bcrypt Valeur par défaut, peut changer dans les versions futures
PASSWORD_BCRYPT bcrypt Facteur de coût de 12 par défaut depuis PHP 8.4, auparavant 10 ; seuls les 72 premiers octets comptent
PASSWORD_ARGON2I Argon2i depuis PHP 7.2, uniquement si PHP est compilé avec la prise en charge d'Argon2
PASSWORD_ARGON2ID Argon2id depuis PHP 7.3, même condition

L'OWASP recommande Argon2id en premier choix, avec au moins 19 Mio de mémoire, deux itérations et un thread, et bcrypt surtout pour les systèmes existants. Pour la plupart des applications PHP, la différence en pratique est plus petite que celle entre n'importe lequel de ces algorithmes et MD5. Le plus important est d'en utiliser un et d'ajuster régulièrement les paramètres.

Un court test indique si Argon2id est disponible :

<?php
var_dump(defined('PASSWORD_ARGON2ID'));
var_dump(password_algos());

Stocker et vérifier : un modèle complet

L'exemple suivant montre l'inscription et la connexion avec mise à jour automatique des hachages obsolètes. Le contrôle de longueur protège contre la limite de 72 octets de bcrypt :

<?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() compte des octets, pas des caractères. C'est voulu ici, car la limite de bcrypt porte sur des octets. Selon le manuel, la colonne de la base de données devrait pouvoir contenir plus de 60 caractères, 255 étant un bon choix. Ne définissez jamais un sel personnalisé : l'option est obsolète et ignorée depuis PHP 8.0.

Avec le passage à PHP 8.4, le facteur de coût par défaut de bcrypt passe de 10 à 12. Grâce à password_needs_rehash(), les hachages existants sont relevés automatiquement à la prochaine connexion. Sur des systèmes très sollicités, un court test de charge de la connexion s'impose avant la migration.

Astuce

Mesurez le facteur de coût sur votre serveur de production. Une vérification isolée doit consommer un temps de calcul perceptible, tout en restant nettement sous la seconde, afin que les connexions ne deviennent pas un goulot d'étranglement sous charge.

Remplacer d'anciens hachages MD5 et SHA-1

Le véritable défi est généralement l'existant. Vous ne connaissez pas les mots de passe eux-mêmes ; vous ne pouvez donc pas simplement les hacher à nouveau. Attendre que chaque utilisateur se reconnecte laisse les comptes inactifs sans protection. L'OWASP décrit donc une démarche en deux temps :

  1. Envelopper immédiatement : envelopper chaque valeur MD5 ou SHA-1 existante avec password_hash() au moyen d'un script, et la stocker avec un marqueur, par exemple dans une colonne hash_typ.
  2. Vérifier à la connexion : pour les comptes marqués, convertir d'abord la saisie avec l'ancien algorithme, puis la comparer à la valeur enveloppée avec password_verify().
  3. Remplacer directement : après une connexion réussie, générer un hachage normal à partir du texte en clair et supprimer le marqueur.
  4. Traiter le reliquat : passer en réinitialisation de mot de passe les comptes qui possèdent encore des hachages enveloppés après un délai fixé.
<?php
// Étape 1 (une seule fois, par script) : $neu = password_hash($alterMd5Wert, PASSWORD_DEFAULT);
// Étapes 2 et 3 à la connexion :
if ($konto['hash_typ'] === 'md5-umhuellt') {
    $ok = password_verify(md5($eingabe), $konto['passwort_hash']);
    if ($ok) {
        $speichere(password_hash($eingabe, PASSWORD_DEFAULT), 'direkt');
    }
}

Les hachages enveloppés valent mieux que des valeurs MD5 brutes, mais sont plus faibles que des hachages directs. Ils constituent une solution transitoire, pas un objectif. Après un incident susceptible d'avoir touché la base de données, cette migration ne remplace pas la demande faite aux utilisateurs de changer leur mot de passe. Les indices qui signalent un tel incident sont décrits dans Suis-je déjà compromis ?.

Poivre, chiffrement et autres fausses pistes

Certaines idées donnent l'impression d'une meilleure sécurité, mais apportent peu ou nuisent :

  • Les constructions de hachage maison, comme un SHA-256 multiple avec un sel généré soi-même, sont plus rapides que bcrypt et donc plus faibles.
  • Les mots de passe chiffrés sont réversibles. Si la clé se trouve sur le même serveur, le chiffrement protège à peine après une intrusion.
  • Un poivre (pepper), c'est-à-dire une valeur secrète supplémentaire commune à tous les mots de passe, peut constituer une couche additionnelle s'il est conservé séparément de la base de données. Il ne remplace toutefois pas un bon algorithme de hachage et complique les changements ultérieurs.
  • Le pré-hachage avant bcrypt, pour contourner la limite de 72 octets, comporte selon l'OWASP ses propres risques. Une limite de longueur claire ou Argon2id est plus simple.

Pour les règles applicables aux nouveaux mots de passe, un coup d'œil à l'état de l'art s'impose aussi : la longueur protège davantage que des exigences compliquées sur les caractères spéciaux, et les mots de passe très courants ou déjà apparus dans des fuites de données devraient être refusés. Les changements forcés à intervalles fixes, sans motif, conduisent d'après l'expérience plutôt à des schémas prévisibles qu'à des mots de passe plus robustes.

Pour les systèmes d'authentification, les mots de passe ne sont en outre jamais seuls : l'authentification à deux facteurs pour les administrateurs et un frein aux tentatives de connexion massives complètent le stockage. Le guide Protéger les connexions admin contre la force brute traite des deux. Pour les liens de réinitialisation et les codes à usage unique, d'autres règles s'appliquent, voir Nombres aléatoires et jetons sûrs en PHP. Un aperçu d'autres fonctions est donné dans Fonctions de sécurité intégrées de PHP.

Comment PHP SOS aide-t-il concrètement ?

  • Nous vérifions comment votre application et vos extensions stockent les mots de passe, et repérons les procédés obsolètes y compris dans les fonctions annexes.
  • Nous planifions la migration de l'existant avec enveloppement, migration à la connexion et délai pour les comptes restants.
  • Nous ajustons le facteur de coût ou les paramètres d'Argon2 à vos serveurs.
  • Après un incident, nous vous aidons à décider des réinitialisations de mot de passe et à les mettre en œuvre sur le plan technique.

Nous proposons un contrôle de votre logiciel PHP sous forme d'audit de sécurité et test d'intrusion.

Questions fréquentes

Quel algorithme password_hash utilise-t-il avec PASSWORD_DEFAULT ?

Actuellement bcrypt. Le manuel PHP précise que la valeur par défaut peut changer lorsque des algorithmes plus robustes sont ajoutés. La colonne de la base de données devrait donc pouvoir contenir plus de 60 caractères ; le manuel cite 255 comme bon choix.

Faut-il chiffrer les mots de passe plutôt que les hacher ?

Non. Les mots de passe chiffrés peuvent être relus avec la clé, et cette clé se trouve généralement sur le même serveur. Un hachage de mot de passe est à sens unique : l'application ne peut que vérifier si une saisie correspond.

Quel facteur de coût choisir pour bcrypt ?

Depuis PHP 8.4, la valeur par défaut est 12, auparavant 10. L'OWASP indique 10 comme minimum et recommande d'aller aussi haut que vos serveurs le supportent. Mesurez sur votre propre matériel le temps que dure une vérification.

Que signifie la limite de 72 octets de bcrypt ?

bcrypt ne prend en compte que les 72 premiers octets d'un mot de passe ; tout ce qui suit est tronqué. Avec les caractères accentués, un caractère compte souvent deux octets. Limitez donc explicitement la longueur ou utilisez Argon2id si votre PHP est compilé avec cette prise en charge.

Les utilisateurs doivent-ils changer leur mot de passe après une intrusion ?

S'il ne peut être exclu que la base de données ait été exfiltrée, vous devriez le demander. De bons hachages ralentissent considérablement le craquage, mais les mots de passe faibles restent vulnérables. Pour savoir s'il existe en plus une obligation de déclaration, voyez avec votre délégué à la protection des données.

Sources

Contact immédiat

Chaque heure compte. Écrivez-nous maintenant.

Décrivez brièvement votre système, votre version de PHP et ce que vous avez observé. Nous vous répondons, faisons le point et travaillons à distance jusqu'à la clôture et la documentation de l'incident.

Aide immédiate