Password-Generator-Advanced 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- password_generator_advanced/__init__.py +1 -0
- password_generator_advanced/__main__.py +5 -0
- password_generator_advanced/clipboard.py +26 -0
- password_generator_advanced/generator.py +99 -0
- password_generator_advanced/main.py +281 -0
- password_generator_advanced/wordlist.py +2052 -0
- password_generator_advanced-1.0.0.dist-info/METADATA +11 -0
- password_generator_advanced-1.0.0.dist-info/RECORD +10 -0
- password_generator_advanced-1.0.0.dist-info/WHEEL +4 -0
- password_generator_advanced-1.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.0.0"
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Copie dans le presse-papier (cross-platform, stdlib uniquement)."""
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def copy_to_clipboard(text: str) -> bool:
|
|
8
|
+
try:
|
|
9
|
+
if sys.platform == "win32":
|
|
10
|
+
process = subprocess.Popen(
|
|
11
|
+
["clip"], stdin=subprocess.PIPE, shell=True
|
|
12
|
+
)
|
|
13
|
+
process.communicate(text.encode("utf-16le"))
|
|
14
|
+
elif sys.platform == "darwin":
|
|
15
|
+
process = subprocess.Popen(
|
|
16
|
+
["pbcopy"], stdin=subprocess.PIPE
|
|
17
|
+
)
|
|
18
|
+
process.communicate(text.encode("utf-8"))
|
|
19
|
+
else:
|
|
20
|
+
process = subprocess.Popen(
|
|
21
|
+
["xclip", "-selection", "clipboard"], stdin=subprocess.PIPE
|
|
22
|
+
)
|
|
23
|
+
process.communicate(text.encode("utf-8"))
|
|
24
|
+
return process.returncode == 0
|
|
25
|
+
except (OSError, FileNotFoundError):
|
|
26
|
+
return False
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Générateur de mots de passe sécurisés avec contraintes strictes."""
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
import secrets
|
|
5
|
+
import string
|
|
6
|
+
|
|
7
|
+
from .wordlist import WORDLIST
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
DEFAULT_MIN_DIGITS = 9
|
|
11
|
+
DEFAULT_MIN_SPECIAL = 9
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def minimum_length(min_digits: int, min_special: int) -> int:
|
|
15
|
+
return min_digits + min_special + 2
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def generate_password(
|
|
19
|
+
length: int,
|
|
20
|
+
min_digits: int = DEFAULT_MIN_DIGITS,
|
|
21
|
+
min_special: int = DEFAULT_MIN_SPECIAL,
|
|
22
|
+
) -> str:
|
|
23
|
+
min_len = minimum_length(min_digits, min_special)
|
|
24
|
+
if length < min_len:
|
|
25
|
+
raise ValueError(
|
|
26
|
+
f"La longueur minimale est {min_len} "
|
|
27
|
+
f"({min_digits} chiffres + {min_special} spéciaux + 1 min + 1 maj)"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
lowercase = string.ascii_lowercase
|
|
31
|
+
uppercase = string.ascii_uppercase
|
|
32
|
+
digits = string.digits
|
|
33
|
+
special = string.punctuation
|
|
34
|
+
|
|
35
|
+
password_chars: list[str] = []
|
|
36
|
+
|
|
37
|
+
password_chars.extend(secrets.choice(digits) for _ in range(min_digits))
|
|
38
|
+
password_chars.extend(secrets.choice(special) for _ in range(min_special))
|
|
39
|
+
password_chars.append(secrets.choice(lowercase))
|
|
40
|
+
password_chars.append(secrets.choice(uppercase))
|
|
41
|
+
|
|
42
|
+
all_chars = lowercase + uppercase + digits + special
|
|
43
|
+
remaining = length - len(password_chars)
|
|
44
|
+
password_chars.extend(secrets.choice(all_chars) for _ in range(remaining))
|
|
45
|
+
|
|
46
|
+
result = list(password_chars)
|
|
47
|
+
secrets.SystemRandom().shuffle(result)
|
|
48
|
+
|
|
49
|
+
return "".join(result)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def generate_passphrase(num_words: int = 6, separator: str = "-") -> str:
|
|
53
|
+
if num_words < 1:
|
|
54
|
+
raise ValueError("Le nombre de mots doit être au moins 1")
|
|
55
|
+
words = [secrets.choice(WORDLIST) for _ in range(num_words)]
|
|
56
|
+
return separator.join(words)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def evaluate_password(password: str) -> dict:
|
|
60
|
+
length = len(password)
|
|
61
|
+
charset_size = 0
|
|
62
|
+
|
|
63
|
+
has_lower = any(c.islower() for c in password)
|
|
64
|
+
has_upper = any(c.isupper() for c in password)
|
|
65
|
+
has_digits = any(c.isdigit() for c in password)
|
|
66
|
+
has_special = any(not c.isalnum() for c in password)
|
|
67
|
+
|
|
68
|
+
if has_lower:
|
|
69
|
+
charset_size += 26
|
|
70
|
+
if has_upper:
|
|
71
|
+
charset_size += 26
|
|
72
|
+
if has_digits:
|
|
73
|
+
charset_size += 10
|
|
74
|
+
if has_special:
|
|
75
|
+
charset_size += 32
|
|
76
|
+
|
|
77
|
+
entropy = length * math.log2(charset_size) if charset_size > 0 else 0
|
|
78
|
+
|
|
79
|
+
if entropy >= 128:
|
|
80
|
+
strength = "Très fort"
|
|
81
|
+
elif entropy >= 80:
|
|
82
|
+
strength = "Fort"
|
|
83
|
+
elif entropy >= 64:
|
|
84
|
+
strength = "Moyen"
|
|
85
|
+
elif entropy >= 48:
|
|
86
|
+
strength = "Faible"
|
|
87
|
+
else:
|
|
88
|
+
strength = "Très faible"
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
"length": length,
|
|
92
|
+
"entropy": round(entropy, 1),
|
|
93
|
+
"charset_size": charset_size,
|
|
94
|
+
"has_lower": has_lower,
|
|
95
|
+
"has_upper": has_upper,
|
|
96
|
+
"has_digits": has_digits,
|
|
97
|
+
"has_special": has_special,
|
|
98
|
+
"strength": strength,
|
|
99
|
+
}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
"""Menu interactif et CLI du générateur de mots de passe."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
if sys.platform == "win32":
|
|
8
|
+
os.system("")
|
|
9
|
+
if sys.stdout.encoding.lower() != "utf-8":
|
|
10
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
11
|
+
sys.stderr.reconfigure(encoding="utf-8")
|
|
12
|
+
|
|
13
|
+
from .clipboard import copy_to_clipboard
|
|
14
|
+
from .generator import (
|
|
15
|
+
DEFAULT_MIN_DIGITS,
|
|
16
|
+
DEFAULT_MIN_SPECIAL,
|
|
17
|
+
evaluate_password,
|
|
18
|
+
generate_passphrase,
|
|
19
|
+
generate_password,
|
|
20
|
+
minimum_length,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def print_header(min_digits: int, min_special: int):
|
|
25
|
+
min_len = minimum_length(min_digits, min_special)
|
|
26
|
+
print("\n" + "=" * 50)
|
|
27
|
+
print(" GÉNÉRATEUR DE MOTS DE PASSE SÉCURISÉS")
|
|
28
|
+
print("=" * 50)
|
|
29
|
+
print("\nContraintes appliquées :")
|
|
30
|
+
print(f" - Minimum {min_digits} chiffres")
|
|
31
|
+
print(f" - Minimum {min_special} caractères spéciaux")
|
|
32
|
+
print(" - Au moins 1 lettre minuscule")
|
|
33
|
+
print(" - Au moins 1 lettre majuscule")
|
|
34
|
+
print(f" - Longueur minimale : {min_len} caractères")
|
|
35
|
+
print()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def print_menu():
|
|
39
|
+
print("-" * 50)
|
|
40
|
+
print(" 1 - Générer un mot de passe")
|
|
41
|
+
print(" 2 - Générer plusieurs mots de passe")
|
|
42
|
+
print(" 3 - Générer une passphrase")
|
|
43
|
+
print(" 4 - Évaluer un mot de passe / passphrase")
|
|
44
|
+
print(" 5 - Quitter")
|
|
45
|
+
print("-" * 50)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def get_length(min_len: int) -> int:
|
|
49
|
+
while True:
|
|
50
|
+
try:
|
|
51
|
+
length = int(input(f"\nLongueur du mot de passe (min {min_len}) : "))
|
|
52
|
+
if length < min_len:
|
|
53
|
+
print(f" Erreur : minimum {min_len} caractères requis.")
|
|
54
|
+
continue
|
|
55
|
+
return length
|
|
56
|
+
except ValueError:
|
|
57
|
+
print(" Erreur : veuillez entrer un nombre entier.")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def get_count() -> int:
|
|
61
|
+
while True:
|
|
62
|
+
try:
|
|
63
|
+
count = int(input("\nCombien de mots de passe ? (1-50) : "))
|
|
64
|
+
if count < 1 or count > 50:
|
|
65
|
+
print(" Erreur : entre 1 et 50.")
|
|
66
|
+
continue
|
|
67
|
+
return count
|
|
68
|
+
except ValueError:
|
|
69
|
+
print(" Erreur : veuillez entrer un nombre entier.")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def get_num_words() -> int:
|
|
73
|
+
while True:
|
|
74
|
+
try:
|
|
75
|
+
num = int(input("\nNombre de mots (4-12, défaut 6) : ") or "6")
|
|
76
|
+
if num < 4 or num > 12:
|
|
77
|
+
print(" Erreur : entre 4 et 12 mots.")
|
|
78
|
+
continue
|
|
79
|
+
return num
|
|
80
|
+
except ValueError:
|
|
81
|
+
print(" Erreur : veuillez entrer un nombre entier.")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def offer_clipboard(text: str):
|
|
85
|
+
choice = input("\n Copier dans le presse-papier ? (o/N) : ").strip().lower()
|
|
86
|
+
if choice in ("o", "oui", "y", "yes"):
|
|
87
|
+
if copy_to_clipboard(text):
|
|
88
|
+
print(" ✓ Copié dans le presse-papier.")
|
|
89
|
+
else:
|
|
90
|
+
print(" ✗ Impossible de copier (outil clipboard non disponible).")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def display_password(password: str):
|
|
94
|
+
print("\n Mot de passe généré :")
|
|
95
|
+
print(f" {password}")
|
|
96
|
+
print(f" Longueur : {len(password)} caractères")
|
|
97
|
+
|
|
98
|
+
digits = sum(1 for c in password if c.isdigit())
|
|
99
|
+
special = sum(1 for c in password if not c.isalnum())
|
|
100
|
+
lower = sum(1 for c in password if c.islower())
|
|
101
|
+
upper = sum(1 for c in password if c.isupper())
|
|
102
|
+
|
|
103
|
+
print("\n Composition :")
|
|
104
|
+
print(f" Chiffres : {digits}")
|
|
105
|
+
print(f" Spéciaux : {special}")
|
|
106
|
+
print(f" Minuscules : {lower}")
|
|
107
|
+
print(f" Majuscules : {upper}")
|
|
108
|
+
|
|
109
|
+
offer_clipboard(password)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def display_passphrase(passphrase: str, num_words: int):
|
|
113
|
+
print("\n Passphrase générée :")
|
|
114
|
+
print(f" {passphrase}")
|
|
115
|
+
bits = num_words * 11
|
|
116
|
+
print(f" {num_words} mots — ~{bits} bits d'entropie")
|
|
117
|
+
|
|
118
|
+
offer_clipboard(passphrase)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def display_evaluation(result: dict):
|
|
122
|
+
print("\n Entropie théorique maximale :")
|
|
123
|
+
print(f" Longueur : {result['length']} caractères")
|
|
124
|
+
print(f" Entropie max. : {result['entropy']} bits")
|
|
125
|
+
print(f" Niveau : {result['strength']}")
|
|
126
|
+
print(f" Jeu de caractères: {result['charset_size']} symboles possibles")
|
|
127
|
+
print("\n Catégories détectées :")
|
|
128
|
+
print(f" Minuscules : {'✓' if result['has_lower'] else '✗'}")
|
|
129
|
+
print(f" Majuscules : {'✓' if result['has_upper'] else '✗'}")
|
|
130
|
+
print(f" Chiffres : {'✓' if result['has_digits'] else '✗'}")
|
|
131
|
+
print(f" Spéciaux : {'✓' if result['has_special'] else '✗'}")
|
|
132
|
+
print("\n ⚠ Ce calcul suppose un choix aléatoire par caractère.")
|
|
133
|
+
print(" Un mot de passe basé sur des mots du dictionnaire ou des")
|
|
134
|
+
print(" patterns prévisibles aura une entropie réelle inférieure.")
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def interactive_mode(min_digits: int, min_special: int):
|
|
138
|
+
min_len = minimum_length(min_digits, min_special)
|
|
139
|
+
print_header(min_digits, min_special)
|
|
140
|
+
|
|
141
|
+
while True:
|
|
142
|
+
print_menu()
|
|
143
|
+
choice = input("\n Votre choix (1-5) : ").strip()
|
|
144
|
+
|
|
145
|
+
if choice == "1":
|
|
146
|
+
length = get_length(min_len)
|
|
147
|
+
password = generate_password(length, min_digits, min_special)
|
|
148
|
+
display_password(password)
|
|
149
|
+
|
|
150
|
+
elif choice == "2":
|
|
151
|
+
length = get_length(min_len)
|
|
152
|
+
count = get_count()
|
|
153
|
+
print(f"\n {count} mot(s) de passe de {length} caractères :\n")
|
|
154
|
+
for i in range(count):
|
|
155
|
+
password = generate_password(length, min_digits, min_special)
|
|
156
|
+
print(f" {i + 1:2d}. {password}")
|
|
157
|
+
|
|
158
|
+
elif choice == "3":
|
|
159
|
+
num_words = get_num_words()
|
|
160
|
+
passphrase = generate_passphrase(num_words)
|
|
161
|
+
display_passphrase(passphrase, num_words)
|
|
162
|
+
|
|
163
|
+
elif choice == "4":
|
|
164
|
+
pwd = input("\n Entrez le mot de passe à évaluer : ")
|
|
165
|
+
if pwd:
|
|
166
|
+
result = evaluate_password(pwd)
|
|
167
|
+
display_evaluation(result)
|
|
168
|
+
else:
|
|
169
|
+
print(" Erreur : saisie vide.")
|
|
170
|
+
|
|
171
|
+
elif choice == "5":
|
|
172
|
+
print("\n Au revoir !\n")
|
|
173
|
+
break
|
|
174
|
+
|
|
175
|
+
else:
|
|
176
|
+
print("\n Erreur : choix invalide (1-5).")
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
180
|
+
parser = argparse.ArgumentParser(
|
|
181
|
+
prog="password-generator-advanced",
|
|
182
|
+
description="Générateur de mots de passe sécurisés avec contraintes strictes",
|
|
183
|
+
)
|
|
184
|
+
parser.add_argument(
|
|
185
|
+
"--length", "-l",
|
|
186
|
+
type=int,
|
|
187
|
+
help="Longueur du mot de passe à générer",
|
|
188
|
+
)
|
|
189
|
+
parser.add_argument(
|
|
190
|
+
"--min-digits",
|
|
191
|
+
type=int,
|
|
192
|
+
default=DEFAULT_MIN_DIGITS,
|
|
193
|
+
help=f"Nombre minimum de chiffres (défaut: {DEFAULT_MIN_DIGITS})",
|
|
194
|
+
)
|
|
195
|
+
parser.add_argument(
|
|
196
|
+
"--min-special",
|
|
197
|
+
type=int,
|
|
198
|
+
default=DEFAULT_MIN_SPECIAL,
|
|
199
|
+
help=f"Nombre minimum de caractères spéciaux (défaut: {DEFAULT_MIN_SPECIAL})",
|
|
200
|
+
)
|
|
201
|
+
parser.add_argument(
|
|
202
|
+
"--count", "-n",
|
|
203
|
+
type=int,
|
|
204
|
+
default=1,
|
|
205
|
+
help="Nombre de mots de passe à générer (défaut: 1)",
|
|
206
|
+
)
|
|
207
|
+
parser.add_argument(
|
|
208
|
+
"--passphrase", "-p",
|
|
209
|
+
action="store_true",
|
|
210
|
+
help="Générer une passphrase au lieu d'un mot de passe",
|
|
211
|
+
)
|
|
212
|
+
parser.add_argument(
|
|
213
|
+
"--words", "-w",
|
|
214
|
+
type=int,
|
|
215
|
+
default=6,
|
|
216
|
+
help="Nombre de mots pour la passphrase (défaut: 6)",
|
|
217
|
+
)
|
|
218
|
+
parser.add_argument(
|
|
219
|
+
"--separator",
|
|
220
|
+
type=str,
|
|
221
|
+
default="-",
|
|
222
|
+
help="Séparateur pour la passphrase (défaut: -)",
|
|
223
|
+
)
|
|
224
|
+
parser.add_argument(
|
|
225
|
+
"--copy", "-c",
|
|
226
|
+
action="store_true",
|
|
227
|
+
help="Copier le résultat dans le presse-papier",
|
|
228
|
+
)
|
|
229
|
+
parser.add_argument(
|
|
230
|
+
"--evaluate", "-e",
|
|
231
|
+
type=str,
|
|
232
|
+
help="Évaluer la force d'un mot de passe ou passphrase",
|
|
233
|
+
)
|
|
234
|
+
return parser
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def cli_mode(args: argparse.Namespace):
|
|
238
|
+
if args.evaluate:
|
|
239
|
+
result = evaluate_password(args.evaluate)
|
|
240
|
+
display_evaluation(result)
|
|
241
|
+
elif args.passphrase:
|
|
242
|
+
for _ in range(args.count):
|
|
243
|
+
passphrase = generate_passphrase(args.words, args.separator)
|
|
244
|
+
print(passphrase)
|
|
245
|
+
if args.copy and args.count == 1:
|
|
246
|
+
passphrase = generate_passphrase(args.words, args.separator)
|
|
247
|
+
copy_to_clipboard(passphrase)
|
|
248
|
+
else:
|
|
249
|
+
min_len = minimum_length(args.min_digits, args.min_special)
|
|
250
|
+
length = args.length if args.length else min_len
|
|
251
|
+
passwords = []
|
|
252
|
+
for _ in range(args.count):
|
|
253
|
+
password = generate_password(length, args.min_digits, args.min_special)
|
|
254
|
+
passwords.append(password)
|
|
255
|
+
print(password)
|
|
256
|
+
if args.copy and len(passwords) == 1:
|
|
257
|
+
copy_to_clipboard(passwords[0])
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def main():
|
|
261
|
+
parser = build_parser()
|
|
262
|
+
args = parser.parse_args()
|
|
263
|
+
|
|
264
|
+
has_cli_args = (
|
|
265
|
+
args.length is not None
|
|
266
|
+
or args.passphrase
|
|
267
|
+
or args.count > 1
|
|
268
|
+
or args.copy
|
|
269
|
+
or args.evaluate is not None
|
|
270
|
+
or args.min_digits != DEFAULT_MIN_DIGITS
|
|
271
|
+
or args.min_special != DEFAULT_MIN_SPECIAL
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
if has_cli_args:
|
|
275
|
+
cli_mode(args)
|
|
276
|
+
else:
|
|
277
|
+
interactive_mode(args.min_digits, args.min_special)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
if __name__ == "__main__":
|
|
281
|
+
main()
|