# TP : Gestionnaire de mots de passe ## Contexte Vous développez un gestionnaire de mots de passe simplifié, similaire à **Bitwarden**, **1Password** ou **KeePass**. Ce type d'application permet de stocker de manière sécurisée tous ses identifiants en les chiffrant avec un mot de passe maître. L'objectif est de comprendre les mécanismes de chiffrement symétrique et les bonnes pratiques de sécurité. --- ## Objectifs - Implémenter un chiffrement symétrique simple - Comprendre le rôle du mot de passe maître - Stocker et récupérer des données chiffrées - Générer des mots de passe robustes --- ## Partie 1 : Chiffrement XOR ### 1.1. Principe du XOR L'opération XOR (ou exclusif) est fondamentale en cryptographie. Elle possède une propriété intéressante : ``` A XOR B XOR B = A ``` Autrement dit, appliquer deux fois XOR avec la même clé redonne le message original. ### 1.2. Implémentation Implémenter la fonction de chiffrement XOR : ```python 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 ``` **Test** : ```python >>> message = "Bonjour le monde" >>> cle = "secret" >>> chiffre = xor_chiffrement(message, cle) >>> print(chiffre) # Caractères illisibles >>> xor_chiffrement(chiffre, cle) # Déchiffrement 'Bonjour le monde' ``` ### 1.3. Questions 1. Pourquoi la clé est-elle utilisée de manière cyclique ? 2. Que se passe-t-il si la clé est aussi longue que le message ? 3. Pourquoi XOR seul n'est-il pas suffisamment sécurisé ? --- ## Partie 2 : Classe MotDePasse ### 2.1. Structure d'une entrée Créer une classe pour représenter un mot de passe stocké : ```python 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): # Ne pas afficher le mot de passe en clair ! 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("|") return MotDePasse(parties[0], parties[1], parties[2]) ``` --- ## Partie 3 : Gestionnaire principal ### 3.1. Classe GestionnaireMDP Implémenter le gestionnaire complet : ```python 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 = [] # Liste des MotDePasse 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 """ # À compléter pass 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 """ # À compléter pass def supprimer(self, site): """ Supprime une entrée du coffre. :param site: (str) Nom du site à supprimer :return: (bool) True si supprimé, False sinon """ # À compléter pass def lister_sites(self): """ Liste tous les sites enregistrés (sans les mots de passe). :return: (list) Liste des noms de sites """ # À compléter pass ``` ### 3.2. Sauvegarde chiffrée Ajouter les méthodes de sauvegarde et chargement : ```python def sauvegarder(self, fichier): """ Sauvegarde le coffre dans un fichier chiffré. :param fichier: (str) Chemin du fichier """ # Convertir toutes les entrées en une chaîne contenu = "\n".join([mdp.to_string() for mdp in self.coffre]) # Chiffrer le contenu contenu_chiffre = xor_chiffrement(contenu, self.cle) # Écrire dans le fichier 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 """ # À compléter pass ``` --- ## Partie 4 : Génération de mots de passe ### 4.1. Générateur simple Créer une fonction de génération de mots de passe robustes : ```python import random import string 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 += "!@#$%^&*()_+-=[]{}|;:,.<>?" # À compléter : générer le mot de passe pass ``` ### 4.2. Vérification de robustesse Implémenter une fonction d'évaluation : ```python 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 (Faible, Moyen, Fort, Très fort) """ 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 # À compléter : retourner le niveau selon le score pass ``` --- ## Partie 5 : Interface utilisateur ### 5.1. Menu interactif Créer une interface en ligne de commande : ```python 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) # Tenter de charger un coffre existant 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": # Ajouter 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": # Rechercher 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": # Générer longueur = int(input("Longueur (défaut 16) : ") or "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": # Lister sites = gestionnaire.lister_sites() print("Sites enregistrés :") for s in sites: print(f" - {s}") elif choix == "5": # Supprimer site = input("Site à supprimer : ") if gestionnaire.supprimer(site): print("Supprimé !") else: print("Site non trouvé.") elif choix == "6": # Sauvegarder gestionnaire.sauvegarder("coffre.dat") print("Coffre sauvegardé.") elif choix == "7": gestionnaire.sauvegarder("coffre.dat") print("Au revoir !") break ``` --- ## Partie 6 : Améliorations (Bonus) ### 6.1. Hashage du mot de passe maître Au lieu de stocker le mot de passe maître directement, utiliser un hash : ```python import hashlib def hasher(mot_de_passe): """Retourne le hash SHA-256 du mot de passe.""" return hashlib.sha256(mot_de_passe.encode()).hexdigest() ``` ### 6.2. Dérivation de clé Utiliser une fonction de dérivation de clé (PBKDF2) pour renforcer la sécurité. ### 6.3. Chiffrement AES Remplacer XOR par AES pour un chiffrement professionnel (nécessite la bibliothèque `cryptography`). --- ## Questions de synthèse 1. Pourquoi ne faut-il **jamais** stocker un mot de passe en clair ? 2. Quelle est la différence entre **chiffrement** et **hashage** ? 3. Pourquoi le mot de passe maître doit-il être robuste ? 4. Quels sont les risques si quelqu'un obtient le fichier `coffre.dat` ? 5. Comment les vrais gestionnaires de mots de passe (Bitwarden, 1Password) améliorent-ils la sécurité ? --- ## Barème indicatif | Partie | Points | |--------|--------| | Partie 1 : Chiffrement XOR | 3 | | Partie 2 : Classe MotDePasse | 2 | | Partie 3 : Gestionnaire | 5 | | Partie 4 : Génération | 4 | | Partie 5 : Interface | 4 | | Partie 6 : Bonus | 2 | | **Total** | **20** | --- Auteur : Florian Mathieu Licence CC BY NC Licence Creative Commons
Ce cours est mis à disposition selon les termes de la Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International.