""" 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, "Usage: !hashid <hash>") 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__ = """
!hashid – Identify hash type

!hashid <hash> – Recognises 100+ formats and displays tool modes in a clean table.

"""