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.
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
- for length in range(min_len, max_len + 1):
225
- for combo in itertools.product(charset, repeat=length):
226
- attempts += 1
227
- if progress_callback:
228
- progress_callback(1)
229
- candidate = "".join(combo)
230
- if algorithm == "bcrypt":
231
- if bcrypt is None:
232
- raise ValueError("bcrypt library is required for bcrypt cracking")
233
- if bcrypt.checkpw(candidate.encode("utf-8"), target_hash.encode("utf-8")):
234
- elapsed = time.perf_counter() - start
235
- rate = counter.rate(attempts)
236
- return CrackResult(True, candidate, attempts, elapsed, rate)
237
- elif algorithm == "argon2":
238
- hasher, verify_mismatch, invalid_hash = argon2_verify
239
- try:
240
- hasher.verify(target_hash, candidate)
241
- elapsed = time.perf_counter() - start
242
- rate = counter.rate(attempts)
243
- return CrackResult(True, candidate, attempts, elapsed, rate)
244
- except (verify_mismatch, invalid_hash):
245
- pass
246
- elif algorithm == "scrypt":
247
- n, r, p, salt_bytes, digest = scrypt_params
248
- value = hashlib.scrypt(candidate.encode("utf-8"), salt=salt_bytes, n=n, r=r, p=p, dklen=len(digest))
249
- if value == digest:
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, candidate, attempts, elapsed, rate)
253
- elif algorithm in {"mssql2005", "mssql2012"}:
254
- digest = hashlib.sha1(mssql_salt + candidate.encode("utf-16le")).hexdigest().upper()
255
- if target_hash.lower().startswith("0x0100") and target_hash[14:].upper() == digest:
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
- elif hash_text(candidate, algorithm, salt, salt_mode) == target_hash:
260
- elapsed = time.perf_counter() - start
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 dictionary attack (0=auto)")
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:
@@ -0,0 +1 @@
1
+ Not Found
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hashsmith-cli
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Hashsmith CLI for encoding, decoding, hashing, and cracking
5
5
  Home-page: https://github.com/salihsefer36/Hashsmith
6
6
  Author: Salih Sefer
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hashsmith-cli",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Modular toolkit for encoding, hashing, and cracking.",
5
5
  "main": "bin/index.js",
6
6
  "bin": {
package/setup.cfg CHANGED
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = hashsmith-cli
3
- version = 0.1.0
3
+ version = 0.1.1
4
4
  description = Hashsmith CLI for encoding, decoding, hashing, and cracking
5
5
  long_description = file: README.md
6
6
  long_description_content_type = text/markdown
Binary file