First commit

This commit is contained in:
Johan
2025-12-18 15:23:38 +01:00
commit 965ed9aaba
7 changed files with 2307 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/.idea

115
README.md Normal file
View File

@@ -0,0 +1,115 @@
# Démonstration : sécurisation des mots de passe (MD5 vs Argon2)
## Objectif
Comprendre pourquoi les algorithmes de hachage rapides comme MD5 ne doivent **jamais** être utilisés pour stocker des mots de passe. Implémenter la méthode moderne et sécurisée, **Argon2id**, de manière asynchrone pour ne pas bloquer le serveur.
Nous allons utiliser le routeur `routes/users.js` de notre application Express.
Voici un aperçu du flux général de l'application utilisateur → serveur :
```mermaid
flowchart TD
client["client http"]
router["routes /users"]
md5["route /users/register-bad hash md5"]
argon["route /users/register-good hash argon2id"]
client --> router
router --> md5
router --> argon
```
---
### Étape 1 : L'erreur (MD5) - ne faites pas ça ! (register)
L'objectif ici est de simuler ce qu'il ne faut **pas** faire.
Ouvrez le fichier `routes/users.js` et constatez que la route `POST /users/register-bad` est déjà créée pour vous.
Elle utilise l'algorithme de hachage MD5, qui est **inadapté** pour les mots de passe :
```javascript
// Indice pour le hachage MD5
const hash = crypto.createHash('md5').update(password).digest('hex');
```
### Étape 2 : La preuve du danger (register)
1. Démarrez votre serveur Express :
```bash
nodemon ./bin/www
```
1. Avec un outil comme Postman, faites un appel `POST` sur `http://localhost:3000/users/register-bad` avec le JSON suivant :
```json
{
"username": "testeur",
"password": "123456"
}
```
2. **Notez le hash renvoyé.** (Il devrait être `e10adc3949ba59abbe56e057f20f883e`. Vous pouvez le confirmer également via ce site : https://www.md5hashgenerator.com/).
3. Allez sur le site [crackstation.net](https://crackstation.net/).
4. Collez ce hash dans leur outil. Que se passe-t-il ?
---
### Étape 3 : la correction (Argon2id Asynchrone)
Maintenant, implémentons la bonne pratique. Argon2 est conçu pour être lent et gourmand en mémoire, le rendant résistant aux attaques. Nous devons **absolument** l'utiliser en asynchrone pour ne pas bloquer le *thread* principal de Node.js.
1. Installez la bibliothèque recommandée :
```bash
npm install argon2
```
2. Dans `routes/users.js`, importez `argon2` :
```javascript
const argon2 = require('argon2');
```
3. Complétez la route `POST /users/register-good` :
* Cette route **doit** être `async`.
* Récupérez `username` et `password` depuis `req.body`.
* Utilisez `await argon2.hash(...)` pour hacher le mot de passe.
* Utilisez un bloc `try...catch` pour gérer les erreurs de hachage.
* Renvoie le nouveau hachage sécurisé.
```mermaid
sequenceDiagram
participant c as client
participant u as route /register-good
participant a as argon2id
c->>u: envoie username + password
u->>a: "hash du mot de passe"
a-->>u: "hash argon2id"
u-->>c: "hash retourné"
```
### Étape 4 : la vérification (login)
Pour un cycle complet, testez les routes de connexion suivantes :
1. `POST /users/login-bad` :
* Récupère le mot de passe, le hache en MD5.
* Compare le hash obtenu avec un hash "stocké" (ex: `e10adc3949ba59abbe56e057f20f883e`).
* *Problème :* C'est une simple comparaison de chaînes de caractères.
2. `POST /users/login-good` : implémentation à faire !
* Route `async`.
* Récupère le hash "stocké" (celui de l'étape 3) et le mot de passe de la tentative.
* Utilise `await argon2.verify(hashStocke, motDePasseTentative)`.
* Cette fonction renvoie `true` ou `false`.
```mermaid
sequenceDiagram
participant c as client
participant u as route /login-good
participant a as argon2id
c->>u: envoie password
u->>a: "verification du mot de passe"
a-->>u: "true ou false"
u-->>c: resultat authentification
```

37
app.js Normal file
View File

@@ -0,0 +1,37 @@
var createError = require('http-errors');
var express = require('express');
var logger = require('morgan');
var usersRouter = require('./routes/users');
var app = express();
app.use(logger('dev'));
app.use(express.json()); // Indispensable pour lire req.body en JSON
app.use(express.urlencoded({ extended: false }));
// --- Routes ---
app.use('/users', usersRouter); // Notre route API principale
// --- Gestion des erreurs ---
// catch 404 (route non trouvée) et transfert à l'errorHandler
app.use(function(req, res, next) {
next(createError(404, 'Route non trouvée'));
});
// error handler
app.use(function(err, req, res, next) {
const errorDetails = req.app.get('env') === 'development' ? err : {};
// Renvoyer une erreur JSON au lieu d'une page HTML (res.render)
res.status(err.status || 500);
res.json({
error: {
message: err.message,
status: err.status,
details: errorDetails.stack // Affiche le stack trace en dev
}
});
});
module.exports = app;

90
bin/www Normal file
View File

@@ -0,0 +1,90 @@
#!/usr/bin/env node
/**
* Module dependencies.
*/
var app = require('../app');
var debug = require('debug')('hachage:server');
var http = require('http');
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
/**
* Create HTTP server.
*/
var server = http.createServer(app);
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val) {
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
/**
* Event listener for HTTP server "error" event.
*/
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}

1893
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

18
package.json Normal file
View File

@@ -0,0 +1,18 @@
{
"name": "hachage",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www"
},
"dependencies": {
"argon2": "^0.44.0",
"cookie-parser": "~1.4.4",
"debug": "~2.6.9",
"express": "~4.16.1",
"http-errors": "~1.6.3",
"morgan": "~1.9.1",
"nodemon": "^3.1.10",
"pug": "2.0.0-beta11"
}
}

153
routes/users.js Normal file
View File

@@ -0,0 +1,153 @@
const express = require('express');
const router = express.Router();
const crypto = require('crypto'); // Natif à Node.js
const argon2 = require('argon2'); // Bibliothèque installée
/* Simulation d'une base de données utilisateurs.
Dans une vraie app, on utiliserait MongoDB, MySQL, PostgreSQL, etc.
On la place ici pour que la démo soit auto-contenue.
*/
const fakeDatabase = {};
/*
==================================================================
DÉMONSTRATION 1 : LA MAUVAISE MÉTHODE (MD5)
==================================================================
*/
/**
* @route POST /users/register-bad
* @desc Enregistre un utilisateur avec MD5 (DANGEREUX !)
*/
router.post('/register-bad', (req, res) => {
try {
const { username, password } = req.body;
if (!username || !password) {
return res.status(400).json({ msg: 'Veuillez fournir un username et un password' });
}
// Hachage MD5 : rapide, synchrone, non salé.
// Une simple recherche Google ou Crackstation inverse ce hash.
const hash = crypto.createHash('md5').update(password).digest('hex');
// Stockage du hash "cassable"
fakeDatabase[username] = { hash, algorithm: 'md5' };
console.log('Utilisateur (MD5) créé :', fakeDatabase[username]);
res.status(201).json({
msg: 'Utilisateur créé avec MD5 (Non sécurisé !)',
username: username,
hash: hash,
// On montre que le mot de passe "123456" donne bien le hash connu
note: (password === '123456' ? 'Hash pour 123456: e10adc3949ba59abbe56e057f20f883e' : '')
});
} catch (err) {
res.status(500).json({ error: err.message });
}
});
/**
* @route POST /users/login-bad
* @desc Connecte un utilisateur avec MD5
*/
router.post('/login-bad', (req, res) => {
const { username, password } = req.body;
const user = fakeDatabase[username];
if (!user || user.algorithm !== 'md5') {
return res.status(404).json({ msg: 'Utilisateur non trouvé ou mauvais algo' });
}
// On re-hache la tentative de mdp
const attemptHash = crypto.createHash('md5').update(password).digest('hex');
// Simple comparaison de chaînes.
if (user.hash === attemptHash) {
res.json({ msg: 'Connexion MD5 réussie ! (Mais vous n\'êtes pas en sécurité)' });
} else {
res.status(401).json({ msg: 'Mot de passe incorrect' });
}
});
/*
==================================================================
DÉMONSTRATION 2 : LA BONNE MÉTHODE (Argon2id)
==================================================================
*/
/**
* @route POST /users/register-good
* @desc Enregistre un utilisateur avec Argon2id (SÉCURISÉ)
*/
router.post('/register-good', async (req, res) => {
// La route est déclarée "async"
const { username, password } = req.body;
if (!username || !password) {
return res.status(400).json({ msg: 'Veuillez fournir un username et un password' });
}
try {
// 1. Le 'await' est crucial.
// Pendant que le CPU calcule le hash (ce qui est LENT),
// le thread principal de Node.js est libéré et peut traiter
// d'autres requêtes.
const hash = ''; // TODO
// 2. Le hash contient le sel (salt) et les paramètres de coût.
// Il est auto-suffisant.
// ex: $argon2id$v=19$m=65536,t=3,p=1$Gb...
fakeDatabase[username] = { hash, algorithm: 'argon2id' };
console.log('Utilisateur (Argon2id) créé :', fakeDatabase[username]);
res.status(201).json({
msg: 'Utilisateur créé avec Argon2id (Sécurisé !)',
username: username,
hash: hash
});
} catch (err) {
console.error(err);
res.status(500).json({ msg: 'Erreur lors du hachage du mot de passe' });
}
});
/**
* @route POST /users/login-good
* @desc Connecte un utilisateur avec Argon2id
*/
router.post('/login-good', async (req, res) => {
const { username, password } = req.body;
const user = fakeDatabase[username];
if (!user || user.algorithm !== 'argon2id') {
return res.status(404).json({ msg: 'Utilisateur non trouvé ou mauvais algo' });
}
try {
// 3. On utilise 'argon2.verify'
// Cette fonction lit le hash, extrait le sel et les paramètres,
// re-hache la tentative (password) et compare en temps constant.
const isMatch = false; // TODO
if (isMatch) {
res.json({ msg: 'Connexion Argon2id réussie ! (Vous êtes en sécurité)' });
} else {
res.status(401).json({ msg: 'Mot de passe incorrect' });
}
} catch (err) {
// Si le format du hash est mauvais, ou autre erreur
console.error(err);
res.status(500).json({ msg: 'Erreur lors de la vérification' });
}
});
module.exports = router;