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 :
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
- Pourquoi la clé est-elle utilisée de manière cyclique ?
- Que se passe-t-il si la clé est aussi longue que le message ?
- 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
-
Pourquoi ne faut-il jamais stocker un mot de passe en clair ?
-
Quelle est la différence entre chiffrement et hashage ?
-
Pourquoi le mot de passe maître doit-il être robuste ?
-
Quels sont les risques si quelqu'un obtient le fichier
coffre.dat? -
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
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.