hashsmith-cli 1.0.0 → 1.0.1
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.
- package/dist/hashsmith_cli-0.1.1-py3-none-any.whl +0 -0
- package/dist/hashsmith_cli-0.1.1.tar.gz +0 -0
- package/hashsmith/algorithms/cracking.py +181 -36
- package/hashsmith/cli.py +6 -1
- package/hashsmith.tar.gz +1 -0
- package/hashsmith_cli.egg-info/PKG-INFO +1 -1
- package/package.json +1 -1
- package/setup.cfg +1 -1
- package/dist/hashsmith_cli-0.1.0-py3-none-any.whl +0 -0
- package/dist/hashsmith_cli-0.1.0.tar.gz +0 -0
|
Binary file
|
|
Binary file
|
|
@@ -93,6 +93,100 @@ def _dictionary_worker(words: List[str], target_hash: str, algorithm: str, salt:
|
|
|
93
93
|
return None, attempts
|
|
94
94
|
|
|
95
95
|
|
|
96
|
+
def _bruteforce_worker(
|
|
97
|
+
prefixes: List[str],
|
|
98
|
+
target_hash: str,
|
|
99
|
+
algorithm: str,
|
|
100
|
+
charset: str,
|
|
101
|
+
length: int,
|
|
102
|
+
salt: str,
|
|
103
|
+
salt_mode: str,
|
|
104
|
+
) -> Tuple[Optional[str], int]:
|
|
105
|
+
attempts = 0
|
|
106
|
+
scrypt_params = None
|
|
107
|
+
if algorithm == "scrypt":
|
|
108
|
+
scrypt_params = _parse_scrypt_hash(target_hash)
|
|
109
|
+
mssql_salt = None
|
|
110
|
+
if algorithm in {"mssql2005", "mssql2012"}:
|
|
111
|
+
mssql_salt = _parse_mssql_2005_salt(target_hash)
|
|
112
|
+
hasher = None
|
|
113
|
+
verify_mismatch = None
|
|
114
|
+
invalid_hash = None
|
|
115
|
+
if algorithm == "argon2":
|
|
116
|
+
try:
|
|
117
|
+
from argon2 import PasswordHasher # type: ignore
|
|
118
|
+
from argon2.exceptions import VerifyMismatchError, InvalidHash # type: ignore
|
|
119
|
+
except Exception: # pragma: no cover
|
|
120
|
+
return None, attempts
|
|
121
|
+
hasher = PasswordHasher()
|
|
122
|
+
verify_mismatch = VerifyMismatchError
|
|
123
|
+
invalid_hash = InvalidHash
|
|
124
|
+
|
|
125
|
+
for prefix in prefixes:
|
|
126
|
+
remaining = length - len(prefix)
|
|
127
|
+
if remaining < 0:
|
|
128
|
+
continue
|
|
129
|
+
if remaining == 0:
|
|
130
|
+
attempts += 1
|
|
131
|
+
candidate = prefix
|
|
132
|
+
if algorithm == "bcrypt":
|
|
133
|
+
if bcrypt is None:
|
|
134
|
+
continue
|
|
135
|
+
if bcrypt.checkpw(candidate.encode("utf-8"), target_hash.encode("utf-8")):
|
|
136
|
+
return candidate, attempts
|
|
137
|
+
elif algorithm == "argon2":
|
|
138
|
+
try:
|
|
139
|
+
hasher.verify(target_hash, candidate)
|
|
140
|
+
return candidate, attempts
|
|
141
|
+
except (verify_mismatch, invalid_hash):
|
|
142
|
+
continue
|
|
143
|
+
except Exception:
|
|
144
|
+
continue
|
|
145
|
+
elif algorithm == "scrypt":
|
|
146
|
+
n, r, p, salt_bytes, digest = scrypt_params
|
|
147
|
+
value = hashlib.scrypt(candidate.encode("utf-8"), salt=salt_bytes, n=n, r=r, p=p, dklen=len(digest))
|
|
148
|
+
if value == digest:
|
|
149
|
+
return candidate, attempts
|
|
150
|
+
elif algorithm in {"mssql2005", "mssql2012"}:
|
|
151
|
+
digest = hashlib.sha1(mssql_salt + candidate.encode("utf-16le")).hexdigest().upper()
|
|
152
|
+
if target_hash.lower().startswith("0x0100") and target_hash[14:].upper() == digest:
|
|
153
|
+
return candidate, attempts
|
|
154
|
+
else:
|
|
155
|
+
if hash_text(candidate, algorithm, salt, salt_mode) == target_hash:
|
|
156
|
+
return candidate, attempts
|
|
157
|
+
continue
|
|
158
|
+
|
|
159
|
+
for combo in itertools.product(charset, repeat=remaining):
|
|
160
|
+
attempts += 1
|
|
161
|
+
candidate = prefix + "".join(combo)
|
|
162
|
+
if algorithm == "bcrypt":
|
|
163
|
+
if bcrypt is None:
|
|
164
|
+
continue
|
|
165
|
+
if bcrypt.checkpw(candidate.encode("utf-8"), target_hash.encode("utf-8")):
|
|
166
|
+
return candidate, attempts
|
|
167
|
+
elif algorithm == "argon2":
|
|
168
|
+
try:
|
|
169
|
+
hasher.verify(target_hash, candidate)
|
|
170
|
+
return candidate, attempts
|
|
171
|
+
except (verify_mismatch, invalid_hash):
|
|
172
|
+
continue
|
|
173
|
+
except Exception:
|
|
174
|
+
continue
|
|
175
|
+
elif algorithm == "scrypt":
|
|
176
|
+
n, r, p, salt_bytes, digest = scrypt_params
|
|
177
|
+
value = hashlib.scrypt(candidate.encode("utf-8"), salt=salt_bytes, n=n, r=r, p=p, dklen=len(digest))
|
|
178
|
+
if value == digest:
|
|
179
|
+
return candidate, attempts
|
|
180
|
+
elif algorithm in {"mssql2005", "mssql2012"}:
|
|
181
|
+
digest = hashlib.sha1(mssql_salt + candidate.encode("utf-16le")).hexdigest().upper()
|
|
182
|
+
if target_hash.lower().startswith("0x0100") and target_hash[14:].upper() == digest:
|
|
183
|
+
return candidate, attempts
|
|
184
|
+
else:
|
|
185
|
+
if hash_text(candidate, algorithm, salt, salt_mode) == target_hash:
|
|
186
|
+
return candidate, attempts
|
|
187
|
+
return None, attempts
|
|
188
|
+
|
|
189
|
+
|
|
96
190
|
def dictionary_attack(
|
|
97
191
|
target_hash: str,
|
|
98
192
|
algorithm: str,
|
|
@@ -198,11 +292,14 @@ def brute_force(
|
|
|
198
292
|
max_len: int = 4,
|
|
199
293
|
salt: str = "",
|
|
200
294
|
salt_mode: str = "prefix",
|
|
295
|
+
workers: int = 1,
|
|
201
296
|
progress_callback: Optional[Callable[[int], None]] = None,
|
|
202
297
|
) -> CrackResult:
|
|
203
298
|
start = time.perf_counter()
|
|
204
299
|
counter = RateCounter()
|
|
205
300
|
attempts = 0
|
|
301
|
+
if algorithm == "bcrypt" and bcrypt is None:
|
|
302
|
+
raise ValueError("bcrypt library is required for bcrypt cracking")
|
|
206
303
|
if algorithm == "argon2":
|
|
207
304
|
try:
|
|
208
305
|
from argon2 import PasswordHasher # type: ignore
|
|
@@ -221,47 +318,95 @@ def brute_force(
|
|
|
221
318
|
if algorithm in {"mssql2005", "mssql2012"}:
|
|
222
319
|
mssql_salt = _parse_mssql_2005_salt(target_hash)
|
|
223
320
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
321
|
+
if workers > 1:
|
|
322
|
+
prefixes = list(charset)
|
|
323
|
+
futures = []
|
|
324
|
+
with ProcessPoolExecutor(max_workers=workers) as executor:
|
|
325
|
+
for length in range(min_len, max_len + 1):
|
|
326
|
+
batch: List[str] = []
|
|
327
|
+
for prefix in prefixes:
|
|
328
|
+
batch.append(prefix)
|
|
329
|
+
if len(batch) >= 50:
|
|
330
|
+
futures.append(
|
|
331
|
+
executor.submit(
|
|
332
|
+
_bruteforce_worker,
|
|
333
|
+
batch,
|
|
334
|
+
target_hash,
|
|
335
|
+
algorithm,
|
|
336
|
+
charset,
|
|
337
|
+
length,
|
|
338
|
+
salt,
|
|
339
|
+
salt_mode,
|
|
340
|
+
)
|
|
341
|
+
)
|
|
342
|
+
batch = []
|
|
343
|
+
if batch:
|
|
344
|
+
futures.append(
|
|
345
|
+
executor.submit(
|
|
346
|
+
_bruteforce_worker,
|
|
347
|
+
batch,
|
|
348
|
+
target_hash,
|
|
349
|
+
algorithm,
|
|
350
|
+
charset,
|
|
351
|
+
length,
|
|
352
|
+
salt,
|
|
353
|
+
salt_mode,
|
|
354
|
+
)
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
for future in as_completed(futures):
|
|
358
|
+
found, count = future.result()
|
|
359
|
+
attempts += count
|
|
360
|
+
if progress_callback:
|
|
361
|
+
progress_callback(count)
|
|
362
|
+
if found:
|
|
250
363
|
elapsed = time.perf_counter() - start
|
|
251
364
|
rate = counter.rate(attempts)
|
|
252
|
-
return CrackResult(True,
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
365
|
+
return CrackResult(True, found, attempts, elapsed, rate)
|
|
366
|
+
if attempts % 1000 == 0:
|
|
367
|
+
counter.rate(attempts)
|
|
368
|
+
else:
|
|
369
|
+
for length in range(min_len, max_len + 1):
|
|
370
|
+
for combo in itertools.product(charset, repeat=length):
|
|
371
|
+
attempts += 1
|
|
372
|
+
if progress_callback:
|
|
373
|
+
progress_callback(1)
|
|
374
|
+
candidate = "".join(combo)
|
|
375
|
+
if algorithm == "bcrypt":
|
|
376
|
+
if bcrypt is None:
|
|
377
|
+
raise ValueError("bcrypt library is required for bcrypt cracking")
|
|
378
|
+
if bcrypt.checkpw(candidate.encode("utf-8"), target_hash.encode("utf-8")):
|
|
379
|
+
elapsed = time.perf_counter() - start
|
|
380
|
+
rate = counter.rate(attempts)
|
|
381
|
+
return CrackResult(True, candidate, attempts, elapsed, rate)
|
|
382
|
+
elif algorithm == "argon2":
|
|
383
|
+
hasher, verify_mismatch, invalid_hash = argon2_verify
|
|
384
|
+
try:
|
|
385
|
+
hasher.verify(target_hash, candidate)
|
|
386
|
+
elapsed = time.perf_counter() - start
|
|
387
|
+
rate = counter.rate(attempts)
|
|
388
|
+
return CrackResult(True, candidate, attempts, elapsed, rate)
|
|
389
|
+
except (verify_mismatch, invalid_hash):
|
|
390
|
+
pass
|
|
391
|
+
elif algorithm == "scrypt":
|
|
392
|
+
n, r, p, salt_bytes, digest = scrypt_params
|
|
393
|
+
value = hashlib.scrypt(candidate.encode("utf-8"), salt=salt_bytes, n=n, r=r, p=p, dklen=len(digest))
|
|
394
|
+
if value == digest:
|
|
395
|
+
elapsed = time.perf_counter() - start
|
|
396
|
+
rate = counter.rate(attempts)
|
|
397
|
+
return CrackResult(True, candidate, attempts, elapsed, rate)
|
|
398
|
+
elif algorithm in {"mssql2005", "mssql2012"}:
|
|
399
|
+
digest = hashlib.sha1(mssql_salt + candidate.encode("utf-16le")).hexdigest().upper()
|
|
400
|
+
if target_hash.lower().startswith("0x0100") and target_hash[14:].upper() == digest:
|
|
401
|
+
elapsed = time.perf_counter() - start
|
|
402
|
+
rate = counter.rate(attempts)
|
|
403
|
+
return CrackResult(True, candidate, attempts, elapsed, rate)
|
|
404
|
+
elif hash_text(candidate, algorithm, salt, salt_mode) == target_hash:
|
|
256
405
|
elapsed = time.perf_counter() - start
|
|
257
406
|
rate = counter.rate(attempts)
|
|
258
407
|
return CrackResult(True, candidate, attempts, elapsed, rate)
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
rate = counter.rate(attempts)
|
|
262
|
-
return CrackResult(True, candidate, attempts, elapsed, rate)
|
|
263
|
-
if attempts % 1000 == 0:
|
|
264
|
-
counter.rate(attempts)
|
|
408
|
+
if attempts % 1000 == 0:
|
|
409
|
+
counter.rate(attempts)
|
|
265
410
|
|
|
266
411
|
elapsed = time.perf_counter() - start
|
|
267
412
|
rate = counter.rate(attempts)
|
package/hashsmith/cli.py
CHANGED
|
@@ -261,7 +261,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
261
261
|
crack_params.add_argument("-x", "--max-len", type=int, default=4)
|
|
262
262
|
crack_params.add_argument("-s", "--salt", default="")
|
|
263
263
|
crack_params.add_argument("-S", "--salt-mode", default="prefix", choices=["prefix", "suffix"])
|
|
264
|
-
crack_params.add_argument("-p", "--workers", type=int, default=0, help="Parallel workers for
|
|
264
|
+
crack_params.add_argument("-p", "--workers", type=int, default=0, help="Parallel workers for cracking (0=auto)")
|
|
265
265
|
|
|
266
266
|
interactive_parser = subparsers.add_parser(
|
|
267
267
|
"interactive",
|
|
@@ -519,6 +519,10 @@ def handle_crack(args: argparse.Namespace, console: Console, accent: str = "cyan
|
|
|
519
519
|
progress.stop()
|
|
520
520
|
raise
|
|
521
521
|
else:
|
|
522
|
+
if args.workers < 1:
|
|
523
|
+
args.workers = os.cpu_count() or 1
|
|
524
|
+
if args.type == "bcrypt" and args.workers > 1:
|
|
525
|
+
console.print("[yellow]bcrypt is CPU-expensive; multi-processing may not scale well.[/yellow]")
|
|
522
526
|
total = 0
|
|
523
527
|
charset_len = len(args.charset)
|
|
524
528
|
for length in range(args.min_len, args.max_len + 1):
|
|
@@ -552,6 +556,7 @@ def handle_crack(args: argparse.Namespace, console: Console, accent: str = "cyan
|
|
|
552
556
|
max_len=args.max_len,
|
|
553
557
|
salt=args.salt,
|
|
554
558
|
salt_mode=args.salt_mode,
|
|
559
|
+
workers=args.workers,
|
|
555
560
|
progress_callback=progress_callback,
|
|
556
561
|
)
|
|
557
562
|
finally:
|
package/hashsmith.tar.gz
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Not Found
|
package/package.json
CHANGED
package/setup.cfg
CHANGED
|
Binary file
|
|
Binary file
|