""" 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)