hashsmith-cli 1.0.0
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/LICENSE +21 -0
- package/MANIFEST.in +2 -0
- package/README.md +256 -0
- package/bin/index.js +10 -0
- package/dist/hashsmith_cli-0.1.0-py3-none-any.whl +0 -0
- package/dist/hashsmith_cli-0.1.0.tar.gz +0 -0
- package/hashsmith/__init__.py +3 -0
- package/hashsmith/__main__.py +4 -0
- package/hashsmith/algorithms/__init__.py +1 -0
- package/hashsmith/algorithms/cracking.py +276 -0
- package/hashsmith/algorithms/decoding.py +317 -0
- package/hashsmith/algorithms/encoding.py +203 -0
- package/hashsmith/algorithms/hashing.py +223 -0
- package/hashsmith/algorithms/morse.py +64 -0
- package/hashsmith/cli.py +1014 -0
- package/hashsmith/utils/__init__.py +1 -0
- package/hashsmith/utils/banner.py +20 -0
- package/hashsmith/utils/clipboard.py +37 -0
- package/hashsmith/utils/hashdetect.py +33 -0
- package/hashsmith/utils/identify.py +629 -0
- package/hashsmith/utils/io.py +30 -0
- package/hashsmith/utils/metrics.py +20 -0
- package/hashsmith/utils/wordlist.py +11 -0
- package/hashsmith_cli.egg-info/PKG-INFO +272 -0
- package/hashsmith_cli.egg-info/SOURCES.txt +29 -0
- package/hashsmith_cli.egg-info/dependency_links.txt +1 -0
- package/hashsmith_cli.egg-info/entry_points.txt +2 -0
- package/hashsmith_cli.egg-info/requires.txt +4 -0
- package/hashsmith_cli.egg-info/top_level.txt +1 -0
- package/package.json +15 -0
- package/pyproject.toml +3 -0
- package/requirements.txt +4 -0
- package/setup.cfg +23 -0
- package/setup.py +5 -0
- package/wordlists/common.txt +230931 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 s4l1hs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/MANIFEST.in
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
```
|
|
2
|
+
_ _ _ ____ _ _ _
|
|
3
|
+
| | | | __ _ ___| |__ / ___| _ __ ___ (_) |_| |__
|
|
4
|
+
| |_| |/ _` / __| '_ \\___ \| '_ ` _ \| | __| '_ \
|
|
5
|
+
| _ | (_| \__ \ | | |___) | | | | | | | |_| | | |
|
|
6
|
+
|_| |_|\__,_|___/_| |_|____/|_| |_| |_|_|\__|_| |_|
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
# Hashsmith
|
|
10
|
+
|
|
11
|
+
Hashsmith is a modular, terminal-first toolkit for encoding, decoding, hashing, cracking, and identification. It’s designed for security-focused workflows, quick experiments, and automation in scripts or pipelines.
|
|
12
|
+
|
|
13
|
+
## Highlights ⚡
|
|
14
|
+
- Clean CLI with guided interactive mode
|
|
15
|
+
- Extensive encoding/decoding support (base* formats, morse, url, classical ciphers, and more)
|
|
16
|
+
- Modern hash support (MD5/SHA/NTLM/Bcrypt/Argon2/Scrypt, etc.)
|
|
17
|
+
- Identify mode for best-guess detection of encoding and hash types
|
|
18
|
+
- File input/output and clipboard copy support
|
|
19
|
+
- Themed UI with Rich
|
|
20
|
+
|
|
21
|
+
## Installation 🔐
|
|
22
|
+
|
|
23
|
+
**From source**
|
|
24
|
+
```bash
|
|
25
|
+
pip install -r requirements.txt
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Run as module**
|
|
29
|
+
```bash
|
|
30
|
+
python -m hashsmith --help
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quick Start ⚡
|
|
34
|
+
```bash
|
|
35
|
+
hashsmith encode -t base64 -i "hello"
|
|
36
|
+
hashsmith decode -t base64 -i "aGVsbG8="
|
|
37
|
+
hashsmith hash -t sha256 -i "secret" -c
|
|
38
|
+
hashsmith identify -i "aGVsbG8="
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Global Options 🛡️
|
|
42
|
+
- `-N`, `--no-banner`: Disable banner
|
|
43
|
+
- `-T`, `--theme`: Accent color (cyan, green, magenta, blue, yellow, red, white)
|
|
44
|
+
- `-A`, `--help-all`: Show help for all commands
|
|
45
|
+
- `-id`, `--identify`: Shortcut for identify (use with `-i/-f`)
|
|
46
|
+
|
|
47
|
+
## Common Input/Output Options 🧬
|
|
48
|
+
These options are shared across commands that accept input and output:
|
|
49
|
+
- `-i`, `--text`: Text input
|
|
50
|
+
- `-f`, `--file`: Read input from file
|
|
51
|
+
- `-o`, `--out`: Write output to file
|
|
52
|
+
- `-c`, `--copy`: Copy output to clipboard
|
|
53
|
+
|
|
54
|
+
## Commands 🛡️
|
|
55
|
+
|
|
56
|
+
### 1) Encode
|
|
57
|
+
Encode text with a selected algorithm.
|
|
58
|
+
|
|
59
|
+
**Usage**
|
|
60
|
+
```bash
|
|
61
|
+
hashsmith encode -t <type> [-i <text> | -f <file>] [-o <file>] [-c]
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Examples**
|
|
65
|
+
```bash
|
|
66
|
+
hashsmith encode -t base64 -i "hello"
|
|
67
|
+
hashsmith encode -t caesar -s 5 -f input.txt -o output.txt
|
|
68
|
+
hashsmith encode -t hex -i "hello" -c
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
### 2) Decode
|
|
74
|
+
Decode text with a selected algorithm.
|
|
75
|
+
|
|
76
|
+
**Usage**
|
|
77
|
+
```bash
|
|
78
|
+
hashsmith decode -t <type> [-i <text> | -f <file>] [-o <file>] [-c]
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Examples**
|
|
82
|
+
```bash
|
|
83
|
+
hashsmith decode -t base64 -i "aGVsbG8="
|
|
84
|
+
hashsmith decode -t morse -i ".... . .-.. .-.. ---"
|
|
85
|
+
hashsmith decode -t hex -i "68656c6c6f" -c
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
### 3) Hash
|
|
91
|
+
Hash text using a selected algorithm.
|
|
92
|
+
|
|
93
|
+
**Usage**
|
|
94
|
+
```bash
|
|
95
|
+
hashsmith hash -t <type> [-i <text> | -f <file>] [--salt <s>] [--salt-mode prefix|suffix] [-o <file>] [-c]
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Examples**
|
|
99
|
+
```bash
|
|
100
|
+
hashsmith hash -t sha256 -i "hello"
|
|
101
|
+
hashsmith hash -t md5 -i "secret" -s "pepper" -S suffix
|
|
102
|
+
hashsmith hash -t sha256 -i "hello" -c
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
### 4) Crack
|
|
108
|
+
Crack hashes using dictionary or brute-force attacks.
|
|
109
|
+
|
|
110
|
+
**Usage**
|
|
111
|
+
```bash
|
|
112
|
+
hashsmith crack -t <type|auto> -H <hash> -M <dict|brute> [options]
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**Examples**
|
|
116
|
+
```bash
|
|
117
|
+
hashsmith crack -t md5 -H 5f4dcc3b5aa765d61d8327deb882cf99 -M dict -w wordlists/common.txt
|
|
118
|
+
hashsmith crack -t sha1 -H 2aae6c35c94fcfb415dbe95f408b9ce91ee846ed -M brute -n 1 -x 4
|
|
119
|
+
hashsmith crack -t md5 -H 5f4dcc3b5aa765d61d8327deb882cf99 -M dict -w wordlists/common.txt -c
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
### 5) Identify
|
|
125
|
+
Detect probable encoding and hash types. Prioritizes reliable results and avoids false positives for raw text.
|
|
126
|
+
|
|
127
|
+
**Usage**
|
|
128
|
+
```bash
|
|
129
|
+
hashsmith identify -i <text>
|
|
130
|
+
hashsmith identify -f <file>
|
|
131
|
+
hashsmith -id -i <text>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**Examples**
|
|
135
|
+
```bash
|
|
136
|
+
hashsmith identify -i "aGVsbG8="
|
|
137
|
+
hashsmith identify -i 5f4dcc3b5aa765d61d8327deb882cf99
|
|
138
|
+
hashsmith -id -i "aGVsbG8="
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
### 6) Interactive Mode
|
|
144
|
+
Guided prompt flow for encoding/decoding/hashing/cracking/identify.
|
|
145
|
+
|
|
146
|
+
**Usage**
|
|
147
|
+
```bash
|
|
148
|
+
hashsmith
|
|
149
|
+
hashsmith interactive
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Algorithms 🔐
|
|
153
|
+
|
|
154
|
+
### Hashing Algorithms
|
|
155
|
+
| Category | Algorithms |
|
|
156
|
+
| --- | --- |
|
|
157
|
+
| Cryptographic | md5, md4, sha1, sha224, sha256, sha384, sha512, sha3_224, sha3_256, sha3_512 |
|
|
158
|
+
| Modern/Alt | blake2b, blake2s, ntlm, mysql323, mysql41 |
|
|
159
|
+
| Password | bcrypt, argon2, scrypt, mssql2000, mssql2005, mssql2012, postgres |
|
|
160
|
+
|
|
161
|
+
### Encoding/Decoding Algorithms
|
|
162
|
+
| Category | Algorithms |
|
|
163
|
+
| --- | --- |
|
|
164
|
+
| Base Encodings | base64, base64url, base32, base85, base58 |
|
|
165
|
+
| Numeric | hex, binary, decimal, octal |
|
|
166
|
+
| Text/URL | morse, url, unicode |
|
|
167
|
+
| Ciphers | caesar, rot13, vigenere, xor, atbash, baconian, leet, reverse, railfence, polybius |
|
|
168
|
+
| Esoteric | brainf*ck |
|
|
169
|
+
|
|
170
|
+
### Cracking Modes
|
|
171
|
+
| Mode | Description |
|
|
172
|
+
| --- | --- |
|
|
173
|
+
| dict | Dictionary attack using a wordlist |
|
|
174
|
+
| brute | Brute-force with a chosen charset and length range |
|
|
175
|
+
|
|
176
|
+
## Clipboard Support 🔐
|
|
177
|
+
When `-c/--copy` is set, output is copied to the clipboard using platform-native tools:
|
|
178
|
+
- macOS: `pbcopy`
|
|
179
|
+
- Windows: `clip`
|
|
180
|
+
- Linux: `xclip`, `xsel`, or `wl-copy`
|
|
181
|
+
|
|
182
|
+
## Themes 🛡️
|
|
183
|
+
Set the accent color globally:
|
|
184
|
+
```bash
|
|
185
|
+
hashsmith -T magenta
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Troubleshooting 🧬
|
|
189
|
+
- If hashing output in `base58` fails, ensure the hash is hex-based.
|
|
190
|
+
- For dictionary cracking, validate your wordlist path.
|
|
191
|
+
|
|
192
|
+
## Security Notice 🛡️
|
|
193
|
+
Hashsmith is intended for educational and authorized security testing only. You are responsible for compliance with applicable laws.
|
|
194
|
+
|
|
195
|
+
## License
|
|
196
|
+
See [LICENSE](LICENSE).
|
|
197
|
+
Hashsmith is a modular, terminal-based Swiss Army knife for encoding, decoding, hashing, and password cracking. Built for security enthusiasts 🛠️🔐
|
|
198
|
+
|
|
199
|
+
## Features
|
|
200
|
+
- Encoding/Decoding: Base64, Hex, Binary, Morse, URL, Caesar, ROT13
|
|
201
|
+
- Hashing: MD5, SHA-1, SHA-256, SHA-512
|
|
202
|
+
- Cracking: Dictionary attack and basic brute-force
|
|
203
|
+
- File input/output support
|
|
204
|
+
- Optional salt support for hashing and cracking
|
|
205
|
+
|
|
206
|
+
## Installation
|
|
207
|
+
1. Create a virtual environment (optional)
|
|
208
|
+
2. Install dependencies:
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
pip install -r requirements.txt
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Usage
|
|
215
|
+
Run via module:
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
python -m hashsmith --help
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Encode
|
|
222
|
+
```
|
|
223
|
+
python -m hashsmith encode --type base64 --text "hello"
|
|
224
|
+
python -m hashsmith encode --type caesar --shift 5 --file input.txt --out output.txt
|
|
225
|
+
python -m hashsmith encode --type hex --text "hello" --copy
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Decode
|
|
229
|
+
```
|
|
230
|
+
python -m hashsmith decode --type base64 --text "aGVsbG8="
|
|
231
|
+
python -m hashsmith decode --type morse --text ".... . .-.. .-.. ---"
|
|
232
|
+
python -m hashsmith decode --type hex --text "68656c6c6f" --copy
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Hash
|
|
236
|
+
```
|
|
237
|
+
python -m hashsmith hash --type sha256 --text "hello"
|
|
238
|
+
python -m hashsmith hash --type md5 --text "secret" --salt "pepper" --salt-mode suffix
|
|
239
|
+
python -m hashsmith hash --type sha256 --text "hello" --copy
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Crack
|
|
243
|
+
```
|
|
244
|
+
python -m hashsmith crack --type md5 --hash 5f4dcc3b5aa765d61d8327deb882cf99 --mode dict --wordlist wordlists/common.txt
|
|
245
|
+
python -m hashsmith crack --type sha1 --hash 2aae6c35c94fcfb415dbe95f408b9ce91ee846ed --mode brute --min-len 1 --max-len 4
|
|
246
|
+
python -m hashsmith crack --type md5 --hash 5f4dcc3b5aa765d61d8327deb882cf99 --mode dict --wordlist wordlists/common.txt --copy
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Notes
|
|
250
|
+
- Dictionary cracking uses the provided wordlist file.
|
|
251
|
+
- Brute-force is intentionally small by default; adjust `--min-len` and `--max-len` carefully.
|
|
252
|
+
|
|
253
|
+
## Roadmap
|
|
254
|
+
- Multithreading for cracking
|
|
255
|
+
- Additional encodings and hash types
|
|
256
|
+
- Better progress indicators
|
package/bin/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const { spawn } = require('child_process');
|
|
3
|
+
|
|
4
|
+
// Terminalden gelen argümanları yakala ve python paketine ilet
|
|
5
|
+
const args = process.argv.slice(2);
|
|
6
|
+
const child = spawn('hashsmith', args, { stdio: 'inherit', shell: true });
|
|
7
|
+
|
|
8
|
+
child.on('exit', (code) => {
|
|
9
|
+
process.exit(code);
|
|
10
|
+
});
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Algorithm implementations for Hashsmith."""
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import itertools
|
|
3
|
+
import string
|
|
4
|
+
import time
|
|
5
|
+
from concurrent.futures import ProcessPoolExecutor, as_completed
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Iterable, Optional, Tuple, List, Callable
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
import bcrypt # type: ignore
|
|
11
|
+
except Exception: # pragma: no cover
|
|
12
|
+
bcrypt = None
|
|
13
|
+
|
|
14
|
+
from .hashing import hash_text
|
|
15
|
+
from ..utils.metrics import RateCounter
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class CrackResult:
|
|
20
|
+
found: bool
|
|
21
|
+
password: Optional[str]
|
|
22
|
+
attempts: int
|
|
23
|
+
elapsed: float
|
|
24
|
+
rate: float
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _parse_scrypt_hash(target_hash: str) -> Tuple[int, int, int, bytes, bytes]:
|
|
28
|
+
parts = target_hash.split("$")
|
|
29
|
+
if len(parts) != 6 or parts[0] != "scrypt":
|
|
30
|
+
raise ValueError("Invalid scrypt hash format")
|
|
31
|
+
n = int(parts[1])
|
|
32
|
+
r = int(parts[2])
|
|
33
|
+
p = int(parts[3])
|
|
34
|
+
salt = bytes.fromhex(parts[4])
|
|
35
|
+
digest = bytes.fromhex(parts[5])
|
|
36
|
+
return n, r, p, salt, digest
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _parse_mssql_2005_salt(target_hash: str) -> bytes:
|
|
40
|
+
value = target_hash.strip()
|
|
41
|
+
if not value.lower().startswith("0x0100") or len(value) < 6 + 8:
|
|
42
|
+
raise ValueError("Invalid MSSQL 2005/2012 hash format")
|
|
43
|
+
return bytes.fromhex(value[6:14])
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _dictionary_worker(words: List[str], target_hash: str, algorithm: str, salt: str, salt_mode: str) -> Tuple[Optional[str], int]:
|
|
47
|
+
attempts = 0
|
|
48
|
+
scrypt_params = None
|
|
49
|
+
if algorithm == "scrypt":
|
|
50
|
+
scrypt_params = _parse_scrypt_hash(target_hash)
|
|
51
|
+
mssql_salt = None
|
|
52
|
+
if algorithm in {"mssql2005", "mssql2012"}:
|
|
53
|
+
mssql_salt = _parse_mssql_2005_salt(target_hash)
|
|
54
|
+
hasher = None
|
|
55
|
+
verify_mismatch = None
|
|
56
|
+
invalid_hash = None
|
|
57
|
+
if algorithm == "argon2":
|
|
58
|
+
try:
|
|
59
|
+
from argon2 import PasswordHasher # type: ignore
|
|
60
|
+
from argon2.exceptions import VerifyMismatchError, InvalidHash # type: ignore
|
|
61
|
+
except Exception: # pragma: no cover
|
|
62
|
+
return None, attempts
|
|
63
|
+
hasher = PasswordHasher()
|
|
64
|
+
verify_mismatch = VerifyMismatchError
|
|
65
|
+
invalid_hash = InvalidHash
|
|
66
|
+
for word in words:
|
|
67
|
+
attempts += 1
|
|
68
|
+
if algorithm == "bcrypt":
|
|
69
|
+
if bcrypt is None:
|
|
70
|
+
continue
|
|
71
|
+
if bcrypt.checkpw(word.encode("utf-8"), target_hash.encode("utf-8")):
|
|
72
|
+
return word, attempts
|
|
73
|
+
elif algorithm == "argon2":
|
|
74
|
+
try:
|
|
75
|
+
hasher.verify(target_hash, word)
|
|
76
|
+
return word, attempts
|
|
77
|
+
except (verify_mismatch, invalid_hash):
|
|
78
|
+
continue
|
|
79
|
+
except Exception:
|
|
80
|
+
continue
|
|
81
|
+
elif algorithm == "scrypt":
|
|
82
|
+
n, r, p, salt_bytes, digest = scrypt_params
|
|
83
|
+
candidate = hashlib.scrypt(word.encode("utf-8"), salt=salt_bytes, n=n, r=r, p=p, dklen=len(digest))
|
|
84
|
+
if candidate == digest:
|
|
85
|
+
return word, attempts
|
|
86
|
+
elif algorithm in {"mssql2005", "mssql2012"}:
|
|
87
|
+
digest = hashlib.sha1(mssql_salt + word.encode("utf-16le")).hexdigest().upper()
|
|
88
|
+
if target_hash.lower().startswith("0x0100") and target_hash[14:].upper() == digest:
|
|
89
|
+
return word, attempts
|
|
90
|
+
else:
|
|
91
|
+
if hash_text(word, algorithm, salt, salt_mode) == target_hash:
|
|
92
|
+
return word, attempts
|
|
93
|
+
return None, attempts
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def dictionary_attack(
|
|
97
|
+
target_hash: str,
|
|
98
|
+
algorithm: str,
|
|
99
|
+
words: Iterable[str],
|
|
100
|
+
salt: str = "",
|
|
101
|
+
salt_mode: str = "prefix",
|
|
102
|
+
workers: int = 1,
|
|
103
|
+
progress_callback: Optional[Callable[[int], None]] = None,
|
|
104
|
+
) -> CrackResult:
|
|
105
|
+
start = time.perf_counter()
|
|
106
|
+
counter = RateCounter()
|
|
107
|
+
attempts = 0
|
|
108
|
+
|
|
109
|
+
if algorithm == "bcrypt" and bcrypt is None:
|
|
110
|
+
raise ValueError("bcrypt library is required for bcrypt cracking")
|
|
111
|
+
if algorithm == "argon2":
|
|
112
|
+
try:
|
|
113
|
+
from argon2 import PasswordHasher # type: ignore
|
|
114
|
+
from argon2.exceptions import VerifyMismatchError, InvalidHash # type: ignore
|
|
115
|
+
except Exception as exc: # pragma: no cover
|
|
116
|
+
raise ValueError("argon2-cffi library is required for argon2 cracking") from exc
|
|
117
|
+
argon2_verify = (PasswordHasher(), VerifyMismatchError, InvalidHash)
|
|
118
|
+
else:
|
|
119
|
+
argon2_verify = None
|
|
120
|
+
|
|
121
|
+
if workers > 1:
|
|
122
|
+
batch = []
|
|
123
|
+
futures = []
|
|
124
|
+
with ProcessPoolExecutor(max_workers=workers) as executor:
|
|
125
|
+
for word in words:
|
|
126
|
+
batch.append(word)
|
|
127
|
+
if len(batch) >= 500:
|
|
128
|
+
futures.append(executor.submit(_dictionary_worker, batch, target_hash, algorithm, salt, salt_mode))
|
|
129
|
+
batch = []
|
|
130
|
+
if batch:
|
|
131
|
+
futures.append(executor.submit(_dictionary_worker, batch, target_hash, algorithm, salt, salt_mode))
|
|
132
|
+
|
|
133
|
+
for future in as_completed(futures):
|
|
134
|
+
found, count = future.result()
|
|
135
|
+
attempts += count
|
|
136
|
+
if progress_callback:
|
|
137
|
+
progress_callback(count)
|
|
138
|
+
if found:
|
|
139
|
+
elapsed = time.perf_counter() - start
|
|
140
|
+
rate = counter.rate(attempts)
|
|
141
|
+
return CrackResult(True, found, attempts, elapsed, rate)
|
|
142
|
+
else:
|
|
143
|
+
scrypt_params = None
|
|
144
|
+
if algorithm == "scrypt":
|
|
145
|
+
scrypt_params = _parse_scrypt_hash(target_hash)
|
|
146
|
+
mssql_salt = None
|
|
147
|
+
if algorithm in {"mssql2005", "mssql2012"}:
|
|
148
|
+
mssql_salt = _parse_mssql_2005_salt(target_hash)
|
|
149
|
+
for word in words:
|
|
150
|
+
attempts += 1
|
|
151
|
+
if progress_callback:
|
|
152
|
+
progress_callback(1)
|
|
153
|
+
if algorithm == "bcrypt":
|
|
154
|
+
if bcrypt.checkpw(word.encode("utf-8"), target_hash.encode("utf-8")):
|
|
155
|
+
elapsed = time.perf_counter() - start
|
|
156
|
+
rate = counter.rate(attempts)
|
|
157
|
+
return CrackResult(True, word, attempts, elapsed, rate)
|
|
158
|
+
elif algorithm == "argon2":
|
|
159
|
+
hasher, verify_mismatch, invalid_hash = argon2_verify
|
|
160
|
+
try:
|
|
161
|
+
hasher.verify(target_hash, word)
|
|
162
|
+
elapsed = time.perf_counter() - start
|
|
163
|
+
rate = counter.rate(attempts)
|
|
164
|
+
return CrackResult(True, word, attempts, elapsed, rate)
|
|
165
|
+
except (verify_mismatch, invalid_hash):
|
|
166
|
+
pass
|
|
167
|
+
elif algorithm == "scrypt":
|
|
168
|
+
n, r, p, salt_bytes, digest = scrypt_params
|
|
169
|
+
candidate = hashlib.scrypt(word.encode("utf-8"), salt=salt_bytes, n=n, r=r, p=p, dklen=len(digest))
|
|
170
|
+
if candidate == digest:
|
|
171
|
+
elapsed = time.perf_counter() - start
|
|
172
|
+
rate = counter.rate(attempts)
|
|
173
|
+
return CrackResult(True, word, attempts, elapsed, rate)
|
|
174
|
+
elif algorithm in {"mssql2005", "mssql2012"}:
|
|
175
|
+
digest = hashlib.sha1(mssql_salt + word.encode("utf-16le")).hexdigest().upper()
|
|
176
|
+
if target_hash.lower().startswith("0x0100") and target_hash[14:].upper() == digest:
|
|
177
|
+
elapsed = time.perf_counter() - start
|
|
178
|
+
rate = counter.rate(attempts)
|
|
179
|
+
return CrackResult(True, word, attempts, elapsed, rate)
|
|
180
|
+
else:
|
|
181
|
+
if hash_text(word, algorithm, salt, salt_mode) == target_hash:
|
|
182
|
+
elapsed = time.perf_counter() - start
|
|
183
|
+
rate = counter.rate(attempts)
|
|
184
|
+
return CrackResult(True, word, attempts, elapsed, rate)
|
|
185
|
+
if attempts % 1000 == 0:
|
|
186
|
+
counter.rate(attempts)
|
|
187
|
+
|
|
188
|
+
elapsed = time.perf_counter() - start
|
|
189
|
+
rate = counter.rate(attempts)
|
|
190
|
+
return CrackResult(False, None, attempts, elapsed, rate)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def brute_force(
|
|
194
|
+
target_hash: str,
|
|
195
|
+
algorithm: str,
|
|
196
|
+
charset: str = string.ascii_lowercase + string.digits,
|
|
197
|
+
min_len: int = 1,
|
|
198
|
+
max_len: int = 4,
|
|
199
|
+
salt: str = "",
|
|
200
|
+
salt_mode: str = "prefix",
|
|
201
|
+
progress_callback: Optional[Callable[[int], None]] = None,
|
|
202
|
+
) -> CrackResult:
|
|
203
|
+
start = time.perf_counter()
|
|
204
|
+
counter = RateCounter()
|
|
205
|
+
attempts = 0
|
|
206
|
+
if algorithm == "argon2":
|
|
207
|
+
try:
|
|
208
|
+
from argon2 import PasswordHasher # type: ignore
|
|
209
|
+
from argon2.exceptions import VerifyMismatchError, InvalidHash # type: ignore
|
|
210
|
+
except Exception as exc: # pragma: no cover
|
|
211
|
+
raise ValueError("argon2-cffi library is required for argon2 cracking") from exc
|
|
212
|
+
argon2_verify = (PasswordHasher(), VerifyMismatchError, InvalidHash)
|
|
213
|
+
else:
|
|
214
|
+
argon2_verify = None
|
|
215
|
+
|
|
216
|
+
scrypt_params = None
|
|
217
|
+
if algorithm == "scrypt":
|
|
218
|
+
scrypt_params = _parse_scrypt_hash(target_hash)
|
|
219
|
+
|
|
220
|
+
mssql_salt = None
|
|
221
|
+
if algorithm in {"mssql2005", "mssql2012"}:
|
|
222
|
+
mssql_salt = _parse_mssql_2005_salt(target_hash)
|
|
223
|
+
|
|
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:
|
|
250
|
+
elapsed = time.perf_counter() - start
|
|
251
|
+
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:
|
|
256
|
+
elapsed = time.perf_counter() - start
|
|
257
|
+
rate = counter.rate(attempts)
|
|
258
|
+
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)
|
|
265
|
+
|
|
266
|
+
elapsed = time.perf_counter() - start
|
|
267
|
+
rate = counter.rate(attempts)
|
|
268
|
+
return CrackResult(False, None, attempts, elapsed, rate)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def format_rate(rate: float) -> str:
|
|
272
|
+
if rate >= 1_000_000:
|
|
273
|
+
return f"{rate / 1_000_000:.2f} M/s"
|
|
274
|
+
if rate >= 1_000:
|
|
275
|
+
return f"{rate / 1_000:.2f} K/s"
|
|
276
|
+
return f"{rate:.2f} /s"
|