Projet 01 : Générateur de mots de passe sécurisé et analyseur d'entropie#
En informatique moderne et en cybersécurité, la solidité d'une chaîne d'authentification dépend avant tout de la robustesse des secrets partagés. Les attaques par dictionnaire et par force brute (brute-force) sont capables de tester plusieurs milliards de combinaisons par seconde sur des processeurs graphiques (GPU) grand public.
L'objectif de ce premier projet pratique est de concevoir un générateur de mots de passe de niveau cryptographique en console, doté d'options de configuration granulaires et d'un moteur d'évaluation d'entropie de Shannon conforme aux recommandations de l'ANSSI et du NIST.
1. Contexte & Pièges de sécurité en Python#
Le piège du module standard random#
Comme nous l'avons étudié dans le module dédié à l'aléatoire, le module standard random de Python repose sur l'algorithme du Mersenne Twister (MT19937) :
[!WARNING] Le module
randomn'est jamais sécurisé pour la cryptographie ou l'authentification. Le générateur pseudo-aléatoire du Mersenne Twister est déterministe : il suffit d'observer 624 sorties consécutives de 32 bits pour reconstituer intégralement l'état interne de la mémoire du générateur et prédire à 100 % toutes les sorties passées et futures !
La solution : Le module secrets (CSPRNG)#
Depuis Python 3.6, la bibliothèque standard intègre le module secrets. Ce module exploite les générateurs d'aléa cryptographiquement sûrs fournis par le système d'exploitation sous-jacent (/dev/urandom sur Linux/macOS, CryptGenRandom ou BCryptGenRandom sur Windows), alimentés par le bruit thermique des composants matériels et les interruptions système (hardware entropy).
2. Cahier des charges et spécifications fonctionnelles#
Notre application devra satisfaire les exigences suivantes :
- Paramétrage granulaire :
- Choix de la longueur totale (minimum 8 caractères, recommandé \(\ge 16\)).
- Activation ou désactivation des 4 familles de caractères :
- Lettres minuscules (
aàz) - Lettres majuscules (
AàZ) - Chiffres décimaux (
0à9) - Caractères spéciaux et signes de ponctuation (
!@#$%^&*()-_=+[]{}|;:,.<>?)
- Lettres minuscules (
- Exclusion des caractères visuellement ambigus :
- Option permettant de bannir les glyphes susceptibles d'être confondus lors d'une retranscription manuscrite :
0,O,o,1,l,I,|. - Garantie d'inclusion stricte :
- Un tirage aléatoire brut peut statistiquement omettre une catégorie sélectionnée (par exemple ne générer aucun chiffre sur un mot de passe court). Notre algorithme doit garantir la présence d'au moins un caractère de chaque catégorie active.
- Brassage cryptographique (Shuffle) :
- Après avoir garanti un caractère par groupe, le reste du mot de passe est complété, puis l'ensemble des caractères doit être mélangé de manière imprévisible avec
secrets.SystemRandom().shuffle()pour éviter que les catégories imposées ne figurent toujours aux mêmes indices. - Calcul de l'entropie de Shannon (\(E\)) :
- Mesurer la quantité d'incertitude théorique du mot de passe exprimée en bits :
$\(E = L \times \log_2(N)\)$
où : - \(L\) représente la longueur effective du mot de passe généré. - \(N\) représente la taille totale de l'espace de caractères disponible (taille du pool utilisé).
- Échelle de robustesse :
| Entropie (\(E\) en bits) | Niveau de robustesse | Risque associé |
|---|---|---|
| \(< 40\) bits | 🔴 Très faible | Cassable quasi-instantanément |
| \(40 - 59\) bits | 🟠 Faible | Vulnérable aux attaques rapides par dictionnaire |
| \(60 - 79\) bits | 🟡 Moyen | Acceptable pour des usages non critiques |
| \(80 - 99\) bits | 🟢 Fort | Standard recommandé pour les comptes sensibles |
| \(\ge 100\) bits | 🟣 Très fort | Résistant aux supercalculateurs et attaques massives |
3. Architecture du programme#
Le flux de traitement suit une logique modulaire et séquentielle :
flowchart TD
A[Saisie des options : Longueur, Familles, Filtre ambigu] --> B{Validation des parametres}
B -- Au moins 1 famille active & L >= 8 --> C[Constitution du dictionnaire de pools]
B -- Erreur --> A
C --> D[Tirage garanti : 1 caractere CSPRNG par famille active]
D --> E[Tirage du reliquat dans le pool global fusionne]
E --> F[Brassage Fisher-Yates cryptographique secrets.SystemRandom]
F --> G[Assemblage de la chaine finale]
G --> H[Calcul d'entropie de Shannon en bits]
H --> I[Evaluation du niveau et restitution console formatee]
4. Implémentation complète en Python#
Voici le code source complet, typé, documenté et conforme aux directives PEP 8 :
"""
Générateur de mots de passe cryptographiquement sécurisé avec évaluation d'entropie.
Auteur : Joseph Konka (AlgoJungle)
"""
import math
import secrets
import string
from typing import Dict, List, Tuple
# Constantes définissant les jeux de caractères
CARACTERES_AMBIGUS: set[str] = {"0", "O", "o", "1", "l", "I", "|"}
SYMBOLES_SECURISES: str = "!@#$%^&*()-_=+[]{}|;:,.<>?"
def nettoyer_pool(pool: str, exclure_ambigus: bool) -> str:
"""Filtre les caractères ambigus d'un jeu donné si demandé."""
if not exclure_ambigus:
return pool
return "".join(c for c in pool if c not in CARACTERES_AMBIGUS)
def calculer_entropie(longueur: int, taille_pool: int) -> float:
"""
Calcule l'entropie de Shannon en bits : E = L * log2(N).
Exemple : 16 caractères tirés parmi un pool de 94 symboles = ~104.87 bits.
"""
if longueur <= 0 or taille_pool <= 1:
return 0.0
return longueur * math.log2(taille_pool)
def qualifier_force(entropie: float) -> Tuple[str, str]:
"""Retourne l'indicateur textuel et le symbole de couleur pour l'entropie."""
if entropie < 40:
return "Très faible", "🔴"
elif entropie < 60:
return "Faible", "🟠"
elif entropie < 80:
return "Moyen", "🟡"
elif entropie < 100:
return "Fort", "🟢"
else:
return "Très fort (Excellent)", "🟣"
def generer_mot_de_passe(
longueur: int = 16,
avec_minuscules: bool = True,
avec_majuscules: bool = True,
avec_chiffres: bool = True,
avec_symboles: bool = True,
exclure_ambigus: bool = True,
) -> Tuple[str, float, int]:
"""
Génère un mot de passe sécurisé à l'aide du CSPRNG du module secrets.
Retourne un tuple : (mot_de_passe, entropie_bits, taille_du_pool).
"""
if longueur < 8:
raise ValueError("La longueur minimale de sécurité est de 8 caractères.")
# 1. Constitution des pools individuels
pools: Dict[str, str] = {}
if avec_minuscules:
pools["minuscules"] = nettoyer_pool(string.ascii_lowercase, exclure_ambigus)
if avec_majuscules:
pools["majuscules"] = nettoyer_pool(string.ascii_uppercase, exclure_ambigus)
if avec_chiffres:
pools["chiffres"] = nettoyer_pool(string.digits, exclure_ambigus)
if avec_symboles:
pools["symboles"] = nettoyer_pool(SYMBOLES_SECURISES, exclure_ambigus)
if not pools:
raise ValueError("Au moins une catégorie de caractères doit être activée.")
# Pool combiné unique pour le reste des tirages
pool_global: str = "".join(pools.values())
taille_pool_unique: int = len(set(pool_global))
# 2. Garantie d'inclusion : 1 caractère obligatoire par catégorie sélectionnée
resultat: List[str] = [secrets.choice(pool) for pool in pools.values()]
# 3. Remplissage du reste de la longueur demandée
caracteres_restants: int = longueur - len(resultat)
if caracteres_restants < 0:
raise ValueError(
f"La longueur ({longueur}) est inférieure au nombre de catégories activées ({len(resultat)})."
)
for _ in range(caracteres_restants):
resultat.append(secrets.choice(pool_global))
# 4. Brassage aléatoire cryptographique (Fisher-Yates via SystemRandom)
rng = secrets.SystemRandom()
rng.shuffle(resultat)
mot_de_passe_final: str = "".join(resultat)
entropie: float = calculer_entropie(longueur, taille_pool_unique)
return mot_de_passe_final, entropie, taille_pool_unique
def afficher_banniere() -> None:
"""Affiche l'en-tête de bienvenue de la console."""
print("=" * 65)
print(" 🔐 ALGOJUNGLE - GÉNÉRATEUR DE MOTS DE PASSE SÉCURISÉ")
print("=" * 65)
def interface_utilisateur() -> None:
"""Point d'entrée interactif en ligne de commande."""
afficher_banniere()
try:
saisie_longueur = input("👉 Longueur désirée (défaut : 16) : ").strip()
longueur = int(saisie_longueur) if saisie_longueur else 16
def demander_booleen(message: str, defaut: str = "o") -> bool:
rep = input(f"👉 {message} [O/n] : ").strip().lower()
if not rep:
return defaut == "o"
return rep in {"o", "oui", "y", "yes"}
inclure_min = demander_booleen("Inclure les minuscules ?")
inclure_maj = demander_booleen("Inclure les majuscules ?")
inclure_num = demander_booleen("Inclure les chiffres ?")
inclure_sym = demander_booleen("Inclure les symboles spéciaux ?")
filtre_ambigus = demander_booleen("Exclure les caractères ambigus (0, O, l, 1, etc.) ?")
mdp, entropie, taille_pool = generer_mot_de_passe(
longueur=longueur,
avec_minuscules=inclure_min,
avec_majuscules=inclure_maj,
avec_chiffres=inclure_num,
avec_symboles=inclure_sym,
exclure_ambigus=filtre_ambigus,
)
qualification, icone = qualifier_force(entropie)
print("\n" + "-" * 65)
print("🎉 RÉSULTAT DE LA GÉNÉRATION :")
print(f"🔑 Mot de passe : {mdp}")
print(f"📏 Longueur : {len(mdp)} caractères")
print(f"🎲 Espace (pool): {taille_pool} caractères distincts")
print(f"📊 Entropie : {entropie:.2f} bits")
print(f"🛡️ Robustesse : {icone} {qualification}")
print("-" * 65 + "\n")
except ValueError as err:
print(f"\n❌ Erreur de paramétrage : {err}\n")
except (KeyboardInterrupt, EOFError):
print("\n\n👋 Interruption par l'utilisateur. À bientôt !")
if __name__ == "__main__":
interface_utilisateur()
5. Exemple d'exécution en console#
Voici une trace réelle d'exécution du script :
=================================================================
🔐 ALGOJUNGLE - GÉNÉRATEUR DE MOTS DE PASSE SÉCURISÉ
=================================================================
👉 Longueur désirée (défaut : 16) : 20
👉 Inclure les minuscules ? [O/n] : o
👉 Inclure les majuscules ? [O/n] : o
👉 Inclure les chiffres ? [O/n] : o
👉 Inclure les symboles spéciaux ? [O/n] : o
👉 Exclure les caractères ambigus (0, O, l, 1, etc.) ? [O/n] : o
-----------------------------------------------------------------
🎉 RÉSULTAT DE LA GÉNÉRATION :
🔑 Mot de passe : k#7H&mQ8$wP9!vX2@rT5
📏 Longueur : 20 caractères
🎲 Espace (pool): 84 caractères distincts
📊 Entropie : 127.85 bits
🛡️ Robustesse : 🟣 Très fort (Excellent)
-----------------------------------------------------------------
6. Mise en pratique & Défis d'extension#
Pour consolider vos compétences, voici 3 défis d'amélioration à relever :
Défi 1 : Générateur de phrases de passe (Passphrases Diceware)#
Les mots de passe longs composés de mots aléatoires (ex. correct-cheval-pile-fusil) sont plus mémorables pour les humains tout en offrant une entropie colossale.
- Mission : Implémentez une fonction generer_passphrase(nb_mots: int = 5, separateur: str = "-") qui pioche dans une liste de mots français courants via secrets.choice().
Défi 2 : Export automatique vers le presse-papiers#
- Mission : Utilisez la bibliothèque standard ou le module
pyperclippour copier automatiquement le mot de passe généré dans le presse-papiers de l'utilisateur, tout en affichant un compte à rebours pour effacer le presse-papiers au bout de 30 secondes pour des raisons de confidentialité.
Défi 3 : Test unitaire du générateur#
- Mission : Rédigez un fichier de test avec
pytestvalidant que : - La longueur finale est exactement égale à la longueur demandée.
- Chaque famille active contient au moins un caractère dans le mot de passe généré.
- L'exception
ValueErrorest bien levée si la longueur est inférieure à 8.
Commentaires