All pure stdlib, zero external dependencies: - dns: raw UDP resolver with A/AAAA/MX/NS/TXT/CNAME/PTR/SOA - encode: base64, hex, URL, ROT13 encode/decode - hash: md5/sha1/sha256/sha512 generation + type identification - defang: IOC defanging/refanging for safe sharing - revshell: reverse shell one-liners for 11 languages - cidr: subnet calculator with IP membership check Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
76 lines
2.4 KiB
Python
76 lines
2.4 KiB
Python
"""Plugin: hash strings and identify hash types."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import re
|
|
|
|
from derp.plugin import command
|
|
|
|
_ALGOS = ("md5", "sha1", "sha256", "sha512")
|
|
|
|
# Patterns for hash identification (length -> possible types)
|
|
_HASH_PATTERNS: list[tuple[str, int, str]] = [
|
|
(r"^[a-fA-F0-9]{32}$", 32, "MD5"),
|
|
(r"^[a-fA-F0-9]{40}$", 40, "SHA-1"),
|
|
(r"^[a-fA-F0-9]{56}$", 56, "SHA-224"),
|
|
(r"^[a-fA-F0-9]{64}$", 64, "SHA-256 / SHA3-256"),
|
|
(r"^[a-fA-F0-9]{96}$", 96, "SHA-384 / SHA3-384"),
|
|
(r"^[a-fA-F0-9]{128}$", 128, "SHA-512 / SHA3-512"),
|
|
(r"^\$2[aby]\$\d{2}\$.{53}$", 0, "bcrypt"),
|
|
(r"^\$6\$", 0, "sha512crypt"),
|
|
(r"^\$5\$", 0, "sha256crypt"),
|
|
(r"^\$1\$", 0, "md5crypt"),
|
|
(r"^[a-fA-F0-9]{16}$", 16, "MySQL 3.x / Half MD5"),
|
|
(r"^\*[a-fA-F0-9]{40}$", 0, "MySQL 4.1+"),
|
|
]
|
|
|
|
|
|
@command("hash", help="Hash text: !hash [algo] <text>")
|
|
async def cmd_hash(bot, message):
|
|
"""Generate hash digests.
|
|
|
|
!hash hello -> MD5, SHA1, SHA256
|
|
!hash sha512 hello -> specific algorithm
|
|
"""
|
|
parts = message.text.split(None, 2)
|
|
if len(parts) < 2:
|
|
await bot.reply(message, f"Usage: !hash [{'|'.join(_ALGOS)}] <text>")
|
|
return
|
|
|
|
# Check if first arg is an algorithm name
|
|
if len(parts) >= 3 and parts[1].lower() in _ALGOS:
|
|
algo = parts[1].lower()
|
|
text = parts[2]
|
|
digest = hashlib.new(algo, text.encode()).hexdigest()
|
|
await bot.reply(message, f"{algo}: {digest}")
|
|
return
|
|
|
|
# No algorithm specified -- show all common hashes
|
|
text = message.text.split(None, 1)[1]
|
|
results = []
|
|
for algo in ("md5", "sha1", "sha256"):
|
|
digest = hashlib.new(algo, text.encode()).hexdigest()
|
|
results.append(f"{algo}:{digest}")
|
|
await bot.reply(message, " ".join(results))
|
|
|
|
|
|
@command("hashid", help="Identify hash type: !hashid <hash>")
|
|
async def cmd_hashid(bot, message):
|
|
"""Identify a hash type by its format and length."""
|
|
parts = message.text.split(None, 1)
|
|
if len(parts) < 2:
|
|
await bot.reply(message, "Usage: !hashid <hash>")
|
|
return
|
|
|
|
value = parts[1].strip()
|
|
matches = []
|
|
for pattern, _, name in _HASH_PATTERNS:
|
|
if re.match(pattern, value):
|
|
matches.append(name)
|
|
|
|
if matches:
|
|
await bot.reply(message, f"Possible: {', '.join(matches)}")
|
|
else:
|
|
await bot.reply(message, f"Unknown hash format (length: {len(value)})")
|