| name | hash-cracking |
| description | Crack password hashes using dictionary attacks, rainbow tables, and hashcat. Use this skill when recovering passwords from hashes, identifying hash types, or exploiting weak password policies in CTF challenges. |
Hash Cracking
Identify and crack password hashes.
Hash Type Identification
import re
def identify_hash(hash_str: str) -> list:
"""Identify possible hash types based on format and length."""
hash_str = hash_str.strip()
possibilities = []
# Length-based identification
length = len(hash_str)
if length == 32 and re.match(r'^[a-fA-F0-9]+$', hash_str):
possibilities.extend(['MD5', 'MD4', 'NTLM', 'LM'])
elif length == 40 and re.match(r'^[a-fA-F0-9]+$', hash_str):
possibilities.extend(['SHA1', 'MySQL5'])
elif length == 64 and re.match(r'^[a-fA-F0-9]+$', hash_str):
possibilities.extend(['SHA256', 'SHA3-256', 'BLAKE2s'])
elif length == 128 and re.match(r'^[a-fA-F0-9]+$', hash_str):
possibilities.extend(['SHA512', 'SHA3-512', 'BLAKE2b', 'Whirlpool'])
# Format-based identification
if hash_str.startswith('$1$'):
possibilities.append('MD5-Crypt')
elif hash_str.startswith('$2a$') or hash_str.startswith('$2b$'):
possibilities.append('Bcrypt')
elif hash_str.startswith('$5$'):
possibilities.append('SHA256-Crypt')
elif hash_str.startswith('$6$'):
possibilities.append('SHA512-Crypt')
elif hash_str.startswith('$argon2'):
possibilities.append('Argon2')
elif ':' in hash_str:
possibilities.append('Hash:Salt format')
return possibilities
# Example
test_hash = "5d41402abc4b2a76b9719d911017c592"
print(identify_hash(test_hash)) # ['MD5', ...]
Common Hash Functions
import hashlib
def compute_hash(data: str, algorithm: str = 'md5') -> str:
"""Compute hash of string."""
h = hashlib.new(algorithm)
h.update(data.encode())
return h.hexdigest()
def compute_all_hashes(data: str) -> dict:
"""Compute all common hashes."""
algorithms = ['md5', 'sha1', 'sha256', 'sha512', 'sha3_256']
return {algo: compute_hash(data, algo) for algo in algorithms}
# Example
password = "password123"
hashes = compute_all_hashes(password)
for algo, h in hashes.items():
print(f"{algo}: {h}")
Dictionary Attack
import hashlib
def dictionary_attack(target_hash: str, wordlist_path: str, algorithm: str = 'md5') -> str:
"""Crack hash using dictionary attack."""
with open(wordlist_path, 'r', encoding='latin-1') as f:
for line in f:
word = line.strip()
h = hashlib.new(algorithm)
h.update(word.encode())
if h.hexdigest() == target_hash.lower():
return word
return None
def dictionary_attack_with_rules(target_hash: str, wordlist_path: str, algorithm: str = 'md5') -> str:
"""Dictionary attack with common mutations."""
rules = [
lambda x: x,
lambda x: x.upper(),
lambda x: x.capitalize(),
lambda x: x + '1',
lambda x: x + '123',
lambda x: x + '!',
lambda x: x.replace('a', '@'),
lambda x: x.replace('e', '3'),
lambda x: x.replace('o', '0'),
lambda x: x[::-1],
]
with open(wordlist_path, 'r', encoding='latin-1') as f:
for line in f:
word = line.strip()
for rule in rules:
try:
mutated = rule(word)
h = hashlib.new(algorithm)
h.update(mutated.encode())
if h.hexdigest() == target_hash.lower():
return mutated
except:
continue
return None
# Example
target = "5f4dcc3b5aa765d61d8327deb882cf99" # MD5 of "password"
# result = dictionary_attack(target, "/usr/share/wordlists/rockyou.txt")
Brute Force Attack
import itertools
import string
def brute_force(target_hash: str, max_length: int = 6, charset: str = None, algorithm: str = 'md5') -> str:
"""Brute force hash with character set."""
if charset is None:
charset = string.ascii_lowercase + string.digits
for length in range(1, max_length + 1):
for combo in itertools.product(charset, repeat=length):
candidate = ''.join(combo)
h = hashlib.new(algorithm)
h.update(candidate.encode())
if h.hexdigest() == target_hash.lower():
return candidate
return None
# Example (small charset for demo)
target = "900150983cd24fb0d6963f7d28e17f72" # MD5 of "abc"
result = brute_force(target, max_length=4, charset="abc")
print(f"Cracked: {result}")
Hash Extension Attack (Length Extension)
import struct
def md5_padding(message_len: int) -> bytes:
"""Generate MD5 padding for length extension attack."""
padding = b'\x80'
padding += b'\x00' * ((55 - message_len) % 64)
padding += struct.pack('<Q', message_len * 8)
return padding
def sha1_padding(message_len: int) -> bytes:
"""Generate SHA1 padding for length extension attack."""
padding = b'\x80'
padding += b'\x00' * ((55 - message_len) % 64)
padding += struct.pack('>Q', message_len * 8)
return padding
# Length extension attack concept:
# If you have H(secret || message), you can compute
# H(secret || message || padding || extension) without knowing secret
def length_extension_attack(original_hash: str, original_msg: bytes,
secret_len: int, extension: bytes) -> tuple:
"""Perform length extension attack on MD5.
Returns (new_message, new_hash)
"""
# This requires implementing MD5 with custom initial state
# Use hashpumpy library for actual implementation
pass
Hashcat Integration
import subprocess
import tempfile
import os
def crack_with_hashcat(hash_file: str, wordlist: str, hash_type: int) -> str:
"""Crack hash using hashcat.
Common hash types:
- 0: MD5
- 100: SHA1
- 1400: SHA256
- 1800: SHA512
- 3200: bcrypt
- 1000: NTLM
"""
cmd = [
'hashcat',
'-m', str(hash_type),
'-a', '0', # Dictionary attack
hash_file,
wordlist,
'--quiet',
'--potfile-disable'
]
result = subprocess.run(cmd, capture_output=True, text=True)
return result.stdout
# Example hashcat command lines
hashcat_examples = """
# MD5 dictionary attack
hashcat -m 0 hashes.txt wordlist.txt
# MD5 with rules
hashcat -m 0 hashes.txt wordlist.txt -r rules/best64.rule
# SHA256 brute force (6 chars, lowercase + digits)
hashcat -m 1400 hash.txt -a 3 ?l?l?l?l?l?l
# Bcrypt with wordlist
hashcat -m 3200 hash.txt wordlist.txt
# Show cracked hashes
hashcat -m 0 hashes.txt --show
"""
Rainbow Table Lookup
import requests
def online_hash_lookup(hash_str: str) -> str:
"""Look up hash in online rainbow tables."""
# Example using md5decrypt API
try:
# Note: This is conceptual - actual APIs vary
response = requests.get(f"https://api.example.com/hash/{hash_str}")
if response.status_code == 200:
return response.json().get('plaintext')
except:
pass
return None
def check_crackstation(hash_str: str) -> str:
"""Check hash against CrackStation (conceptual)."""
# CrackStation has web interface, not API
# Use their website or local rainbow tables
pass
# Pre-computed common password hashes for quick lookup
COMMON_HASHES = {
'5f4dcc3b5aa765d61d8327deb882cf99': 'password',
'e99a18c428cb38d5f260853678922e03': 'abc123',
'd8578edf8458ce06fbc5bb76a58c5ca4': 'qwerty',
'25d55ad283aa400af464c76d713c07ad': '12345678',
'827ccb0eea8a706c4c34a16891f84e7b': '12345',
}
def quick_lookup(hash_str: str) -> str:
"""Quick lookup in common hashes."""
return COMMON_HASHES.get(hash_str.lower())