458 lines
14 KiB
Python
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)
|