passmaker 0.1.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.
passmaker/__init__.py
ADDED
|
File without changes
|
passmaker/__main__.py
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import contextlib
|
|
4
|
+
import functools
|
|
5
|
+
import secrets
|
|
6
|
+
import string
|
|
7
|
+
from typing import Callable, Sequence
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
from click_option_group import optgroup
|
|
11
|
+
|
|
12
|
+
# Rule is a filter to an alphabet
|
|
13
|
+
type Rule = Callable[[str, set[str]], set[str]]
|
|
14
|
+
|
|
15
|
+
QWERTY: str = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def no_duplicates(password: str, alphabet: set[str]) -> set[str]:
|
|
19
|
+
"""Don't use the same character more than once in a password.
|
|
20
|
+
|
|
21
|
+
This rule removes from alphabet every char from password."""
|
|
22
|
+
if not password:
|
|
23
|
+
return alphabet
|
|
24
|
+
|
|
25
|
+
return alphabet - set(password)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def no_sequence_characters(password: str, alphabet: set[str]) -> set[str]:
|
|
29
|
+
"""Don't use characters in password in a sequence, e.g. abc, 123, etc.
|
|
30
|
+
|
|
31
|
+
This rule enforces that the next symbol in password cannot be from
|
|
32
|
+
sequence, thus it's removed from alphabet."""
|
|
33
|
+
|
|
34
|
+
def _get_char(string: str, idx: int) -> str:
|
|
35
|
+
try:
|
|
36
|
+
return string[idx]
|
|
37
|
+
except IndexError:
|
|
38
|
+
return ""
|
|
39
|
+
|
|
40
|
+
if not password:
|
|
41
|
+
return alphabet
|
|
42
|
+
|
|
43
|
+
with contextlib.suppress(ValueError):
|
|
44
|
+
index = QWERTY.index(password[-1])
|
|
45
|
+
|
|
46
|
+
alphabet -= {
|
|
47
|
+
_get_char(QWERTY, index - 1),
|
|
48
|
+
_get_char(QWERTY, index + 1),
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return alphabet - {chr(ord(password[-1]) - 1), chr(ord(password[-1]) + 1)}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def no_similar_characters(password: str, alphabet: set[str]) -> set[str]:
|
|
55
|
+
"""Don't use similar characters in password, e.g. i, I, L, l, 1, etc.
|
|
56
|
+
|
|
57
|
+
This rule removes from alphabet these characters: i1Il|, oO0, `.:"""
|
|
58
|
+
return alphabet - set("i1Il|oO0`.:")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
all_rules = [no_duplicates, no_sequence_characters, no_similar_characters]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def build_alphabet(
|
|
65
|
+
*,
|
|
66
|
+
digits: bool = True,
|
|
67
|
+
lowercase: bool = True,
|
|
68
|
+
uppercase: bool = True,
|
|
69
|
+
symbols: bool | str = True,
|
|
70
|
+
) -> str:
|
|
71
|
+
alphabet = ""
|
|
72
|
+
|
|
73
|
+
if digits:
|
|
74
|
+
alphabet += string.digits
|
|
75
|
+
if lowercase:
|
|
76
|
+
alphabet += string.ascii_lowercase
|
|
77
|
+
if uppercase:
|
|
78
|
+
alphabet += string.ascii_uppercase
|
|
79
|
+
if symbols:
|
|
80
|
+
alphabet += (
|
|
81
|
+
symbols if isinstance(symbols, str) else string.punctuation
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
return alphabet
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def generate_password(
|
|
88
|
+
length: int,
|
|
89
|
+
alphabet: str | None = None,
|
|
90
|
+
rules: Sequence[Rule] = all_rules,
|
|
91
|
+
) -> str:
|
|
92
|
+
if alphabet is None:
|
|
93
|
+
alphabet = build_alphabet()
|
|
94
|
+
|
|
95
|
+
password = ""
|
|
96
|
+
|
|
97
|
+
for _ in range(length):
|
|
98
|
+
allowed_symbols = functools.reduce(
|
|
99
|
+
lambda alpha, rule: rule(password, alpha),
|
|
100
|
+
rules,
|
|
101
|
+
set(alphabet),
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
password += secrets.choice(list(allowed_symbols))
|
|
105
|
+
|
|
106
|
+
return password
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@click.command()
|
|
110
|
+
@click.option(
|
|
111
|
+
"-l", "--length", default=22, type=int, help="Length of each password."
|
|
112
|
+
)
|
|
113
|
+
@click.option(
|
|
114
|
+
"-c",
|
|
115
|
+
"--count",
|
|
116
|
+
default=1,
|
|
117
|
+
type=int,
|
|
118
|
+
help="Count of passwords to generate in one take.",
|
|
119
|
+
)
|
|
120
|
+
@optgroup.group(
|
|
121
|
+
"Password alphabet",
|
|
122
|
+
help="Options controlling what set of symbols can be used in a password.",
|
|
123
|
+
)
|
|
124
|
+
@optgroup.option(
|
|
125
|
+
"-d/-D",
|
|
126
|
+
"--digits/--no-digits",
|
|
127
|
+
"use_digits",
|
|
128
|
+
default=True,
|
|
129
|
+
help="Where to use digits in password or no (default: yes)",
|
|
130
|
+
)
|
|
131
|
+
@optgroup.option(
|
|
132
|
+
"-a/-A",
|
|
133
|
+
"--alpha/--no-alpha",
|
|
134
|
+
"use_alpha",
|
|
135
|
+
default=True,
|
|
136
|
+
help="Where to use ASCII alphabet symbols in password or no "
|
|
137
|
+
"(default: yes)",
|
|
138
|
+
)
|
|
139
|
+
@optgroup.option(
|
|
140
|
+
"-S",
|
|
141
|
+
"--no-symbols",
|
|
142
|
+
default=False,
|
|
143
|
+
help="Don't use punctuation symbols for password generation "
|
|
144
|
+
"(overrides 'symbols' option).",
|
|
145
|
+
)
|
|
146
|
+
@optgroup.option(
|
|
147
|
+
"-s",
|
|
148
|
+
"--symbols",
|
|
149
|
+
type=str,
|
|
150
|
+
help="Use these exact symbols for password generation.",
|
|
151
|
+
)
|
|
152
|
+
@optgroup.group(
|
|
153
|
+
"Generation rules", help="Set of rules to control password generation."
|
|
154
|
+
)
|
|
155
|
+
@optgroup.option(
|
|
156
|
+
"--no-duplicates/--duplicates",
|
|
157
|
+
"use_duplicates",
|
|
158
|
+
default=True,
|
|
159
|
+
help="Allow or disallow duplicate characters.",
|
|
160
|
+
)
|
|
161
|
+
@optgroup.option(
|
|
162
|
+
"--no-sequence/--sequence",
|
|
163
|
+
"use_sequence",
|
|
164
|
+
default=True,
|
|
165
|
+
help="Allow or disallow sequential characters.",
|
|
166
|
+
)
|
|
167
|
+
@optgroup.option(
|
|
168
|
+
"--no-similar/--similar",
|
|
169
|
+
"use_similar",
|
|
170
|
+
default=True,
|
|
171
|
+
help="Allow or disallow similar looking characters.",
|
|
172
|
+
)
|
|
173
|
+
def main( # noqa: PLR0913
|
|
174
|
+
length: int,
|
|
175
|
+
count: int,
|
|
176
|
+
*,
|
|
177
|
+
use_digits: bool,
|
|
178
|
+
use_alpha: bool,
|
|
179
|
+
symbols: str,
|
|
180
|
+
no_symbols: bool,
|
|
181
|
+
use_duplicates: bool,
|
|
182
|
+
use_sequence: bool,
|
|
183
|
+
use_similar: bool,
|
|
184
|
+
) -> None:
|
|
185
|
+
"""Simple password generator. Safe to use, generates quite strong
|
|
186
|
+
passwords.
|
|
187
|
+
"""
|
|
188
|
+
alphabet = build_alphabet(
|
|
189
|
+
digits=use_digits,
|
|
190
|
+
lowercase=use_alpha,
|
|
191
|
+
uppercase=use_alpha,
|
|
192
|
+
symbols=False if no_symbols else symbols or True,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
rules = []
|
|
196
|
+
|
|
197
|
+
if use_duplicates:
|
|
198
|
+
rules.append(no_duplicates)
|
|
199
|
+
if use_sequence:
|
|
200
|
+
rules.append(no_sequence_characters)
|
|
201
|
+
if use_similar:
|
|
202
|
+
rules.append(no_similar_characters)
|
|
203
|
+
|
|
204
|
+
for _ in range(count):
|
|
205
|
+
print(generate_password(length, alphabet=alphabet, rules=rules)) # noqa: T201
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
if __name__ == "__main__":
|
|
209
|
+
main()
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
passmaker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
passmaker/__main__.py,sha256=Xf46YaxRbSkPAPJTKhKQIw830JDAhUYHIxgJmmU-pbQ,5274
|
|
3
|
+
passmaker-0.1.0.dist-info/METADATA,sha256=mvcxsGmjidwK9TMjDqBsLz8Yi1WZSswZ6Fw9jTddtDE,249
|
|
4
|
+
passmaker-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
5
|
+
passmaker-0.1.0.dist-info/entry_points.txt,sha256=yP7kgl6dQ8HOszYiyx1BfDw4Ax_zT7wMvyYIFDnsOEk,54
|
|
6
|
+
passmaker-0.1.0.dist-info/RECORD,,
|