zora-cli 0.1.2__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.
zora/__init__.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.1.2"
zora/cli.py ADDED
@@ -0,0 +1,226 @@
1
+ import math
2
+ import time
3
+ import string
4
+ import argparse
5
+ import random, secrets
6
+ from colorama import Fore, init; init(autoreset=True);
7
+
8
+ def positive_int(value):
9
+ value = int(value)
10
+ if value <= 0:
11
+ raise argparse.ArgumentTypeError("must be greater than 0")
12
+ return value
13
+
14
+ CHARSETS = {
15
+ "digits": string.digits,
16
+ "letters": string.ascii_letters,
17
+ "lower": string.ascii_lowercase,
18
+ "upper": string.ascii_uppercase,
19
+ "hex": string.hexdigits,
20
+ "oct": string.octdigits,
21
+ "bin": "01",
22
+ "special": string.punctuation,
23
+ }
24
+
25
+ class Zora:
26
+ def __init__(self):
27
+ self.lines = []
28
+
29
+ def run(self):
30
+ self.start_timer()
31
+ self.parse_args()
32
+ self.build_charset()
33
+ self.generate_keys()
34
+ self.output()
35
+ self.show_timer()
36
+ self.show_entropy()
37
+
38
+
39
+ def build_charset(self):
40
+ value = self.args.charset
41
+ charset = ""
42
+
43
+ while value:
44
+ if value.startswith("@"):
45
+ value = value[1:]
46
+
47
+ matches = [
48
+ name for name in CHARSETS
49
+ if value.startswith(name)
50
+ ]
51
+
52
+ if not matches:
53
+ self.parser.error(f"unknown charset preset near: @{value}")
54
+
55
+ name = max(matches, key=len)
56
+ charset += CHARSETS[name]
57
+ value = value[len(name):]
58
+
59
+ else:
60
+ charset += value[0]
61
+ value = value[1:]
62
+
63
+ self.charset = "".join(dict.fromkeys(charset))
64
+
65
+ def parse_args(self):
66
+ """
67
+ Parses all the arguments
68
+ """
69
+ self.parser = argparse.ArgumentParser(
70
+ description="Generate random AUTH keys."
71
+ )
72
+
73
+ sc = self.parser.add_argument
74
+
75
+ #
76
+ # ARGUMENTS
77
+ #
78
+ sc("--charset-list", action="store_true", help="Shows available charset lists")
79
+
80
+ sc("length", type=positive_int, help="Length of the key", nargs="?")
81
+ sc("--seed", type=str, help="Seed for generation")
82
+ sc("--prefix", type=str, default="", help="Prefix the generated keys")
83
+ sc("--suffix", type=str, default="", help="Suffix the generated keys")
84
+ sc("--group", type=positive_int, help="Add separator each X chars (use --sep [str] to set separator)")
85
+ sc("--sep", type=str, default="-", help="Grouping separator string")
86
+
87
+ sc("-n", "--count", type=positive_int, default=1, help="Number of keys to generate")
88
+ sc("-o", "--output", type=str, metavar="FILE", help="Write output to file")
89
+
90
+ sc("--unsafe", action="store_true", help="Use PRNG instead of CSPRNG for generation")
91
+ sc("-q", "--quiet", action="store_true", help="Suppress non-essential output")
92
+
93
+
94
+ sc("--charset", default="@letters", help="Character set to use (see --charset-list)")
95
+
96
+ self.args = self.parser.parse_args()
97
+
98
+ if self.args.length is None and not self.args.charset_list:
99
+ self.parser.error("length is required")
100
+
101
+ # RANDOM MODULE DISALLOWED + SEED SET
102
+ if self.args.seed is not None and not self.args.unsafe:
103
+ self.parser.error("--seed requires --unsafe (seeding is insecure)")
104
+
105
+ if self.args.charset_list:
106
+ self.parser.exit(0, ("\nAvailable charsets to use:\n @"+("\n @".join(CHARSETS.keys())) + f"\n\n Use as:\n zora --charset @digits = For digits only charset\n zora --charset @letters@digits = For alphanumeric charset\n zora --charset @hex\"XYZ\" = For hexadecimal charset extended with letters X, Y and Z\n"))
107
+
108
+ if self.args.unsafe and not self.args.quiet:
109
+ print(f"{Fore.RED}Program will output cryptographically insecure keys.\n")
110
+
111
+ def generate_keys(self):
112
+ """
113
+ Generates all the keys
114
+ """
115
+ def gen():
116
+ if self.args.unsafe:
117
+ return "".join(random.choice(self.charset) for _ in range(self.args.length))
118
+ else:
119
+ return "".join(secrets.choice(self.charset) for _ in range(self.args.length))
120
+
121
+ if self.args.seed is not None:
122
+ random.seed(self.args.seed)
123
+
124
+ for _ in range(self.args.count):
125
+ key = gen()
126
+ if self.args.group is not None:
127
+ key = self.args.sep.join(
128
+ key[i:i + self.args.group]
129
+ for i in range(0, len(key), self.args.group)
130
+ )
131
+ self.lines.append(
132
+ f"{self.args.prefix}{key}{self.args.suffix}"
133
+ )
134
+
135
+ def output(self):
136
+ """
137
+ Prints out / Saves the keys (lines)
138
+ """
139
+ if self.args.output:
140
+ with open(self.args.output, "w") as f:
141
+ f.write("\n".join(self.lines))
142
+
143
+ else:
144
+ print("\n".join(_ for _ in self.lines))
145
+
146
+ def start_timer(self):
147
+ """
148
+ Starts the timer
149
+ """
150
+ self.time_start = time.perf_counter()
151
+
152
+
153
+ def show_timer(self, ms_threshold: int = 1000):
154
+ """
155
+ Stops the timer and prints elapsed time
156
+ """
157
+ if self.args.quiet:
158
+ return
159
+
160
+ if not hasattr(self, "time_start"):
161
+ print("Timer has not been started properly.")
162
+ return
163
+
164
+ elapsed = time.perf_counter() - self.time_start
165
+
166
+ total_ms = int(elapsed * 1000)
167
+ total_seconds = total_ms // 1000
168
+
169
+ if total_ms < ms_threshold:
170
+ print(f"\n{Fore.GREEN}Timer: {total_ms}ms elapsed")
171
+ else:
172
+ hours = total_seconds // 3600
173
+ minutes = (total_seconds % 3600) // 60
174
+ seconds = total_seconds % 60
175
+
176
+ parts = []
177
+
178
+ if hours > 0:
179
+ parts.append(f"{hours}h")
180
+
181
+ if minutes > 0:
182
+ parts.append(f"{minutes}m")
183
+
184
+ if seconds > 0 or not parts:
185
+ parts.append(f"{seconds}s")
186
+
187
+ print(f"\n{Fore.GREEN}Timer: {' '.join(parts)} elapsed")
188
+
189
+ def show_entropy(self):
190
+ """
191
+ Shows final charset, entropy, strength and used generator."""
192
+ # MARK PRNG's STRIKETHROUGH
193
+ STRIKETHROUGH = "\033[9m" if self.args.unsafe else ""
194
+ if not self.args.quiet:
195
+ entropy = self.entropy()
196
+ print(f"{Fore.LIGHTBLUE_EX}Charset: {len(self.charset)}")
197
+ print(f"{STRIKETHROUGH}{Fore.CYAN}Entropy: {entropy:.2f} bits")
198
+ print(f"{STRIKETHROUGH}{Fore.CYAN}Strength: {self.strength(entropy)}")
199
+ print(f"{Fore.LIGHTCYAN_EX}Generator: {Fore.RED + 'PRNG' if self.args.unsafe else Fore.GREEN + 'CSPRNG'}")
200
+
201
+ def entropy(self):
202
+ return self.args.length * math.log2(len(self.charset))
203
+
204
+ def strength(self, entropy):
205
+ """
206
+ Finds out the strength according to the entropy
207
+ """
208
+ if entropy < 40:
209
+ return Fore.RED + "Very weak"
210
+ elif entropy < 60:
211
+ return Fore.LIGHTRED_EX + "Weak"
212
+ elif entropy < 80:
213
+ return Fore.YELLOW + "Moderate"
214
+ elif entropy < 100:
215
+ return Fore.LIGHTGREEN_EX + "Strong"
216
+ else:
217
+ return Fore.GREEN + "Very strong"
218
+
219
+
220
+ def main():
221
+ zora = Zora()
222
+ zora.run()
223
+
224
+
225
+ if __name__ == "__main__":
226
+ main()