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
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import os
|
|
3
|
+
from typing import Callable, Dict, Optional, Tuple
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
import bcrypt # type: ignore
|
|
7
|
+
except Exception: # pragma: no cover
|
|
8
|
+
bcrypt = None
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
HASH_FUNCS: Dict[str, Callable[[bytes], "hashlib._Hash"]] = {
|
|
12
|
+
"md5": hashlib.md5,
|
|
13
|
+
"sha1": hashlib.sha1,
|
|
14
|
+
"sha224": hashlib.sha224,
|
|
15
|
+
"sha256": hashlib.sha256,
|
|
16
|
+
"sha384": hashlib.sha384,
|
|
17
|
+
"sha512": hashlib.sha512,
|
|
18
|
+
"sha3_224": hashlib.sha3_224,
|
|
19
|
+
"sha3_256": hashlib.sha3_256,
|
|
20
|
+
"sha3_512": hashlib.sha3_512,
|
|
21
|
+
"blake2b": hashlib.blake2b,
|
|
22
|
+
"blake2s": hashlib.blake2s,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _md4(message: bytes) -> bytes:
|
|
27
|
+
# Minimal MD4 implementation
|
|
28
|
+
def f(x, y, z):
|
|
29
|
+
return (x & y) | (~x & z)
|
|
30
|
+
|
|
31
|
+
def g(x, y, z):
|
|
32
|
+
return (x & y) | (x & z) | (y & z)
|
|
33
|
+
|
|
34
|
+
def h(x, y, z):
|
|
35
|
+
return x ^ y ^ z
|
|
36
|
+
|
|
37
|
+
def left_rotate(x, n):
|
|
38
|
+
x &= 0xFFFFFFFF
|
|
39
|
+
return ((x << n) | (x >> (32 - n))) & 0xFFFFFFFF
|
|
40
|
+
|
|
41
|
+
msg = message
|
|
42
|
+
orig_len_bits = (8 * len(msg)) & 0xFFFFFFFFFFFFFFFF
|
|
43
|
+
msg += b"\x80"
|
|
44
|
+
while (len(msg) % 64) != 56:
|
|
45
|
+
msg += b"\x00"
|
|
46
|
+
msg += orig_len_bits.to_bytes(8, "little")
|
|
47
|
+
|
|
48
|
+
a, b, c, d = 0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476
|
|
49
|
+
|
|
50
|
+
for offset in range(0, len(msg), 64):
|
|
51
|
+
X = [int.from_bytes(msg[offset + i:offset + i + 4], "little") for i in range(0, 64, 4)]
|
|
52
|
+
aa, bb, cc, dd = a, b, c, d
|
|
53
|
+
|
|
54
|
+
# Round 1
|
|
55
|
+
s = [3, 7, 11, 19]
|
|
56
|
+
for i in range(16):
|
|
57
|
+
k = i
|
|
58
|
+
r = i % 4
|
|
59
|
+
a = left_rotate((a + f(b, c, d) + X[k]) & 0xFFFFFFFF, s[r])
|
|
60
|
+
a, b, c, d = d, a, b, c
|
|
61
|
+
|
|
62
|
+
# Round 2
|
|
63
|
+
s = [3, 5, 9, 13]
|
|
64
|
+
for i in range(16):
|
|
65
|
+
k = (i % 4) * 4 + (i // 4)
|
|
66
|
+
r = i % 4
|
|
67
|
+
a = left_rotate((a + g(b, c, d) + X[k] + 0x5A827999) & 0xFFFFFFFF, s[r])
|
|
68
|
+
a, b, c, d = d, a, b, c
|
|
69
|
+
|
|
70
|
+
# Round 3
|
|
71
|
+
s = [3, 9, 11, 15]
|
|
72
|
+
order = [0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15]
|
|
73
|
+
for i in range(16):
|
|
74
|
+
k = order[i]
|
|
75
|
+
r = i % 4
|
|
76
|
+
a = left_rotate((a + h(b, c, d) + X[k] + 0x6ED9EBA1) & 0xFFFFFFFF, s[r])
|
|
77
|
+
a, b, c, d = d, a, b, c
|
|
78
|
+
|
|
79
|
+
a = (a + aa) & 0xFFFFFFFF
|
|
80
|
+
b = (b + bb) & 0xFFFFFFFF
|
|
81
|
+
c = (c + cc) & 0xFFFFFFFF
|
|
82
|
+
d = (d + dd) & 0xFFFFFFFF
|
|
83
|
+
|
|
84
|
+
return b"".join(x.to_bytes(4, "little") for x in (a, b, c, d))
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _mysql323(text: str) -> str:
|
|
88
|
+
nr = 1345345333
|
|
89
|
+
add = 7
|
|
90
|
+
nr2 = 0x12345671
|
|
91
|
+
for ch in text:
|
|
92
|
+
if ch in (" ", "\t"):
|
|
93
|
+
continue
|
|
94
|
+
tmp = ord(ch)
|
|
95
|
+
nr ^= (((nr & 63) + add) * tmp) + (nr << 8)
|
|
96
|
+
nr &= 0xFFFFFFFF
|
|
97
|
+
nr2 += (nr2 << 8) ^ nr
|
|
98
|
+
nr2 &= 0xFFFFFFFF
|
|
99
|
+
add += tmp
|
|
100
|
+
return f"{nr & 0x7FFFFFFF:08x}{nr2 & 0x7FFFFFFF:08x}"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _mysql41(text: str) -> str:
|
|
104
|
+
stage1 = hashlib.sha1(text.encode("utf-8")).digest()
|
|
105
|
+
stage2 = hashlib.sha1(stage1).hexdigest().upper()
|
|
106
|
+
return "*" + stage2
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _ntlm(text: str) -> str:
|
|
110
|
+
return _md4(text.encode("utf-16le")).hex()
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _mssql2000(text: str) -> str:
|
|
114
|
+
return hashlib.sha1(text.encode("utf-16le")).hexdigest().upper()
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _mssql2005_hash(text: str, salt: bytes) -> str:
|
|
118
|
+
return hashlib.sha1(salt + text.encode("utf-16le")).hexdigest().upper()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _parse_salt_bytes(salt: str, default_len: int = 16) -> bytes:
|
|
122
|
+
if not salt:
|
|
123
|
+
return os.urandom(default_len)
|
|
124
|
+
salt_value = salt.strip()
|
|
125
|
+
if salt_value.startswith("0x"):
|
|
126
|
+
salt_value = salt_value[2:]
|
|
127
|
+
if salt_value and all(ch in "0123456789abcdefABCDEF" for ch in salt_value) and len(salt_value) % 2 == 0:
|
|
128
|
+
return bytes.fromhex(salt_value)
|
|
129
|
+
return salt_value.encode("utf-8")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _argon2_hash(text: str, salt: str) -> str:
|
|
133
|
+
try:
|
|
134
|
+
from argon2.low_level import Type, hash_secret # type: ignore
|
|
135
|
+
except Exception as exc: # pragma: no cover
|
|
136
|
+
raise ValueError("argon2-cffi library is required for argon2 hashing") from exc
|
|
137
|
+
|
|
138
|
+
salt_bytes = _parse_salt_bytes(salt, default_len=16)
|
|
139
|
+
return hash_secret(
|
|
140
|
+
text.encode("utf-8"),
|
|
141
|
+
salt_bytes,
|
|
142
|
+
time_cost=2,
|
|
143
|
+
memory_cost=102400,
|
|
144
|
+
parallelism=8,
|
|
145
|
+
hash_len=32,
|
|
146
|
+
type=Type.ID,
|
|
147
|
+
).decode("utf-8")
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _scrypt_hash(text: str, salt: str) -> str:
|
|
151
|
+
salt_bytes = _parse_salt_bytes(salt, default_len=16)
|
|
152
|
+
n = 2**14
|
|
153
|
+
r = 8
|
|
154
|
+
p = 1
|
|
155
|
+
dklen = 64
|
|
156
|
+
digest = hashlib.scrypt(text.encode("utf-8"), salt=salt_bytes, n=n, r=r, p=p, dklen=dklen)
|
|
157
|
+
return f"scrypt${n}${r}${p}${salt_bytes.hex()}${digest.hex()}"
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _postgres_md5(text: str, username: str) -> str:
|
|
161
|
+
if not username:
|
|
162
|
+
raise ValueError("postgres requires a username as salt (use --salt)")
|
|
163
|
+
digest = hashlib.md5(f"{text}{username}".encode("utf-8")).hexdigest()
|
|
164
|
+
return f"md5{digest}"
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def hash_text(text: str, algorithm: str, salt: str = "", salt_mode: str = "prefix") -> str:
|
|
168
|
+
algo = algorithm.lower()
|
|
169
|
+
if algo not in HASH_FUNCS and algo not in {
|
|
170
|
+
"md4",
|
|
171
|
+
"ntlm",
|
|
172
|
+
"mysql323",
|
|
173
|
+
"mysql41",
|
|
174
|
+
"bcrypt",
|
|
175
|
+
"argon2",
|
|
176
|
+
"scrypt",
|
|
177
|
+
"mssql2000",
|
|
178
|
+
"mssql2005",
|
|
179
|
+
"mssql2012",
|
|
180
|
+
"postgres",
|
|
181
|
+
}:
|
|
182
|
+
raise ValueError(f"Unsupported hash algorithm: {algorithm}")
|
|
183
|
+
|
|
184
|
+
salt_in_algorithms = {"bcrypt", "argon2", "scrypt", "postgres", "mssql2000", "mssql2005", "mssql2012"}
|
|
185
|
+
if salt and algo not in salt_in_algorithms:
|
|
186
|
+
if salt_mode == "suffix":
|
|
187
|
+
text = f"{text}{salt}"
|
|
188
|
+
else:
|
|
189
|
+
text = f"{salt}{text}"
|
|
190
|
+
|
|
191
|
+
if algo == "md4":
|
|
192
|
+
return _md4(text.encode("utf-8")).hex()
|
|
193
|
+
if algo == "ntlm":
|
|
194
|
+
return _ntlm(text)
|
|
195
|
+
if algo == "mysql323":
|
|
196
|
+
return _mysql323(text)
|
|
197
|
+
if algo == "mysql41":
|
|
198
|
+
return _mysql41(text)
|
|
199
|
+
if algo == "mssql2000":
|
|
200
|
+
return _mssql2000(text)
|
|
201
|
+
if algo in {"mssql2005", "mssql2012"}:
|
|
202
|
+
salt_bytes = _parse_salt_bytes(salt, default_len=4)
|
|
203
|
+
digest = _mssql2005_hash(text, salt_bytes)
|
|
204
|
+
return f"0x0100{salt_bytes.hex()}{digest}"
|
|
205
|
+
if algo == "postgres":
|
|
206
|
+
return _postgres_md5(text, salt)
|
|
207
|
+
if algo == "argon2":
|
|
208
|
+
return _argon2_hash(text, salt)
|
|
209
|
+
if algo == "scrypt":
|
|
210
|
+
return _scrypt_hash(text, salt)
|
|
211
|
+
if algo == "bcrypt":
|
|
212
|
+
if bcrypt is None:
|
|
213
|
+
raise ValueError("bcrypt library is required for bcrypt hashing")
|
|
214
|
+
if not salt:
|
|
215
|
+
raise ValueError("bcrypt requires a salt (use --salt)")
|
|
216
|
+
if salt.isdigit():
|
|
217
|
+
salt_bytes = bcrypt.gensalt(rounds=int(salt))
|
|
218
|
+
else:
|
|
219
|
+
salt_bytes = salt.encode("utf-8")
|
|
220
|
+
return bcrypt.hashpw(text.encode("utf-8"), salt_bytes).decode("utf-8")
|
|
221
|
+
h = HASH_FUNCS[algo]()
|
|
222
|
+
h.update(text.encode("utf-8"))
|
|
223
|
+
return h.hexdigest()
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
MORSE_MAP = {
|
|
2
|
+
"A": ".-",
|
|
3
|
+
"B": "-...",
|
|
4
|
+
"C": "-.-.",
|
|
5
|
+
"D": "-..",
|
|
6
|
+
"E": ".",
|
|
7
|
+
"F": "..-.",
|
|
8
|
+
"G": "--.",
|
|
9
|
+
"H": "....",
|
|
10
|
+
"I": "..",
|
|
11
|
+
"J": ".---",
|
|
12
|
+
"K": "-.-",
|
|
13
|
+
"L": ".-..",
|
|
14
|
+
"M": "--",
|
|
15
|
+
"N": "-.",
|
|
16
|
+
"O": "---",
|
|
17
|
+
"P": ".--.",
|
|
18
|
+
"Q": "--.-",
|
|
19
|
+
"R": ".-.",
|
|
20
|
+
"S": "...",
|
|
21
|
+
"T": "-",
|
|
22
|
+
"U": "..-",
|
|
23
|
+
"V": "...-",
|
|
24
|
+
"W": ".--",
|
|
25
|
+
"X": "-..-",
|
|
26
|
+
"Y": "-.--",
|
|
27
|
+
"Z": "--..",
|
|
28
|
+
"0": "-----",
|
|
29
|
+
"1": ".----",
|
|
30
|
+
"2": "..---",
|
|
31
|
+
"3": "...--",
|
|
32
|
+
"4": "....-",
|
|
33
|
+
"5": ".....",
|
|
34
|
+
"6": "-....",
|
|
35
|
+
"7": "--...",
|
|
36
|
+
"8": "---..",
|
|
37
|
+
"9": "----.",
|
|
38
|
+
".": ".-.-.-",
|
|
39
|
+
",": "--..--",
|
|
40
|
+
"?": "..--..",
|
|
41
|
+
"!": "-.-.--",
|
|
42
|
+
"/": "-..-.",
|
|
43
|
+
"-": "-....-",
|
|
44
|
+
"(": "-.--.",
|
|
45
|
+
")": "-.--.-",
|
|
46
|
+
" ": "/",
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
REVERSE_MORSE = {value: key for key, value in MORSE_MAP.items()}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def encode_morse(text: str) -> str:
|
|
53
|
+
encoded = []
|
|
54
|
+
for ch in text.upper():
|
|
55
|
+
if ch in MORSE_MAP:
|
|
56
|
+
encoded.append(MORSE_MAP[ch])
|
|
57
|
+
return " ".join(encoded)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def decode_morse(code: str) -> str:
|
|
61
|
+
decoded = []
|
|
62
|
+
for token in code.strip().split():
|
|
63
|
+
decoded.append(REVERSE_MORSE.get(token, ""))
|
|
64
|
+
return "".join(decoded)
|