Files
TermNSI/Sécurité/Corrige_TP_Gestionnaire_MDP.py

458 lines
14 KiB
Python

"""
Corrigé du TP Gestionnaire de mots de passe
"""
import random
import string
import hashlib
# =============================================================================
# PARTIE 1 : Chiffrement XOR
# =============================================================================
def xor_chiffrement(message, cle):
"""
Chiffre ou déchiffre un message avec XOR.
:param message: (str) Le message à traiter
:param cle: (str) La clé de chiffrement
:return: (str) Le message chiffré/déchiffré
"""
resultat = ""
for i, caractere in enumerate(message):
# Récupérer le caractère de la clé (cyclique)
cle_char = cle[i % len(cle)]
# Appliquer XOR
nouveau_char = chr(ord(caractere) ^ ord(cle_char))
resultat += nouveau_char
return resultat
# =============================================================================
# PARTIE 2 : Classe MotDePasse
# =============================================================================
class MotDePasse:
"""Représente un identifiant stocké."""
def __init__(self, site, identifiant, mot_de_passe):
"""
Initialise une entrée.
:param site: (str) Nom du site/service
:param identifiant: (str) Nom d'utilisateur ou email
:param mot_de_passe: (str) Mot de passe en clair
"""
self.site = site
self.identifiant = identifiant
self.mot_de_passe = mot_de_passe
def __repr__(self):
return f"MotDePasse({self.site}, {self.identifiant}, ****)"
def to_string(self):
"""Convertit en chaîne pour le stockage."""
return f"{self.site}|{self.identifiant}|{self.mot_de_passe}"
@staticmethod
def from_string(chaine):
"""Crée un objet depuis une chaîne."""
parties = chaine.split("|")
if len(parties) == 3:
return MotDePasse(parties[0], parties[1], parties[2])
return None
# =============================================================================
# PARTIE 3 : Gestionnaire principal
# =============================================================================
class GestionnaireMDP:
"""Gestionnaire de mots de passe sécurisé."""
def __init__(self, mot_de_passe_maitre):
"""
Initialise le gestionnaire.
:param mot_de_passe_maitre: (str) Clé principale de chiffrement
"""
self.cle = mot_de_passe_maitre
self.coffre = []
def ajouter(self, site, identifiant, mot_de_passe):
"""
Ajoute un nouveau mot de passe au coffre.
:param site: (str) Nom du site
:param identifiant: (str) Identifiant
:param mot_de_passe: (str) Mot de passe
"""
# Vérifier si le site existe déjà
for mdp in self.coffre:
if mdp.site.lower() == site.lower():
# Mettre à jour l'entrée existante
mdp.identifiant = identifiant
mdp.mot_de_passe = mot_de_passe
return
# Sinon, créer une nouvelle entrée
nouvelle_entree = MotDePasse(site, identifiant, mot_de_passe)
self.coffre.append(nouvelle_entree)
def rechercher(self, site):
"""
Recherche un mot de passe par site.
:param site: (str) Nom du site à rechercher
:return: (MotDePasse ou None) L'entrée trouvée
"""
for mdp in self.coffre:
if mdp.site.lower() == site.lower():
return mdp
return None
def supprimer(self, site):
"""
Supprime une entrée du coffre.
:param site: (str) Nom du site à supprimer
:return: (bool) True si supprimé, False sinon
"""
for i, mdp in enumerate(self.coffre):
if mdp.site.lower() == site.lower():
self.coffre.pop(i)
return True
return False
def lister_sites(self):
"""
Liste tous les sites enregistrés.
:return: (list) Liste des noms de sites
"""
return [mdp.site for mdp in self.coffre]
def sauvegarder(self, fichier):
"""
Sauvegarde le coffre dans un fichier chiffré.
:param fichier: (str) Chemin du fichier
"""
if not self.coffre:
contenu = ""
else:
contenu = "\n".join([mdp.to_string() for mdp in self.coffre])
contenu_chiffre = xor_chiffrement(contenu, self.cle)
with open(fichier, 'w', encoding='utf-8') as f:
f.write(contenu_chiffre)
def charger(self, fichier):
"""
Charge le coffre depuis un fichier chiffré.
:param fichier: (str) Chemin du fichier
"""
with open(fichier, 'r', encoding='utf-8') as f:
contenu_chiffre = f.read()
if not contenu_chiffre:
self.coffre = []
return
contenu = xor_chiffrement(contenu_chiffre, self.cle)
lignes = contenu.split("\n")
self.coffre = []
for ligne in lignes:
if ligne:
mdp = MotDePasse.from_string(ligne)
if mdp:
self.coffre.append(mdp)
# =============================================================================
# PARTIE 4 : Génération de mots de passe
# =============================================================================
def generer_mot_de_passe(longueur=16, majuscules=True, chiffres=True, speciaux=True):
"""
Génère un mot de passe aléatoire robuste.
:param longueur: (int) Longueur du mot de passe
:param majuscules: (bool) Inclure des majuscules
:param chiffres: (bool) Inclure des chiffres
:param speciaux: (bool) Inclure des caractères spéciaux
:return: (str) Mot de passe généré
"""
caracteres = string.ascii_lowercase
if majuscules:
caracteres += string.ascii_uppercase
if chiffres:
caracteres += string.digits
if speciaux:
caracteres += "!@#$%^&*()_+-=[]{}|;:,.<>?"
# Générer le mot de passe
mot_de_passe = ''.join(random.choice(caracteres) for _ in range(longueur))
return mot_de_passe
def evaluer_robustesse(mot_de_passe):
"""
Évalue la robustesse d'un mot de passe.
:param mot_de_passe: (str) Le mot de passe à évaluer
:return: (str) Niveau de robustesse
"""
score = 0
# Longueur
if len(mot_de_passe) >= 8:
score += 1
if len(mot_de_passe) >= 12:
score += 1
if len(mot_de_passe) >= 16:
score += 1
# Variété de caractères
if any(c.islower() for c in mot_de_passe):
score += 1
if any(c.isupper() for c in mot_de_passe):
score += 1
if any(c.isdigit() for c in mot_de_passe):
score += 1
if any(c in "!@#$%^&*()_+-=[]{}|;:,.<>?" for c in mot_de_passe):
score += 1
# Retourner le niveau
if score <= 2:
return "Faible"
elif score <= 4:
return "Moyen"
elif score <= 5:
return "Fort"
else:
return "Très fort"
# =============================================================================
# PARTIE 5 : Interface utilisateur
# =============================================================================
def menu_principal():
"""Affiche le menu principal."""
print("\n" + "=" * 40)
print(" GESTIONNAIRE DE MOTS DE PASSE")
print("=" * 40)
print("1. Ajouter un mot de passe")
print("2. Rechercher un mot de passe")
print("3. Générer un mot de passe")
print("4. Lister les sites")
print("5. Supprimer une entrée")
print("6. Sauvegarder")
print("7. Quitter")
print("=" * 40)
return input("Votre choix : ")
def application():
"""Lance l'application."""
print("Bienvenue dans le gestionnaire de mots de passe !")
mdp_maitre = input("Entrez votre mot de passe maître : ")
gestionnaire = GestionnaireMDP(mdp_maitre)
try:
gestionnaire.charger("coffre.dat")
print("Coffre chargé avec succès.")
except FileNotFoundError:
print("Nouveau coffre créé.")
while True:
choix = menu_principal()
if choix == "1":
site = input("Site : ")
identifiant = input("Identifiant : ")
mdp = input("Mot de passe (laisser vide pour générer) : ")
if mdp == "":
mdp = generer_mot_de_passe()
print(f"Mot de passe généré : {mdp}")
gestionnaire.ajouter(site, identifiant, mdp)
print("Ajouté !")
elif choix == "2":
site = input("Site à rechercher : ")
resultat = gestionnaire.rechercher(site)
if resultat:
print(f"Site : {resultat.site}")
print(f"Identifiant : {resultat.identifiant}")
print(f"Mot de passe : {resultat.mot_de_passe}")
else:
print("Site non trouvé.")
elif choix == "3":
try:
longueur = int(input("Longueur (défaut 16) : ") or "16")
except ValueError:
longueur = 16
mdp = generer_mot_de_passe(longueur)
print(f"Mot de passe généré : {mdp}")
print(f"Robustesse : {evaluer_robustesse(mdp)}")
elif choix == "4":
sites = gestionnaire.lister_sites()
if sites:
print("Sites enregistrés :")
for s in sites:
print(f" - {s}")
else:
print("Aucun site enregistré.")
elif choix == "5":
site = input("Site à supprimer : ")
if gestionnaire.supprimer(site):
print("Supprimé !")
else:
print("Site non trouvé.")
elif choix == "6":
gestionnaire.sauvegarder("coffre.dat")
print("Coffre sauvegardé.")
elif choix == "7":
gestionnaire.sauvegarder("coffre.dat")
print("Au revoir !")
break
# =============================================================================
# PARTIE 6 : Améliorations (Bonus)
# =============================================================================
def hasher(mot_de_passe):
"""Retourne le hash SHA-256 du mot de passe."""
return hashlib.sha256(mot_de_passe.encode()).hexdigest()
class GestionnaireMDPSecurise(GestionnaireMDP):
"""Version améliorée avec hashage du mot de passe maître."""
def __init__(self, mot_de_passe_maitre):
# Utiliser le hash comme clé (plus long = plus sécurisé)
cle_derivee = hasher(mot_de_passe_maitre)
super().__init__(cle_derivee)
self.hash_maitre = hasher(mot_de_passe_maitre + "salt_verification")
def verifier_mot_de_passe(self, mot_de_passe):
"""Vérifie si le mot de passe maître est correct."""
return hasher(mot_de_passe + "salt_verification") == self.hash_maitre
# =============================================================================
# RÉPONSES AUX QUESTIONS
# =============================================================================
"""
RÉPONSES AUX QUESTIONS DE SYNTHÈSE
1. Pourquoi ne jamais stocker un mot de passe en clair ?
- Si un attaquant accède à la base de données, il obtient tous les mots de passe
- Les utilisateurs réutilisent souvent leurs mots de passe sur plusieurs sites
- C'est une violation des bonnes pratiques de sécurité (RGPD, etc.)
2. Différence entre chiffrement et hashage :
- Chiffrement : opération réversible avec une clé (on peut retrouver le message)
- Hashage : opération irréversible (on ne peut pas retrouver le message original)
- On chiffre les données confidentielles, on hashe les mots de passe
3. Pourquoi le mot de passe maître doit être robuste ?
- C'est la seule protection de tous vos mots de passe
- Une attaque par force brute pourrait le trouver si trop faible
- Compromis = tous les mots de passe compromis
4. Risques si quelqu'un obtient coffre.dat ?
- Il peut tenter une attaque par force brute sur le mot de passe maître
- XOR est vulnérable à certaines attaques (ex: known-plaintext attack)
- C'est pourquoi les vrais gestionnaires utilisent AES-256
5. Comment les vrais gestionnaires améliorent la sécurité ?
- Chiffrement AES-256 au lieu de XOR
- Dérivation de clé (PBKDF2, Argon2) pour ralentir les attaques
- Authentification à deux facteurs
- Chiffrement de bout en bout
- Audit de sécurité par des experts
"""
# =============================================================================
# TESTS
# =============================================================================
if __name__ == "__main__":
print("=" * 60)
print("TEST 1 : CHIFFREMENT XOR")
print("=" * 60)
message = "Bonjour le monde !"
cle = "secret"
chiffre = xor_chiffrement(message, cle)
print(f"Message original : {message}")
print(f"Clé : {cle}")
print(f"Message chiffré : {repr(chiffre)}")
dechiffre = xor_chiffrement(chiffre, cle)
print(f"Message déchiffré : {dechiffre}")
print(f"Vérification : {message == dechiffre}")
print("\n" + "=" * 60)
print("TEST 2 : GÉNÉRATION DE MOT DE PASSE")
print("=" * 60)
for _ in range(5):
mdp = generer_mot_de_passe(16)
robustesse = evaluer_robustesse(mdp)
print(f"{mdp} -> {robustesse}")
print("\n" + "=" * 60)
print("TEST 3 : GESTIONNAIRE")
print("=" * 60)
gestionnaire = GestionnaireMDP("mon_mot_de_passe_maitre")
gestionnaire.ajouter("Gmail", "alice@gmail.com", "mdp_gmail_123")
gestionnaire.ajouter("Facebook", "alice", "fb_password!")
gestionnaire.ajouter("Netflix", "alice@gmail.com", "netflix2024")
print("Sites enregistrés :", gestionnaire.lister_sites())
resultat = gestionnaire.rechercher("gmail")
if resultat:
print(f"Gmail -> {resultat.identifiant} : {resultat.mot_de_passe}")
print("\n" + "=" * 60)
print("TEST 4 : SAUVEGARDE/CHARGEMENT")
print("=" * 60)
gestionnaire.sauvegarder("test_coffre.dat")
print("Coffre sauvegardé.")
nouveau_gestionnaire = GestionnaireMDP("mon_mot_de_passe_maitre")
nouveau_gestionnaire.charger("test_coffre.dat")
print("Coffre chargé.")
print("Sites récupérés :", nouveau_gestionnaire.lister_sites())
# Nettoyage
import os
os.remove("test_coffre.dat")
print("\n" + "=" * 60)
print("Pour lancer l'application interactive :")
print(" application()")
print("=" * 60)