Stockage des mots de passe
Protéger les mots de passe avec bcrypt et Argon2
Notions théoriques
Pourquoi sécuriser les mots de passe ?
Lorsqu’un utilisateur crée un compte sur un site Web, son mot de passe ne doit jamais être stocké en clair dans la base de données.
Si la base de données est compromise, un attaquant pourrait récupérer tous les mots de passe et les utiliser pour accéder aux comptes des utilisateurs.
Un bon système de stockage des mots de passe doit :
- Empêcher la récupération directe des mots de passe en cas de fuite.
- Rendre les attaques par force brute ou dictionnaire très coûteuses en temps et en ressources.
- Utiliser des algorithmes adaptés et éprouvés pour éviter les failles de sécurité.
Pourquoi ne pas utiliser un simple hachage ?
Un hachage simple (comme SHA-256
) transforme un mot de passe en une empreinte unique, mais il présente plusieurs faiblesses :
- Rapidité excessive : Un attaquant peut tester des milliards de mots de passe par seconde.
- Absence de sel : Deux utilisateurs ayant le même mot de passe auront la même empreinte.
- Vulnérabilité aux attaques par dictionnaire : Les attaquants peuvent précalculer des millions d’empreintes à l’avance (Rainbow Tables).
Les solutions : bcrypt et Argon2
bcrypt
- Algorithme conçu pour stocker les mots de passe de manière sécurisée.
- Ajoute un sel (chaîne aléatoire) pour éviter les empreintes identiques.
- Ralentit volontairement le calcul pour rendre les attaques plus difficiles.
- Paramètre de cost permettant d’ajuster la difficulté du calcul.
Argon2
- Algorithme plus récent, vainqueur du Password Hashing Competition (PHC) en 2015.
- Offre une meilleure résistance aux attaques modernes.
- Permet de régler la mémoire utilisée, le temps de calcul et le nombre de threads.
- Trois variantes : Argon2d (résistant aux attaques GPU), Argon2i (résistant aux attaques par canal auxiliaire), et Argon2id (hybride des deux).
Exemple pratique
Il est possible de hacher un mot de passe en PHP avec bcrypt et Argon2 en utilisant la fonction password_hash()
.
Code PHP pour hacher un mot de passe avec bcrypt et Argon2
<?php
$motDePasse = "MonSuperMotDePasse123";
// Hachage avec bcrypt
$hash_bcrypt = password_hash($motDePasse, PASSWORD_BCRYPT);
// Hachage avec Argon2id
$hash_argon2 = password_hash($motDePasse, PASSWORD_ARGON2ID);
echo "Mot de passe haché avec bcrypt : " . $hash_bcrypt . "<br>";
echo "Mot de passe haché avec Argon2id : " . $hash_argon2;
?>
Vérification d’un mot de passe haché
Une fois un mot de passe stocké, il est possible de le comparer avec une entrée utilisateur grâce à password_verify()
.
<?php
$motDePasseSaisi = "MonSuperMotDePasse123";
if (password_verify($motDePasseSaisi, $hash_bcrypt)) {
echo "Mot de passe correct.";
} else {
echo "Mot de passe incorrect.";
}
?>
Test de mémorisation/compréhension
TP pour réfléchir et résoudre des problèmes
Objectif du TP
Créer un système d’enregistrement et d’authentification sécurisé en utilisant bcrypt pour stocker et vérifier les mots de passe.
Ce TP est divisé en plusieurs étapes :
- Créer un formulaire d’inscription pour permettre aux utilisateurs de s’enregistrer.
- Hacher et enregistrer les mots de passe de manière sécurisée.
- Créer un formulaire de connexion pour permettre aux utilisateurs de s’authentifier.
- Vérifier les identifiants en comparant le mot de passe saisi avec celui stocké.
- Améliorer la sécurité en empêchant les attaques courantes.
Étape 1 : Créer un formulaire d’inscription
Un utilisateur doit pouvoir s’inscrire en fournissant un nom d’utilisateur et un mot de passe.
- Créer un fichier
register.html
. - Ajouter un formulaire contenant :
- Un champ pour le nom d’utilisateur.
- Un champ pour le mot de passe.
- Un bouton pour envoyer les informations.
- Configurer le formulaire pour envoyer les données à
register.php
via la méthodePOST
.
Code du formulaire d’inscription :
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Inscription</title>
</head>
<body>
<h2>Inscription</h2>
<form action="register.php" method="POST">
<label for="username">Nom d'utilisateur :</label>
<input type="text" id="username" name="username" required>
<label for="password">Mot de passe :</label>
<input type="password" id="password" name="password" required>
<button type="submit">S'inscrire</button>
</form>
</body>
</html>
- Le champ
name="username"
permet de récupérer le nom d’utilisateur. - Le champ
name="password"
est utilisé pour le mot de passe. - La
method="POST"
assure que les données ne sont pas visibles dans l’URL.
Étape 2 : Hacher et enregistrer le mot de passe
Une fois le formulaire soumis, il faut traiter les données et enregistrer le mot de passe de manière sécurisée.
- Créer un fichier
register.php
. - Vérifier que la requête est bien envoyée en
POST
. - Récupérer et nettoyer les données saisies.
- Hacher le mot de passe avec bcrypt.
- Enregistrer le nom d’utilisateur et le mot de passe haché dans un fichier
users.txt
.
Code PHP pour enregistrer un utilisateur :
<?php
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$username = trim($_POST["username"]);
$password = trim($_POST["password"]);
if (empty($username) || empty($password)) {
die("Erreur : Tous les champs sont obligatoires.");
}
$hashedPassword = password_hash($password, PASSWORD_BCRYPT);
$file = fopen("users.txt", "a");
fwrite($file, "$username:$hashedPassword\n");
fclose($file);
echo "Utilisateur enregistré avec succès.";
}
?>
trim($_POST["username"])
supprime les espaces inutiles.password_hash($password, PASSWORD_BCRYPT)
hache le mot de passe avec bcrypt.- Les données sont stockées sous la forme
nom_utilisateur:mot_de_passe_haché
dansusers.txt
.
Étape 3 : Créer un formulaire de connexion
L’utilisateur doit maintenant pouvoir se connecter en saisissant ses identifiants.
- Créer un fichier
login.html
. - Ajouter un formulaire avec :
- Un champ pour le nom d’utilisateur.
- Un champ pour le mot de passe.
- Un bouton pour soumettre les informations.
- Configurer le formulaire pour envoyer les données à
login.php
viaPOST
.
Code du formulaire de connexion :
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Connexion</title>
</head>
<body>
<h2>Connexion</h2>
<form action="login.php" method="POST">
<label for="username">Nom d'utilisateur :</label>
<input type="text" id="username" name="username" required>
<label for="password">Mot de passe :</label>
<input type="password" id="password" name="password" required>
<button type="submit">Se connecter</button>
</form>
</body>
</html>
Ce formulaire est similaire à celui de l’inscription, mais il envoie les données à login.php
.
Étape 4 : Vérifier les identifiants
Une fois les informations soumises, il faut comparer le mot de passe saisi avec celui stocké.
- Créer un fichier
login.php
. - Vérifier que la requête est bien en
POST
. - Récupérer et nettoyer les données.
- Lire le fichier
users.txt
pour trouver l’utilisateur correspondant. - Vérifier si le mot de passe saisi correspond au mot de passe haché stocké.
Code PHP pour vérifier les identifiants :
<?php
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$username = trim($_POST["username"]);
$password = trim($_POST["password"]);
if (empty($username) || empty($password)) {
die("Erreur : Tous les champs sont obligatoires.");
}
$users = file("users.txt", FILE_IGNORE_NEW_LINES);
foreach ($users as $user) {
list($storedUsername, $storedPassword) = explode(":", $user);
if ($storedUsername === $username && password_verify($password, $storedPassword)) {
die("Connexion réussie !");
}
}
die("Erreur : Nom d'utilisateur ou mot de passe incorrect.");
}
?>
file("users.txt", FILE_IGNORE_NEW_LINES)
lit chaque ligne du fichier.explode(":", $user)
sépare le nom d’utilisateur et le mot de passe haché.password_verify($password, $storedPassword)
compare le mot de passe saisi avec celui stocké.
Étape 5 : Améliorer la sécurité
- Empêcher les attaques par force brute en limitant le nombre de tentatives.
- Utiliser une base de données au lieu d’un fichier texte pour stocker les utilisateurs.
- Forcer des mots de passe forts en imposant des règles (longueur minimale, caractères spéciaux).