Stockage des mots de passe
Sommaire
Défi n°2 du TP Cyber-Rescue : passer d'un stockage en clair à un stockage conforme aux recommandations ANSSI / OWASP.
1. Le constat
SELECT pseudo, mdp FROM joueurs LIMIT 5;
Si la colonne mdp affiche des mots de passe lisibles, ou des chaînes de 32 caractères
hexadécimaux (signature d'un MD5 sans sel), le stockage est inacceptable.
- Un mot de passe en clair est directement exploitable dès que la base fuite.
- La majorité des utilisateurs réutilisant leurs mots de passe, la fuite compromet leurs autres comptes.
- Cela constitue un manquement à l'obligation de sécurité de l'article 32 du RGPD.
- MD5 et SHA-1 sont proscrits pour cet usage : trop rapides à calculer, donc trop faciles à casser en masse.
L'ANSSI et l'OWASP recommandent une fonction de dérivation lente et salée : bcrypt, scrypt ou Argon2.
2. Hachage, pas chiffrement
| Chiffrement | Hachage | |
|---|---|---|
| Réversible | Oui, avec la clé | Non, par construction |
| Usage | Protéger une donnée qu'il faudra relire | Vérifier une correspondance sans stocker l'original |
| Pour un mot de passe | Inadapté : la clé est sur le serveur, donc récupérable avec la base | Adapté : même compromise, la base ne rend pas les mots de passe |
On ne « décrypte » jamais un mot de passe : on rehache la saisie et l'on compare les empreintes. C'est aussi pourquoi un site sérieux ne peut pas vous renvoyer votre mot de passe par mail — il ne le connaît pas.
3. Le sel
Un sel est une valeur aléatoire, unique par utilisateur, ajoutée au mot de passe avant hachage. Il est stocké en clair à côté du hash — son rôle n'est pas d'être secret.
Sans sel, deux utilisateurs ayant le même mot de passe produisent le même hash. Un attaquant peut alors :
- repérer d'un coup d'œil les mots de passe partagés ;
- utiliser des rainbow tables précalculées ;
- casser tous les comptes d'un seul calcul.
Avec un sel unique, chaque mot de passe doit être attaqué séparément, ce qui multiplie le coût par le nombre de
comptes. bcrypt génère et intègre automatiquement un sel : il est inclus dans la chaîne produite
par password_hash(), aux côtés de l'algorithme et du coût. Rien à gérer manuellement.
$2y$10$N9qo8uLOickgx2ZMRZoMye....
│ │ └── sel (22 caractères) puis empreinte
│ └───── coût (2^10 itérations)
└───────── identifiant d'algorithme (bcrypt)
4. Migrer une base existante
<?php
// migrate_passwords.php — à exécuter une seule fois, après sauvegarde de la base
require 'db.php';
$users = $pdo->query("SELECT id, mdp FROM joueurs")->fetchAll(PDO::FETCH_ASSOC);
$maj = $pdo->prepare("UPDATE joueurs SET mdp = :hash WHERE id = :id");
foreach ($users as $u) {
// Ne pas rehacher un mot de passe déjà haché
if (password_get_info($u['mdp'])['algo']) {
continue;
}
$hash = password_hash($u['mdp'], PASSWORD_BCRYPT);
$maj->execute([':hash' => $hash, ':id' => $u['id']]);
}
- Allonger la colonne
mdpà 255 caractères avant la migration : un hash bcrypt fait 60 caractères, et les algorithmes futurs seront plus longs. - Sauvegarder la base avant : l'opération est irréversible.
- Vérifier ensuite qu'aucune ligne ne contient encore de valeur en clair, et purger les journaux qui auraient pu enregistrer les anciennes valeurs.
5. bcrypt et Argon2
| bcrypt | Argon2id | |
|---|---|---|
| Année | 1999 | 2015, lauréat de la Password Hashing Competition |
| Paramètre de coût | Temps (nombre d'itérations) | Temps, mémoire et parallélisme |
| Résistance GPU / ASIC | Limitée : faible empreinte mémoire, donc très parallélisable | Forte : la consommation mémoire imposée rend le calcul massivement parallèle coûteux |
| PHP | PASSWORD_BCRYPT | PASSWORD_ARGON2ID (PHP 7.3+) |
C'est le paramètre mémoire qui explique le remplacement progressif de bcrypt : une carte graphique peut mener des milliers de calculs bcrypt en parallèle, mais si chaque calcul exige plusieurs dizaines de mégaoctets, le parallélisme s'effondre.
$hash = password_hash($mdp, PASSWORD_ARGON2ID, [
'memory_cost' => 65536, // 64 Mo
'time_cost' => 4,
'threads' => 2,
]);
Utiliser PASSWORD_DEFAULT plutôt qu'un algorithme figé permet à PHP de basculer vers
un algorithme plus robuste au fil des versions. À coupler avec password_needs_rehash() lors de la
connexion, pour remettre à niveau les anciens hachages de façon transparente.