From e9db7a3c02df65df99317f33680cd965f3e58bbf Mon Sep 17 00:00:00 2001 From: Jan Date: Thu, 26 Jun 2025 17:02:11 +0200 Subject: [PATCH 1/2] Add multithreading and tqdm progress to brute force Introduced multithreading using Python's threading and queue modules to parallelize password attempts, improving performance. Integrated tqdm for progress visualization. Added command-line options for character set selection. Updated dependencies to include tqdm and incremented version to 1.0.4. --- .gitignore | 3 ++ compressedcrack/main.py | 78 +++++++++++++++++++++++++++++++++-------- pyproject.toml | 4 +-- requirements.txt | 3 +- setup.py | 4 +-- 5 files changed, 73 insertions(+), 19 deletions(-) diff --git a/.gitignore b/.gitignore index 7bbc71c..e78e6b7 100644 --- a/.gitignore +++ b/.gitignore @@ -99,3 +99,6 @@ ENV/ # mypy .mypy_cache/ + +# vscode +.vscode/ \ No newline at end of file diff --git a/compressedcrack/main.py b/compressedcrack/main.py index 7affcff..127750d 100644 --- a/compressedcrack/main.py +++ b/compressedcrack/main.py @@ -5,11 +5,30 @@ import time import sys +import threading +import queue +from tqdm import tqdm + # Default Constants LETTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" NUMBERS = "0123456789" SPECIAL_CHARACTERS = "!@#$%^&*()-_+=~`[]{}|\\:;\"'<>,.?" +# Define queue +workers = 8 +queue_length = workers * 2 +q = queue.Queue(queue_length) + +def worker(): + while True: + item = q.get() + if test_archive_password(item[0], item[1], item[2]): + tqdm.write(f"\nPassword is correct: {item[1]} ✅") + q.shutdown() + else: + pass + # tqdm.write(f"\nPassword is incorrect: {item[1]} ❌") + q.task_done() def test_archive_password(file_path, password, verbose): """ @@ -68,13 +87,18 @@ def brute_force_password(file_path, start_length, max_length, character_set, ver f"\nGenerating passwords with length {length}...") print( f"\nTotal passwords to be generated: {len(character_set) ** length}") - for password_tuple in product(character_set, repeat=length): + + total_combinations = len(character_set) ** length + for password_tuple in tqdm(product(character_set, repeat=length), + total=total_combinations, + unit="passwords"): password = ''.join(password_tuple) password_count += 1 total_password_count += 1 - if test_archive_password(file_path, password, verbose): - print(f"\nPassword is correct: {password}") - return password, total_password_count, time.time() - start_time + + q.put([file_path, password, verbose]) + + end_time = time.time() if verbose: print( @@ -98,7 +122,7 @@ def main(): Main function to parse command line arguments and initiate the brute force process. """ parser = argparse.ArgumentParser( - description="Crack password-protected archives using brute force.") + description="Crack password-protected archives using brute force.",) parser.add_argument("file_path", help="Path to the compressed file.") parser.add_argument("--min-length", type=int, default=1, help="Minimum password length.") @@ -106,22 +130,43 @@ def main(): help="Maximum password length.") parser.add_argument("--verbose", action="store_true", help="Increase output verbosity.") + parser.add_argument("-l", action=argparse.BooleanOptionalAction, + help="Use default letters set.") + parser.add_argument("-n", action=argparse.BooleanOptionalAction, + help="Use default numbers set.") + parser.add_argument("-s", action=argparse.BooleanOptionalAction, + help=f"Use default special character set.") + parser.add_argument("-c", help="Define character set to be used.") args = parser.parse_args() # Interactive character set definition with customization option character_set = "" - if input("Include letters? (y/n): ").lower().startswith('y'): - character_set += customize_character_set(LETTERS, "letters") - if input("Include numbers? (y/n): ").lower().startswith('y'): - character_set += customize_character_set(NUMBERS, "numbers") - if input("Include special characters? (y/n): ").lower().startswith('y'): - character_set += customize_character_set( - SPECIAL_CHARACTERS, "special characters") + if args.c: + character_set = args.c + else: + if args.l: + character_set += LETTERS + + if args.n: + character_set += NUMBERS + + if args.s: + character_set += SPECIAL_CHARACTERS if not character_set: - print("\nNo characters selected. Using default character set.") - character_set = LETTERS + NUMBERS + SPECIAL_CHARACTERS + if input("Include letters? (y/n): ").lower().startswith('y'): + character_set += customize_character_set(LETTERS, "letters") + + if input("Include numbers? (y/n): ").lower().startswith('y'): + character_set += customize_character_set(NUMBERS, "numbers") + + if input("Include special characters? (y/n): ").lower().startswith('y'): + character_set += customize_character_set( + SPECIAL_CHARACTERS, "special characters") + if not character_set: + print("\nNo characters selected. Using default character set.") + character_set = LETTERS + NUMBERS + SPECIAL_CHARACTERS # Print the character set to be used print(f"\nCharacter set to be used: {character_set}") @@ -129,6 +174,11 @@ def main(): f"Total password combinations to be tried: {calculate_password_combinations(args.min_length, args.max_length, character_set)}") overall_start_time = time.time() + + # Create threads + for i in range(0, workers-1): + threading.Thread(target=worker, daemon=True).start() + password, total_password_count, subset_time = brute_force_password( args.file_path, args.min_length, args.max_length, character_set, args.verbose) overall_end_time = time.time() diff --git a/pyproject.toml b/pyproject.toml index 22725e3..fc00ca4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "compressedcrack" -version = "1.0.3" +version = "1.0.4" description = "A command-line tool to crack password-protected compressed files using brute force." readme = "README.md" authors = [{ name = "Minh Thanh", email = "thanhdoantranminh@gmail.com" }] @@ -20,7 +20,7 @@ classifiers = [ "Programming Language :: Python :: 3.9", ] keywords = ["brute force", "password cracking", "compressed files"] -dependencies = ["patool>=2.2.0"] +dependencies = ["patool>=2.2.0", "tqdm>=2.2.3"] requires-python = ">=3.6" [project.urls] diff --git a/requirements.txt b/requirements.txt index 7007425..d2c43ba 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ -patool==2.2.0 +patool>=2.2.0 +tqdm>=2.2.3 \ No newline at end of file diff --git a/setup.py b/setup.py index 4f27e74..8d7af08 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name="compressedcrack", - version="1.0.3", + version="1.0.4", description="A command-line tool to crack password-protected compressed files using brute force.", long_description=open("README.md").read(), long_description_content_type="text/markdown", @@ -21,7 +21,7 @@ "Programming Language :: Python :: 3.9", ], keywords=["brute force", "password cracking", "compressed files"], - install_requires=["patool>=2.2.0", "click"], + install_requires=["patool>=2.2.0", "tqdm>=2.2.3"], python_requires=">=3.6", entry_points={ "console_scripts": [ From bf6e9261a9d3e5b256c4a592b2da2f8c0309384b Mon Sep 17 00:00:00 2001 From: Jan Date: Thu, 26 Jun 2025 17:07:23 +0200 Subject: [PATCH 2/2] Add thread count option and adjust queue handling Introduces a '-t' argument to set the number of worker threads, and updates queue initialization to use a size based on the thread count. The queue is now initialized in main() with the correct length, improving configurability and resource management. --- compressedcrack/main.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/compressedcrack/main.py b/compressedcrack/main.py index 127750d..2c2b633 100644 --- a/compressedcrack/main.py +++ b/compressedcrack/main.py @@ -15,9 +15,7 @@ SPECIAL_CHARACTERS = "!@#$%^&*()-_+=~`[]{}|\\:;\"'<>,.?" # Define queue -workers = 8 -queue_length = workers * 2 -q = queue.Queue(queue_length) +q = queue.Queue() def worker(): while True: @@ -137,6 +135,8 @@ def main(): parser.add_argument("-s", action=argparse.BooleanOptionalAction, help=f"Use default special character set.") parser.add_argument("-c", help="Define character set to be used.") + parser.add_argument("-t", type=int, default=4, + help="Set amount of threads running simultaneous.") args = parser.parse_args() @@ -176,6 +176,9 @@ def main(): overall_start_time = time.time() # Create threads + workers = args.t + queue_length = workers * 2 + q = queue.Queue(queue_length) for i in range(0, workers-1): threading.Thread(target=worker, daemon=True).start()