Aller au contenu

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 :

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 :

>>> 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é :

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 :

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 :

    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 :

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 :

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 :

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 :

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.