| Question | Réponse |
|---|---|
| Entrée | length, la longueur voulue (un entier, 16 par défaut) |
| Sortie | une chaîne de caractères aléatoire de length caractères |
| Contraintes | au moins une majuscule, une minuscule, un chiffre et un caractère spécial, à des positions variables |
| Cas d'erreur | length inférieur à 12 : lever une ValueError |
| Vérification | 1 000 mots de passe générés, tous validés par check_password() du défi 1 |
>>> import secrets
>>> import string
>>> string.ascii_uppercase
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
>>> string.digits
'0123456789'
>>> secrets.choice("ABC")
'B'
>>> chars = ["a", "B", "7"]
>>> secrets.SystemRandom().shuffle(chars)
>>> "".join(chars)
'7aB'
string fournit les alphabets tout faitssecrets.choice() tire un caractère au hasard, de façon imprévisibleshuffle() mélange une liste sur place. Une chaîne ne se modifie pas, on travaille donc avec une liste"".join(liste) recolle la liste en une chaînerandom pour un secret. Il est prévisible. Qui connaît son état devine la suite. secrets est fait pour ça.
si la longueur est inférieure à 12 : lever une ValueError
préparer les 4 catégories : majuscules, minuscules, chiffres, spéciaux
créer une liste vide de caractères
pour chaque catégorie : tirer un caractère de cette catégorie
pour chaque caractère manquant : tirer un caractère parmi toutes les catégories
mélanger la liste
recoller la liste en une chaîne et la renvoyer
Tirer d'abord un caractère de chaque catégorie garantit les 4 règles. Le mélange final évite qu'elles soient toujours aux mêmes positions.
import secrets
import string
from challenge1 import check_password, MIN_LENGTH
def generate_password(length=16):
"""Génère un mot de passe aléatoire robuste."""
if length < MIN_LENGTH:
raise ValueError(f"longueur minimale : {MIN_LENGTH}")
return ""
MIN_LENGTH du défi 1. La règle des 12 caractères n'est écrite qu'à un endroitraise arrête la fonction et signale l'erreur à celui qui l'appellereturn "" provisoire permet déjà d'exécuter et de tester
SPECIALS = "!@#$%^&*()-_=+[]{};:,.?/"
def generate_password(length=16):
... # vérification de la longueur
categories = [string.ascii_uppercase,
string.ascii_lowercase,
string.digits, SPECIALS]
chars = []
for category in categories:
chars.append(secrets.choice(category))
print(chars) # ['K', 'q', '4', '#']
return ""
print() provisoire montre que l'étape fonctionne
all_chars = "".join(categories)
for _ in range(length - len(chars)):
chars.append(secrets.choice(all_chars))
secrets.SystemRandom().shuffle(chars)
return "".join(chars)
all_chars réunit les 4 catégories en une seule chaînelength - len(chars) caractères, soit 12 pour une longueur de 16_ est le nom d'usage d'une variable de boucle dont on ne se sert pas
print(generate_password()) # par exemple : 45h#O_$APXE[QLje
print(generate_password(24)) # par exemple : {U/7/lzny-b{MJb[-B1agCX(
try:
generate_password(8)
except ValueError as error:
print("Erreur :", error) # Erreur : longueur minimale : 12
Le try / except sert seulement à afficher l'erreur sans arrêter le programme de test.
invalid = 0
for _ in range(1000):
valid, errors = check_password(generate_password())
if not valid:
invalid += 1
print("Mots de passe invalides sur 1000 :", invalid)
# Mots de passe invalides sur 1000 : 0
check_password() renvoie un tuple, que l'on déballe en deux variableschallenge1.py doit être dans le même dossier que challenge3.pychallenge1.py s'affichent à l'import ? Placez-les sous if __name__ == "__main__":. Ils ne s'exécuteront plus que si l'on lance ce fichier directement.
| Piège | Correction |
|---|---|
random au lieu de secrets | un secret doit être imprévisible. Utiliser secrets |
| 16 caractères tirés parmi tous | rien ne garantit les 4 catégories. En tirer une de chaque d'abord |
| pas de mélange | les 4 premiers caractères suivent toujours le même ordre |
print("erreur") au lieu de raise | la fonction continue. Lever une ValueError |
ImportError sur challenge1 | les deux fichiers doivent être dans le même dossier |