Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

TP2e année10 livrablesQuiz corrigé

Lab de cracking de mots de passe

Le film t'a montré ce que vaut une donnée volée. Cet après-midi, tu te mets dans la peau de l'attaquant — sur un jeu 100 % fictif — pour casser des mots de passe hachés, mesurer pourquoi un mauvais hachage est une catastrophe, et coder la vraie parade. Tu codes ton propre cracker.

Par Josselyn C., prof de BTS SIO. Publié le , mis à jour le .

Objectif
coder un cracker par dictionnaire, comparer MD5/SHA-1/bcrypt, et implémenter les parades
On manipule
Node.js (crypto, bcryptjs), un jeu de hash fictif fourni, un mini-dictionnaire
Durée
~3 h (suite de la matinée The Great Hack)
Pré-requis
Node.js installé, un terminal, un éditeur de code
Pour qui
SLAM, 2e année

Compétences : C3.3 Sécuriser les équipements et les usages des utilisateurs, C3.5 Assurer la cybersécurité d'une solution applicative

On casse pour comprendre comment protéger. Rien d'autre.

Avant de commencer

Ce qu'on fait, et pourquoi

Ce matin, The Great Hack a montré la donnée personnelle comme un butin. Mais quand un attaquant vole une base d'utilisateurs, il n'y trouve (presque) jamais des mots de passe en clair : il trouve des hash. Pour s'en servir, il doit les casser. Aujourd'hui tu fais ce travail — sur des comptes GSB inventés que tu vas générer toi-même — pour comprendre de l'intérieur pourquoi certains hachages tombent en une seconde et d'autres résistent des siècles. Puis tu codes la défense. C'est le cœur du métier : on n'apprend à défendre qu'en comprenant l'attaque.

Ce que tu vas apprendre

ConceptUtilité
Hachage vs chiffrementcomprendre pourquoi on ne « déhache » pas, mais on « devine »
Attaque par dictionnairele mécanisme réel du cracking — que tu codes
Coût d'un algorithmepourquoi MD5 tombe et bcrypt résiste (chrono à l'appui)
Sel (salt)pourquoi deux mots de passe identiques ne doivent pas donner le même hash
Parades devbcrypt, refus des mots de passe fuités, rate limiting

Prépare ton poste

powershell
node --version      # doit afficher v18+ (sinon installe Node.js)
mkdir crack-lab && cd crack-lab
npm init -y
npm install bcryptjs

1. Hacher, ce n'est pas chiffrer

Avant de casser, il faut comprendre ce qu'on casse.

ChiffrementHachage
Réversible ?Oui, avec la cléNon, jamais
Sert à…protéger un message qu'on veut relirevérifier un mot de passe sans le stocker
ExempleAES, RSAMD5, SHA-1, bcrypt, argon2

Un mot de passe se hache. Le serveur ne stocke jamais azerty, il stocke son empreinte. À la connexion, il re-hache ce que tu tapes et compare les empreintes.

L'attaque par dictionnaire : hacher chaque candidat et comparer. Rien de magique — juste beaucoup d'essais, très vite.

L'attaque par dictionnaire : hacher chaque candidat et comparer. Rien de magique — juste beaucoup d'essais, très vite.

2. Le marché derrière les hash (recon)

D'où viennent ces bases de hash qu'un attaquant cherche à casser ? Du marché des fuites. Tu dois connaître ce terrain — en analyste, par la documentation, jamais en t'y connectant.

LieuCe qui s'y échange
Forums de leaks (type BreachForums)bases volées ; on gagne des crédits souvent en uploadant d'autres données volées
Marketplaces / Telegramvente et diffusion rapide, avec échantillons de « preuve »
Initial Access Brokersvendent l'accès à un réseau (identifiants VPN/RDP), souvent aux enchères
Ransomware leak sites« name & shame » : publication des données d'une victime qui refuse de payer

Le format d'une annonce (reconstitué, fictif)

[VENTE] Base "GSB-Corp" — 120 000 comptes — FRESH 2026
Contenu : email, hash (voir échantillon), nom, ville
Prix : 400$ — escrow uniquement
Preuve : 50 lignes sur demande | Vendeur : rep 4.8/5

« Traquer la source », côté défense

Identifier → traquer → neutraliser, au niveau des États, ça donne des saisies : RaidForums (Opération TOURNIQUET, 2022), BreachForums (saisi par le FBI en 2023, puis ses réincarnations re-saisies), Genesis Market (Opération COOKIE MONSTER, 2023). Ces domaines changent en permanence : le savoir utile, c'est la mécanique, pas une URL du jour.

Flag D1

Investigation (une saisie)

Via une source de presse fiable, documente une saisie (RaidForums, BreachForums ou Genesis) : qui, quand, comment, ce qui s'y vendait. Cite l'URL et la date.
Rends : ta fiche (6 lignes) + la source.

0 sur 10 dans cette étape

3. Fabrique ta « base volée » (fictive)

Pour attaquer, il te faut une cible. Tu vas la générer toi-même : d'abord tu joues le rôle du site GSB qui stocke (mal) les mots de passe de ses employés, ensuite tu joueras l'attaquant qui a volé le fichier.

Étape 1 — Le dictionnaire d'attaque

Crée un fichier dico.txt avec un mini-dictionnaire (les mots de passe les plus courants, façon RockYou) :

123456
password
azerty
soleil
motdepasse
qwerty
iloveyou
gsb2026
marseille
doliprane
admin
bonjour
123456789
loulou
naruto

Étape 2 — Générer la fuite

Crée generer-fuite.js. Ce script joue GSB : il hache les mots de passe de 10 employés fictifs, avec de mauvais algorithmes (MD5, SHA-1) et un bon (bcrypt), et écrit le fichier fuite-gsb.txt.

const crypto = require("crypto");
const bcrypt = require("bcryptjs");
const fs = require("fs");

const md5  = t => crypto.createHash("md5").update(t).digest("hex");
const sha1 = t => crypto.createHash("sha1").update(t).digest("hex");

// 10 employés GSB fictifs : login, mot de passe, algo de stockage
const comptes = [
  ["arizzo",   "soleil",        "md5"],
  ["bmercier", "gsb2026",       "md5"],
  ["cfaure",   "123456",        "md5"],
  ["dlopez",   "Tr0ub4dour&3!", "md5"],   // fort : hors dico
  ["enguyen",  "azerty",        "sha1"],
  ["froux",    "soleil",        "sha1"],  // meme mdp que arizzo !
  ["gpetit",   "doliprane",     "sha1"],
  ["iortiz",   "Azerty2026",    "md5"],   // dico deguise : majuscule + annee
  ["jbernard", "s0leil",        "sha1"],  // dico en leet : o -> 0
  ["hvidal",   "marseille",     "bcrypt"],
];

let out = "";
for (const [login, mdp, algo] of comptes) {
  let hash;
  if (algo === "md5")  hash = md5(mdp);
  else if (algo === "sha1") hash = sha1(mdp);
  else hash = bcrypt.hashSync(mdp, 10);
  out += `${login}:${algo}:${hash}\n`;
}
fs.writeFileSync("fuite-gsb.txt", out);
console.log("fuite-gsb.txt genere (10 comptes).");
powershell
node generer-fuite.js
Ce que tu dois voir
fuite-gsb.txt genere (10 comptes).

Ouvre fuite-gsb.txt : tu vois 10 lignes de hash. C'est le fichier qu'un attaquant aurait volé. À partir de maintenant, tu es l'attaquant : tu n'as que ça, et ton dico.

Flag A1

Checkpoint (la base)

Lance le script, ouvre fuite-gsb.txt. Combien de comptes sont en MD5, en SHA-1, en bcrypt ? Colle la ligne de cfaure (son hash MD5).
Rends : le décompte par algo + la ligne de cfaure.

0 sur 10 dans cette étape

4. Étape 1 : casser le MD5

MD5 est rapide : des millions de hachages par seconde sur un simple portable. C'est ce qui le rend catastrophique pour stocker des mots de passe. On attaque.

Construis ton cracker, morceau par morceau

Crée cracker.js. D'abord, charger le dico et la fuite :

const crypto = require("crypto");
const fs = require("fs");

const dico  = fs.readFileSync("dico.txt", "utf8").split("\n").filter(Boolean);
const fuite = fs.readFileSync("fuite-gsb.txt", "utf8").split("\n").filter(Boolean);
const md5  = t => crypto.createHash("md5").update(t).digest("hex");

Puis la boucle d'attaque sur les lignes MD5 :

for (const ligne of fuite) {
  const [login, algo, hash] = ligne.split(":");
  if (algo !== "md5") continue;              // on ne traite que MD5 ici

  let trouve = null;
  for (const candidat of dico) {
    if (md5(candidat) === hash) { trouve = candidat; break; }
  }
  console.log(login, "->", trouve ?? "(non cassé)");
}
powershell
node cracker.js
Ce que tu dois voir
arizzo -> soleil
bmercier -> gsb2026
cfaure -> 123456
dlopez -> (non cassé)
iortiz -> (non cassé)

Flag B1

Production (ton cracker MD5)

Fais tourner ton cracker. Rends : ton code + la sortie (les mots de passe cassés). Combien sur les 5 comptes MD5 ? Les deux qui résistent le font pour des raisons différentes : lesquelles ?

0 sur 10 dans cette étape

5. Étape 2 : SHA-1 et le problème du sel

SHA-1 est un peu plus « sérieux » que MD5… mais toujours rapide, et surtout, dans ta fuite, il est non salé. On l'attaque de la même façon — il suffit d'ajouter SHA-1 à ton cracker.

const sha1 = t => crypto.createHash("sha1").update(t).digest("hex");

// dans la boucle, remplace la condition MD5 par :
for (const ligne of fuite) {
  const [login, algo, hash] = ligne.split(":");
  if (algo !== "md5" && algo !== "sha1") continue;
  const fn = algo === "md5" ? md5 : sha1;

  let trouve = null;
  for (const candidat of dico) {
    if (fn(candidat) === hash) { trouve = candidat; break; }
  }
  console.log(login, `(${algo})`, "->", trouve ?? "(non cassé)");
}
Ce que tu dois voir
arizzo (md5) -> soleil
bmercier (md5) -> gsb2026
cfaure (md5) -> 123456
dlopez (md5) -> (non cassé)
enguyen (sha1) -> azerty
froux (sha1) -> soleil
gpetit (sha1) -> doliprane
iortiz (md5) -> (non cassé)
jbernard (sha1) -> (non cassé)

Flag B2

Production (SHA-1 + le sel)

Étends ton cracker au SHA-1 et fais-le tourner. Puis, dans un petit script, hache soleil deux fois en SHA-1 et montre que le résultat est identique.
Rends : la sortie du cracker + la preuve des deux hash identiques + une phrase expliquant en quoi c'est un problème.

0 sur 10 dans cette étape

6. Étape 3 : bcrypt résiste (mesure-le)

Passons au compte hvidal, stocké en bcrypt. Même dico, même méthode… mais tu vas sentir la différence. bcrypt est lent volontairement.

const bcrypt = require("bcryptjs");

for (const ligne of fuite) {
  const [login, algo, hash] = ligne.split(":");
  if (algo !== "bcrypt") continue;

  let trouve = null;
  for (const candidat of dico) {
    if (bcrypt.compareSync(candidat, hash)) { trouve = candidat; break; }
  }
  console.log(login, "(bcrypt) ->", trouve ?? "(non cassé)");
}

Ça finit par trouver marseille… mais mesure le temps. Ajoute un chrono :

const t0 = Date.now();
// ... la boucle bcrypt ci-dessus ...
console.log("temps :", Date.now() - t0, "ms pour", dico.length, "essais");
Ce que tu dois voir
hvidal (bcrypt) -> marseille
temps : 1450 ms pour 15 essais

Fais parler les chiffres

Mesure le temps par essai pour chaque algo (hache 10 000 fois test) :

function chrono(nom, fn) {
  const t0 = Date.now();
  for (let i = 0; i < 10000; i++) fn("test" + i);
  console.log(nom, ":", Date.now() - t0, "ms pour 10 000 hachages");
}
chrono("md5",  md5);
chrono("sha1", sha1);
chrono("bcrypt", t => bcrypt.hashSync(t, 10));
Ce que tu dois voir
md5    : 12 ms pour 10 000 hachages
sha1   : 15 ms pour 10 000 hachages
bcrypt : 68000 ms pour 10 000 hachages
Algo~ temps / hachageCasser un dico de 14 M (RockYou)
MD5~0,001 msquelques secondes
SHA-1~0,0015 msquelques secondes
bcrypt (coût 10)~7 ms~1 jour… par compte, et ça grimpe avec le coût

Flag B3

Production (le tableau de mesure)

Fais tourner le chrono chez toi. Rends : ton tableau des 3 temps (tes vraies valeurs) + une phrase : pourquoi le même dico casse MD5 « pour rien » et rend bcrypt impraticable ?

0 sur 10 dans cette étape

7. Améliorer l'attaque : les règles de mutation

Ton cracker teste les mots du dico bruts. Mais personne n'écrit soleil tout nu : on écrit Soleil, soleil2026, s0leil, Soleil!. Un attaquant sérieux applique des règles de mutation à chaque candidat — c'est précisément ce qui fait tomber iortiz (Azerty2026) et jbernard (s0leil), que ton cracker basique a ratés.

Étape 1 — Générer les variantes d'un mot

function variantes(mot) {
  const cap = mot[0].toUpperCase() + mot.slice(1);   // Soleil
  const leets = [
    s => s.replace(/o/g, "0"),                        // s0leil
    s => s.replace(/a/g, "@"),
    s => s.replace(/e/g, "3"),
  ];
  const set = new Set();
  for (const base of [mot, cap]) {
    set.add(base);
    for (const suf of ["2024","2025","2026","!","123"]) set.add(base + suf);
    for (const leet of leets) set.add(leet(base));
  }
  return [...set];
}
console.log(variantes("soleil"));

Étape 2 — Brancher les règles sur le cracker

for (const ligne of fuite) {
  const [login, algo, hash] = ligne.split(":");
  if (algo === "bcrypt") continue;
  const fn = algo === "md5" ? md5 : sha1;

  let trouve = null;
  for (const candidat of dico) {
    for (const v of variantes(candidat)) {        // <-- la seule nouveauté
      if (fn(v) === hash) { trouve = v; break; }
    }
    if (trouve) break;
  }
  console.log(login, `(${algo})`, "->", trouve ?? "(non cassé)");
}
Ce que tu dois voir
dlopez (md5) -> (non cassé)
iortiz (md5) -> Azerty2026
jbernard (sha1) -> s0leil

Flag B5

Production (le cracker à règles)

Ajoute variantes() à ton cracker et relance. Rends : ton code + la sortie. Combien de comptes en plus tombent grâce aux règles ? Lequel résiste toujours, et pourquoi ?

0 sur 10 dans cette étape

8. Quand le dico échoue : brute force & entropie

dlopez résiste à ton dico, même avec règles. Il reste une arme : la force brute — tester toutes les combinaisons possibles. Elle marche toujours… en théorie. En pratique, elle explose vite.

Démo — casser un code PIN à 4 chiffres

const cible = md5("4271");           // un PIN "volé" (fictif)
const t0 = Date.now();
let found = null;
for (let i = 0; i <= 9999; i++) {
  const pin = String(i).padStart(4, "0");   // 0000, 0001, ... 9999
  if (md5(pin) === cible) { found = pin; break; }
}
console.log("PIN:", found, "en", Date.now() - t0, "ms");
Ce que tu dois voir
PIN: 4271 en 6 ms

Pourquoi la longueur gagne (et pas la « complexité »)

Chaque caractère ajouté multiplie l'espace de recherche. Voici l'explosion, à ~1 milliard de hachages/seconde (un GPU sur du MD5) :

Mot de passeEspace de rechercheTemps de brute force (MD5, GPU)
4 chiffres10⁴ = 10 000instantané
6 lettres minuscules26⁶ ≈ 3 × 10⁸< 1 seconde
8 lettres minuscules26⁸ ≈ 2 × 10¹¹~3 minutes
8 (maj + min + chiffres)62⁸ ≈ 2 × 10¹⁴~2,5 jours
12 (maj + min + chiffres)62¹² ≈ 3 × 10²¹~90 000 ans
16 caractères aléatoires94¹⁶ ≈ 4 × 10³¹bien au-delà de l'âge de l'univers

Flag B6

Production (le brute-forcer)

Code le brute-forcer de PIN et mesure son temps chez toi. Puis, sans le coder, estime : combien de temps pour un PIN à 6 chiffres (× combien de plus) ? Et explique en une phrase pourquoi un attaquant préfère toujours essayer le dictionnaire avant la force brute.
Rends : ton code + le temps mesuré + tes deux réponses.

0 sur 10 dans cette étape

9. La parade côté développeur

Tu as vu l'attaque de l'intérieur. Maintenant, code ce qui rend une base inexploitable même si elle fuit. Trois règles.

Règle 1 — Stocker en bcrypt (jamais MD5/SHA-1/clair)

const bcrypt = require("bcryptjs");

// Inscription
const hash = bcrypt.hashSync(motDePasse, 12);   // 12 = coût, salé automatiquement
// on stocke `hash`, jamais `motDePasse`

// Connexion
const ok = bcrypt.compareSync(saisie, hash);    // true / false, sans "déchiffrer"

Règle 2 — Refuser un mot de passe déjà fuité

On branche l'API Pwned Passwords (modèle k-anonymity : on n'envoie que 5 caractères du hash, le secret ne quitte jamais le serveur).

async function estFuite(pwd) {
  const sha1 = crypto.createHash("sha1").update(pwd).digest("hex").toUpperCase();
  const prefixe = sha1.slice(0, 5), suffixe = sha1.slice(5);
  const rep = await fetch("https://api.pwnedpasswords.com/range/" + prefixe);
  return (await rep.text()).split("\n").some(l => l.split(":")[0] === suffixe);
}
// à l'inscription : if (await estFuite(mdp)) refuser("mot de passe trop courant");

Règle 3 — Ralentir & renforcer

ParadeCe que ça bloque
Rate limiting (ex. 5 essais / 15 min)le credential stuffing et le brute force en ligne
2FA / MFAun mot de passe cassé ne suffit plus à entrer
Longueur > complexité (min. 12)casse les mots de passe type dico (la longueur = l'entropie)

Flag B4

Production (l'inscription sécurisée)

Écris une fonction inscrire(login, mdp) qui : (1) refuse un mot de passe présent dans ton dico.txt (blocklist locale), (2) sinon le hache en bcrypt coût 12 et renvoie la ligne à stocker. Teste avec 123456 (refusé) et un mot de passe long inventé (accepté).
Rends : ton code + les deux sorties.

0 sur 10 dans cette étape

Flag E1

Extension (Pwned Passwords en vrai)

Pour les rapides : implémente estFuite (règle 2) et teste-la avec password123 (doit être fuité) puis un mot de passe long inventé (doit passer).
Rends : ton code + les 2 résultats.

0 sur 10 dans cette étape

10. Pour aller plus loin : ton cracker v2

Le cracker que tu as codé est un jouet — mais un jouet qui marche. Voici la feuille de route pour en faire un vrai outil. Tu n'as pas à tout faire : choisis les niveaux qui t'attirent, construis, et mesure. C'est un terrain de jeu idéal pour un futur pentester, et un excellent projet de portfolio.

Les niveaux de montée en puissance

NiveauCe que tu ajoutesLa notion derrière
1 · Multi-hash autodeviner l'algo par la longueur du hash (32 = MD5, 40 = SHA-1, débute par $2 = bcrypt) au lieu de le lire dans le fichierreconnaissance de format
2 · Masquesbrute force ciblé : générer « prénom + 4 chiffres » plutôt que tout l'espace (masque ?l?l?l?d?d?d?d)réduire l'espace de recherche
3 · Combinateurcoller deux mots du dico (soleil+lune → soleillune)attaque combinatoire
4 · Vraie wordlistbrancher rockyou.txt (dépôt SecLists) à la place du mini-dicopassage à l'échelle
5 · Performancemesurer le hashrate, paralléliser (Worker threads), afficher une barre de progressionoptimisation
6 · Rapportproduire un rapport final (comptes cassés, temps, algo le plus faible, recommandations)livrable de pentester

Les vrais outils à explorer (hors classe, légal)

  • John the Ripper et hashcat — les crackers de référence (règles, masques, GPU). Lis leur documentation avant de les lancer.
  • SecLists — le dépôt public des wordlists (dont rockyou.txt).
  • OWASP Password Storage Cheat Sheet — la référence défensive : quel algo, quel coût, aujourd'hui.
  • Root-Me, TryHackMe, Hack The Box — catégories « cracking » / « crypto » pour t'entraîner légalement, sur des cibles prévues pour ça.

Flag E2

Le grand défi (sur la semaine)

Construis GSB-Crack v2 avec au moins 3 niveaux de la feuille de route. Dépose ton dépôt Git + un court README qui explique : tes choix, ce que ton cracker attrape en plus, et tes mesures de vitesse. Bonus : lance-le sur ton fuite-gsb.txt régénéré tout en bcrypt coût 12 — que se passe-t-il, et que conclus-tu pour un développeur ?
Rends : le lien du dépôt + ton README.

0 sur 10 dans cette étape

Clôture

Mini-quiz (corrige-toi seul)

Exercice Réussi À revoir

#QuestionTa réponse
1Pourquoi ne peut-on pas « déhacher » un mot de passe, mais peut-on le « cracker » ?…
2Pourquoi MD5 est-il catastrophique pour stocker des mots de passe ?…
3À quoi sert le sel, concrètement ?…
4Un mot de passe hors dictionnaire mais stocké en MD5 : est-il en sécurité ? Nuance.…
Voir le corrigéMasquer le corrigé de l'exercice

1. Le hachage est irréversible ; on ne l'inverse pas, on teste des candidats jusqu'à retomber sur le même hash. 2. Il est trop rapide : des millions d'essais/seconde → un dico entier passe en un clin d'œil. 3. Rendre chaque hash unique même pour un mot de passe identique → tue les tables précalculées et masque les réutilisations. 4. Mieux protégé contre un dictionnaire, mais un attaquant motivé peut passer au brute force ou à des règles ; et si l'algo est rapide, ça reste risqué. La vraie sécurité = mot de passe long et algo lent salé.

Tu avais trouvé ?

Glossaire

TermeDéfinition
Hachageempreinte irréversible d'une donnée (MD5, SHA-1, bcrypt, argon2)
Attaque par dictionnairehacher une liste de candidats probables et comparer au hash volé
Brute forcetester toutes les combinaisons possibles (plus lent que le dictionnaire)
Sel (salt)valeur aléatoire ajoutée avant hachage → empreintes uniques
Rainbow tabletable géante de hash précalculés ; inutile si les hash sont salés
Règle de mutationtransformation appliquée à un candidat (majuscule, +année, leet) pour élargir le dico
Masquebrute force ciblé sur un motif connu (ex. ?l?l?l?d?d?d?d)
Entropiemesure de la force d'un mot de passe en bits (= log₂ de l'espace de recherche)
Coût / facteur de travailparamètre de lenteur d'un algo (bcrypt : 10, 11, 12…)
k-anonymityinterroger une base en ne révélant qu'un préfixe du hash

Compétences mobilisées (référentiel BTS SIO SLAM)

Bloc 3 · Cybersécurité des services informatiques
→ C3.3 Sécuriser les équipements et les usages des utilisateurs : identifier les menaces (dictionnaire, brute force, réutilisation) et mettre en œuvre les défenses.
→ C3.5 Assurer la cybersécurité d'une solution applicative et de son développement : prévenir les attaques et prendre en compte la sécurité dans le développement (hachage bcrypt, salage, rate limiting).

TP terminé ?

Coche chaque livrable quand tu l'as : c'est la preuve que l'étape est acquise.

livrables
–
quiz réussi
–
de série
–
Voir tous les TP