Implementing Dictionary-Based Password Attacks on RAR Archives Using Python
Python's rarfile library enables programmatic interaction with RAR archives. A core function for testing passwords can be implemented as follows:
import rarfile
def try_extract_archive(archive_path, passphrase):
"""Attempts to extract a RAR file with a given password.
Returns True on successful extraction, False otherwise."""
try:
with rarfile.RarFile(archive_path) as rar_ref:
rar_ref.extractall(pwd=passphrase)
return True
except (rarfile.PasswordRequired, rarfile.BadRarFile, rarfile.Error) as e:
# Re-raise non-password related critical errors
if isinstance(e, rarfile.BadRarFile):
print("Invalid RAR file format.")
raise
# Check for password mismatch in generic RAR errors
if "bad password" in str(e).lower() or "checksum error" in str(e).lower():
return False
# Handle multi-volume archive error
if isinstance(e, rarfile.NeedFirstVolume):
print("First volume of a multi-part archive is required.")
return False
print(f"RAR-specific error occurred: {e}")
return False
except Exception as general_error:
print(f"An unexpected error occurred: {general_error}")
return False
For efficient testing, passwords can be processed in batches using concurrent execution:
import itertools
import string
from concurrent.futures import ThreadPoolExecutor, as_completed
# Configuration for batch processing
PASSWORD_BATCH_LIMIT = 500
def process_password_batch(archive_path, password_list):
"""Tests a list of passwords against the RAR archive using threads."""
with ThreadPoolExecutor() as executor:
# Map each future to its corresponding password
task_map = {}
for pwd in password_list:
future = executor.submit(try_extract_archive, archive_path, pwd)
task_map[future] = pwd
# Process completed tasks
for completed_future in as_completed(task_map):
current_password = task_map[completed_future]
try:
result = completed_future.result()
if result:
return current_password # Successful password found
except Exception as task_error:
print(f"Error testing password '{current_password}': {task_error}")
return None # No password in this batch succeeded
The main cracking logic generates password combinations within a specified length range and validates succesfull extraction by comparing file sizes:
def execute_crack_sequence(char_set, min_len, max_len):
"""Orchestrates the password cracking process."""
target_file = input("Enter the path to the RAR file: ")
# Get original file size for verification
original_size = None
try:
with rarfile.RarFile(target_file) as rar_ref:
if rar_ref.filelist:
original_size = rar_ref.filelist[0].file_size
except Exception as size_error:
print(f"Could not read archive metadata: {size_error}")
# Iterate through password lengths
for pwd_length in range(min_len, max_len + 1):
print(f"Testing passwords of length {pwd_length}...")
password_combinations = itertools.product(char_set, repeat=pwd_length)
current_batch = []
for pwd_tuple in password_combinations:
candidate = ''.join(pwd_tuple)
current_batch.append(candidate)
# Process batch when it reaches the limit
if len(current_batch) >= PASSWORD_BATCH_LIMIT:
found_password = process_password_batch(target_file, current_batch)
if found_password:
verify_extraction(target_file, found_password, original_size)
return
current_batch = [] # Reset for next batch
# Process any remaining passwords in the final batch
if current_batch:
found_password = process_password_batch(target_file, current_batch)
if found_password:
verify_extraction(target_file, found_password, original_size)
return
print("Password not found within the specified constraints.")
def verify_extraction(archive_path, password, expected_size):
"""Verifies a successful extraction by checking the uncompressed file size."""
if expected_size is not None:
try:
with rarfile.RarFile(archive_path) as rar_ref:
extracted_size = rar_ref.filelist[0].file_size if rar_ref.filelist else None
if extracted_size == expected_size:
print(f"Success! Password is: {password}")
else:
print(f"Size mismatch. Extracted: {extracted_size}, Expected: {expected_size}")
except Exception as verify_error:
print(f"Verification failed: {verify_error}")
else:
print(f"Password found: {password} (size verification skipped)")
Execution begins by defining the character set and length parameters:
if __name__ == "__main__":
# Define the search space: lowercase, uppercase, and digits
charset = string.ascii_letters + string.digits
min_password_len = int(input("Enter minimum password length: "))
max_password_len = int(input("Enter maximum password length: "))
execute_crack_sequence(charset, min_password_len, max_password_len)
This approach systematically tests password combinations. The computational time required grows exponentially with password length, making it practical only for shorter passwords. The verification step helps prevant false positives by comparing the size of the etxracted content with the original archive's metadata.