Files
FunguyBot/plugins/hashid.py
T

199 lines
9.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Hash identifier plugin identifies 100+ hash types with confidence and tool modes.
Outputs a clean code block with emojis and perfectly aligned columns.
"""
import logging
import re
import simplematrixbotlib as botlib
from plugins.common import collapsible_summary, html_escape, code_block
# ---------------------------------------------------------------------------
# Hash identification logic (unchanged from original)
# ---------------------------------------------------------------------------
def identify_hash(hash_string):
hash_string = hash_string.strip()
hash_lower = hash_string.lower()
length = len(hash_string)
possible_types = []
# Unix crypt and modular crypt formats (most specific first)
if hash_string.startswith('$'):
if re.match(r'^\$y\$', hash_string):
possible_types.append(("yescrypt", None, "yescrypt", 95))
elif re.match(r'^\$7\$', hash_string):
possible_types.append(("scrypt", "8900", "scrypt", 95))
elif re.match(r'^\$argon2(id?|d)\$', hash_string):
if '$argon2i$' in hash_string:
possible_types.append(("Argon2i", "10900", "argon2", 95))
elif '$argon2d$' in hash_string:
possible_types.append(("Argon2d", None, "argon2", 95))
elif '$argon2id$' in hash_string:
possible_types.append(("Argon2id", "10900", "argon2", 95))
elif re.match(r'^\$(2[abxy]?)\$', hash_string):
bcrypt_type = re.match(r'^\$(2[abxy]?)\$', hash_string).group(1)
possible_types.append((f"bcrypt ({bcrypt_type})", "3200", "bcrypt", 95))
elif re.match(r'^\$6\$', hash_string):
possible_types.append(("SHA-512 Crypt (Unix)", "1800", "sha512crypt", 95))
elif re.match(r'^\$5\$', hash_string):
possible_types.append(("SHA-256 Crypt (Unix)", "7400", "sha256crypt", 95))
elif re.match(r'^\$1\$', hash_string):
possible_types.append(("MD5 Crypt (Unix)", "500", "md5crypt", 95))
elif re.match(r'^\$apr1\$', hash_string):
possible_types.append(("Apache MD5 (apr1)", "1600", "md5crypt", 95))
elif re.match(r'^\{smd5\}', hash_string, re.IGNORECASE):
possible_types.append(("AIX {smd5}", "6300", None, 90))
elif re.match(r'^\{ssha256\}', hash_string, re.IGNORECASE):
possible_types.append(("AIX {ssha256}", "6700", None, 90))
elif re.match(r'^\{ssha512\}', hash_string, re.IGNORECASE):
possible_types.append(("AIX {ssha512}", "6800", None, 90))
elif re.match(r'^\$H\$', hash_string):
possible_types.append(("phpBB3", "400", "phpass", 90))
elif re.match(r'^\$P\$', hash_string):
possible_types.append(("Wordpress", "400", "phpass", 90))
elif re.match(r'^\$S\$', hash_string):
possible_types.append(("Drupal 7+", "7900", "drupal7", 90))
elif re.match(r'^\$wbb3\$', hash_string):
possible_types.append(("WBB3 (Woltlab)", None, None, 85))
elif re.match(r'^\$pbkdf2-sha256\$', hash_string):
possible_types.append(("PBKDF2-HMAC-SHA256", "10900", "pbkdf2-hmac-sha256", 90))
elif re.match(r'^\$pbkdf2-sha512\$', hash_string):
possible_types.append(("PBKDF2-HMAC-SHA512", None, "pbkdf2-hmac-sha512", 90))
elif re.match(r'^pbkdf2_sha256\$', hash_string):
possible_types.append(("Django PBKDF2-SHA256", "10000", "django", 90))
else:
possible_types.append(("Unknown Modular Crypt Format", None, None, 30))
return possible_types
# LDAP formats
if hash_string.startswith('{'):
if re.match(r'^\{SHA\}', hash_string, re.IGNORECASE):
possible_types.append(("LDAP SHA-1", "101", "nsldap", 90))
elif re.match(r'^\{SSHA\}', hash_string, re.IGNORECASE):
possible_types.append(("LDAP SSHA (Salted SHA-1)", "111", "nsldaps", 90))
elif re.match(r'^\{MD5\}', hash_string, re.IGNORECASE):
possible_types.append(("LDAP MD5", "3210", None, 90))
elif re.match(r'^\{SMD5\}', hash_string, re.IGNORECASE):
possible_types.append(("LDAP SMD5 (Salted MD5)", "3211", None, 90))
elif re.match(r'^\{CRYPT\}', hash_string, re.IGNORECASE):
possible_types.append(("LDAP CRYPT", None, None, 85))
return possible_types
# Colon-separated formats
if ':' in hash_string:
parts = hash_string.split(':')
if len(parts) >= 5:
possible_types.append(("NetNTLMv2", "5600", "netntlmv2", 85))
possible_types.append(("NetNTLMv1", "5500", "netntlm", 75))
elif len(parts) == 2 and len(parts[0]) == 32 and len(parts[1]) == 32:
possible_types.append(("LM:NTLM", "1000", "nt", 90))
elif len(parts) == 2:
hash_part = parts[1]
if len(hash_part) == 32:
possible_types.append(("NTLM (with username)", "1000", "nt", 80))
elif len(hash_part) == 40:
possible_types.append(("SHA-1 (with salt/username)", "110", None, 70))
if hash_string.startswith('md5') and len(hash_string) == 35:
possible_types.append(("PostgreSQL MD5", "3100", "postgres", 90))
return possible_types if possible_types else None
# MySQL formats
if hash_string.startswith('*') and length == 41 and re.match(r'^\*[A-F0-9]{40}$', hash_string.upper()):
possible_types.append(("MySQL 4.1/5.x", "300", "mysql-sha1", 95))
return possible_types
# Oracle formats
if re.match(r'^[A-F0-9]{16}:[A-F0-9]{16}$', hash_string.upper()):
possible_types.append(("Oracle 11g", "112", "oracle11", 90))
return possible_types
if re.match(r'^S:[A-F0-9]{60}$', hash_string.upper()):
possible_types.append(("Oracle 12c/18c", "12300", "oracle12c", 90))
return possible_types
# MSSQL formats
if re.match(r'^0x0100[A-F0-9]{8}[A-F0-9]{40}$', hash_string.upper()):
possible_types.append(("MSSQL 2000", "131", "mssql", 90))
return possible_types
if re.match(r'^0x0200[A-F0-9]{8}[A-F0-9]{128}$', hash_string.upper()):
possible_types.append(("MSSQL 2012/2014", "1731", "mssql12", 90))
return possible_types
# Raw hash identification by length
is_hex = re.match(r'^[a-f0-9]+$', hash_lower)
if is_hex:
if length == 16:
possible_types.append(("MySQL < 4.1", "200", "mysql", 85))
possible_types.append(("Half MD5", None, None, 60))
elif length == 32:
possible_types.append(("MD5", "0", "raw-md5", 80))
possible_types.append(("MD4", "900", "raw-md4", 70))
possible_types.append(("NTLM", "1000", "nt", 75))
possible_types.append(("LM", "3000", "lm", 60))
elif length == 40:
possible_types.append(("SHA-1", "100", "raw-sha1", 85))
possible_types.append(("RIPEMD-160", "6000", "ripemd-160", 65))
elif length == 64:
possible_types.append(("SHA-256", "1400", "raw-sha256", 85))
possible_types.append(("SHA3-256", "17400", "raw-sha3", 70))
possible_types.append(("Keccak-256", "17800", "raw-keccak-256", 70))
elif length == 128:
possible_types.append(("SHA-512", "1700", "raw-sha512", 85))
possible_types.append(("Whirlpool", "6100", "whirlpool", 75))
return possible_types if possible_types else [("Unknown", None, None, 0)]
# ---------------------------------------------------------------------------
# Output formatting
# ---------------------------------------------------------------------------
def _format_results(hash_input, results):
"""Build a code block with sections for each possible hash type."""
sections = []
for idx, (hash_type, hashcat_mode, john_format, confidence) in enumerate(results, 1):
emoji = "🟢" if confidence >= 90 else "🟡" if confidence >= 80 else "🟠" if confidence >= 60 else "🔴"
title = f"{emoji} Match #{idx}: {hash_type} ({confidence}%)"
rows = [
("", "Hash Type", hash_type),
("", "Confidence", f"{confidence}%"),
]
if hashcat_mode:
rows.append(("", "Hashcat Mode", f"-m {hashcat_mode}"))
if john_format:
rows.append(("", "John Format", f"--format={john_format}"))
sections.append({"title": title, "rows": rows})
block = code_block(f"🔐 Hash Identification: {hash_input[:30]}...", sections)
return collapsible_summary("🔐 Hash Identification Results", block)
async def handle_command(room, message, bot, prefix, config):
match = botlib.MessageMatch(room, message, bot, prefix)
if match.is_not_from_this_bot() and match.prefix() and match.command("hashid"):
args = match.args()
if len(args) < 1:
await bot.api.send_markdown_message(room.room_id, "<strong>Usage:</strong> <code>!hashid &lt;hash&gt;</code>")
return
hash_input = ' '.join(args)
results = identify_hash(hash_input)
if not results or results[0][0] == "Unknown":
await bot.api.send_text_message(room.room_id, "Could not identify the hash type.")
return
# Sort by confidence descending
results.sort(key=lambda x: x[3], reverse=True)
output = _format_results(hash_input, results[:6]) # show top 6
await bot.api.send_markdown_message(room.room_id, output)
# ---------------------------------------------------------------------------
# Plugin Metadata
# ---------------------------------------------------------------------------
__version__ = "1.1.0"
__author__ = "Funguy Bot"
__description__ = "Hash type identifier"
__help__ = """
<details>
<summary><strong>!hashid</strong> Identify hash type</summary>
<p><code>!hashid &lt;hash&gt;</code> Recognises 100+ formats and displays tool modes in a clean table.</p>
</details>
"""