Cracking Zip File Passwords with Node.js: A Brute Force Approach
In certain cases, such as retrieving forgotten passwords or working in cybersecurity, cracking password-protected zip files can be necessary. Using a brute force approach, we can attempt to unlock zip files by systematically testing possible passwords until the correct one is found. This guide demonstrates how to implement such an approach in Node.js, using both single-threaded and multi-threaded (worker-thread) methods.
Let’s break down the code and the Node.js packages used in this process.
Overview of Brute Force Approach
Brute-forcing a password means testing all possible combinations until the password is found. While it’s not the most efficient method (especially for long or complex passwords), it is sometimes the only option available. The code we’ll look at uses two variations:
- Single-threaded approach: A basic Node.js implementation for smaller tasks.
- Multi-threaded approach with worker threads: A faster, more optimized approach for large tasks, where multiple threads simultaneously attempt different combinations.
Packages Used in This Implementation
To effectively run our brute-force script, we need a few Node.js packages to help with file handling, multithreading, and command execution:
- worker_threads: This package enables the use of worker threads in Node.js, allowing you to perform tasks in parallel across multiple threads. It’s beneficial for CPU-intensive operations like brute-forcing.
- child_process: This module allows us to execute shell commands from within our Node.js application. Here, we use it to run commands that attempt to unzip files using different password combinations.
- 7zip-bin: This package provides access to the 7-Zip compression tool directly from Node.js. We use it to test if a password successfully unzips the file. The 7-Zip executable is highly efficient for handling various compressed file formats, including password-protected zips.
- colors: This package is a simple utility to add color and styling to console outputs. It enhances readability by allowing us to color-code results, like successful or failed password attempts.
Setting Up the Project
Before we dive into the code, let’s install the required packages. Run the following command in your project directory:
npm install worker_threads child_process 7zip-bin colors
In this article, we will cover both single-threaded and multi-threaded methods.
Let’s start the code.
Single Thread Method
1. Setting Up Required Libraries and Constants
const { exec } = require('child_process');
const pathTo7zip = require('7zip-bin').path7za;
require('colors');
This part imports the necessary modules:
- child_process for executing terminal commands from Node.
- 7zip-bin to get the path to 7zip for file operations.
- colors for console color styling to make logs more readable.
Next, we define constants for brute-forcing the password:
const CHARSET = 'A0123456789'; // Character set
const MIN_LENGTH = 1; // Minimum length of the password
const MAX_LENGTH = 5; // Maximum length of the password
const ZIP_FILE_PATH = './protected.zip'; // Path to your zip file
Here:
- CHARSET specifies the characters to include in passwords (in this case, alphanumeric characters).
- MIN_LENGTH and MAX_LENGTH set the range of password lengths to try.
- ZIP_FILE_PATH is the path to the protected zip file you’re trying to unlock.
2. Generator Function for Passwords
function* generatePasswords(length, charset) {
const password = Array(length).fill(0); // Initial indices array
while (true) {
// Convert indices to password
const currentPassword = password.map(i => charset[i]).join('');
yield currentPassword;
// Increment the password
let i = length - 1;
while (i >= 0 && password[i] === charset.length - 1) {
password[i] = 0;
i--;
}
if (i < 0) break; // All combinations are generated
password[i]++;
}
}
The generatePasswords function is a generator that:
- Creates all possible passwords of a given length using characters from the specified charset.
- Uses an array of indices to track each character position in the password. When it reaches the end of one sequence, it resets that character and increments the next position.
Each generated password is yielded by the generator to be used in the brute-force process.
3. Testing Passwords with 7zip
function tryUnzipWithPassword(password) {
console.log(`password: ${password.red}`);
return new Promise((resolve, reject) => {
const command = `"${pathTo7zip}" t "${ZIP_FILE_PATH}" -p"${password}"`;
exec(command, (error, stdout, stderr) => {
triedPasswordCount++;
if (stderr.includes('Wrong password') || stderr.includes('incorrect password')) {
resolve(false); // Incorrect password
} else if (stdout.includes('Everything is Ok')) {
endTime = new Date().getTime();
resolve(true); // Correct password
} else {
reject(stderr); // Some other error occurred
}
});
});
}
The tryUnzipWithPassword function attempts to open the zip file with a given password by:
- Running a 7zip command to test (t) the zip file with the password.
- Checking the stderr output to detect if the password is incorrect.
- Checking the stdout output to confirm if the file is successfully opened.
If the password is correct, resolve(true) is returned, ending the brute-force attempt.
4. Brute Force Function to Attempt All Passwords
async function bruteForceZip() {
startTime = new Date().getTime(); // Start time of the brute force process
// Loop through all possible password lengths
for (let length = MIN_LENGTH; length <= MAX_LENGTH; length++) {
const passwordGenerator = generatePasswords(length, CHARSET); // Generator for all possible passwords
// Try each password
for (const password of passwordGenerator) {
try {
const found = await tryUnzipWithPassword(password); // Try to unzip the file with the current password
// If the password is correct, log the result and exit
if (found) {
console.log(`PASSWORD FOUND: ${password.green}`);
console.log(`Total ${`${triedPasswordCount}`.bgCyan.black} passwords tried.`);
console.log(`Execution time: ${`${endTime - startTime} ms`.black.bgWhite}`);
return;
}
} catch (err) {
console.error(`Error trying password "${password}":`, err);
}
}
}
console.log("Password not found within the specified range.");
}
bruteForceZip();
The bruteForceZip function is the main function that:
- Starts a loop over the possible password lengths from MIN_LENGTH to MAX_LENGTH.
- Uses the generatePasswords function to create a generator for passwords of each length.
- For each password, it calls tryUnzipWithPassword.
- If the password works, it logs success, the total count of passwords tried, and the execution time, then exits.
- If the password is incorrect, it moves to the next one.
- If no password is found within the specified length and character set, it will log a failure message.
5. Logging Information
The code uses colors to distinguish successful messages (green) and counters (bgCyan.black). This styling makes it easier to interpret logs when monitoring the brute-force process in real time.
Complete code with Single Threading
const { exec } = require('child_process');
const pathTo7zip = require('7zip-bin').path7za;
require('colors');
// Constants for brute force
const CHARSET = 'A0123456789'; // Character set
const MIN_LENGTH = 1; // Minimum length of the password
const MAX_LENGTH = 5; // Maximum length of the password
const ZIP_FILE_PATH = './protected.zip'; // Path to your zip file
let triedPasswordCount = 0;
let startTime, endTime;
/**
* Function to generate all combinations of a given length
* @param {number} length
* @param {string} charset
* @returns {Generator<string>}
*/
function* generatePasswords(length, charset) {
const password = Array(length).fill(0); // Initial indices array
while (true) {
// Convert indices to password
const currentPassword = password.map(i => charset[i]).join('');
yield currentPassword;
// Increment the password
let i = length - 1;
while (i >= 0 && password[i] === charset.length - 1) {
password[i] = 0;
i--;
}
if (i < 0) break; // All combinations are generated
password[i]++;
}
}
/**
* Try to test the file with a given password using 7zip
* @param {string} password
* @returns {Promise<boolean>}
*/
function tryUnzipWithPassword(password) {
console.log(`password: ${password.red}`);
return new Promise((resolve, reject) => {
const command = `"${pathTo7zip}" t "${ZIP_FILE_PATH}" -p"${password}"`;
exec(command, (error, stdout, stderr) => {
triedPasswordCount++;
if (stderr.includes('Wrong password') || stderr.includes('incorrect password')) {
resolve(false); // Incorrect password
} else if (stdout.includes('Everything is Ok')) {
endTime = new Date().getTime();
resolve(true); // Correct password
} else {
reject(stderr); // Some other error occurred
}
});
});
}
/**
* Brute force function to find the password
*/
async function bruteForceZip() {
startTime = new Date().getTime(); // Start time of the brute force process
// Loop through all possible password lengths
for (let length = MIN_LENGTH; length <= MAX_LENGTH; length++) {
const passwordGenerator = generatePasswords(length, CHARSET); // Generator for all possible passwords
// Try each password
for (const password of passwordGenerator) {
try {
const found = await tryUnzipWithPassword(password); // Try to unzip the file with the current password
// If the password is correct, log the result and exit
if (found) {
console.log(`PASSWORD FOUND: ${password.green}`);
console.log(`Total ${`${triedPasswordCount}`.bgCyan.black} passwords tried.`);
console.log(`Execution time: ${`${endTime - startTime} ms`.black.bgWhite}`);
return;
}
} catch (err) {
console.error(`Error trying password "${password}":`, err);
}
}
}
console.log("Password not found within the specified range.");
}
bruteForceZip();
Complete code using Multithreading
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');
const { exec } = require('child_process');
const pathTo7zip = require('7zip-bin').path7za;
require('colors');
// Constants for brute force
const CHARSET = 'A0123456789'; // Character set
const MIN_LENGTH = 1; // Minimum length of the password
const MAX_LENGTH = 5; // Maximum length of the password
const ZIP_FILE_PATH = './protected.zip'; // Path to your zip file
const NUM_WORKERS = 1; // Number of worker threads to use
let triedPasswordCount = 0;
let startTime, endTime;
/**
* Function to generate all combinations of a given length
* @param {number} length
* @param {string} charset
* @param {number} startIndex
* @param {number} endIndex
* @returns {Generator<string>}
*/
function* generatePasswords(length, charset, startIndex, endIndex) {
const password = Array(length).fill(0); // Initial indices array
// Initialize the first character to the startIndex
password[0] = startIndex;
while (true) {
// Convert indices to password
const currentPassword = password.map(i => charset[i]).join('');
// Only yield passwords within the assigned range
if (password[0] > endIndex) break;
yield currentPassword;
// Increment the password
let i = length - 1;
while (i >= 0 && password[i] === charset.length - 1) {
password[i] = 0;
i--;
}
if (i < 0) break; // All combinations are generated
password[i]++;
}
}
/**
* Try to test the file with a given password using 7zip
* @param {string} password
* @returns {Promise<boolean>}
*/
function tryUnzipWithPassword(password) {
console.log(`password: ${password.red}`);
return new Promise((resolve, reject) => {
const command = `"${pathTo7zip}" t "${ZIP_FILE_PATH}" -p"${password}"`;
exec(command, (error, stdout, stderr) => {
triedPasswordCount++;
if (stderr.includes('Wrong password') || stderr.includes('incorrect password')) {
resolve(false); // Incorrect password
} else if (stdout.includes('Everything is Ok')) {
endTime = new Date().getTime();
resolve(true); // Correct password
} else {
reject(stderr); // Some other error occurred
}
});
});
}
if (isMainThread) {
// Main thread: Spawns worker threads and distributes the workload
startTime = new Date().getTime(); // Start time of the brute force process
// Function to split work among worker threads
async function bruteForceZip() {
const rangeSize = Math.ceil(CHARSET.length / NUM_WORKERS); // Split CHARSET range among workers
const workers = [];
// Create workers and assign work ranges
for (let i = 0; i < NUM_WORKERS; i++) {
const startIndex = i * rangeSize;
const endIndex = Math.min(startIndex + rangeSize - 1, CHARSET.length - 1);
workers.push(new Promise((resolve, reject) => {
const worker = new Worker(__filename, {
workerData: { startIndex, endIndex, charset: CHARSET, minLength: MIN_LENGTH, maxLength: MAX_LENGTH }
});
// Listen for messages from worker
worker.on('message', (message) => {
if (message.found) {
endTime = new Date().getTime();
console.log(`PASSWORD FOUND: ${message.password.green}`);
// console.log(`Total ${`${message.triedPasswordCount}`.bgCyan.black} passwords tried.`);
// console.log(`Total ${` ${triedPasswordCount} `.bgCyan.black} passwords tried with average of ${` ${Math.floor(perSecond())} `.bgCyan.black} passwords per second.`);
console.log(`Execution time: ${`${endTime - startTime} ms`.black.bgWhite}`);
process.exit(0);
// resolve(true);
}
});
// Handle worker errors
worker.on('error', reject);
// Worker exits after completing task
worker.on('exit', (code) => {
if (code !== 0) {
reject(new Error(`Worker stopped with exit code ${code}`));
}
});
}));
}
// Wait for all workers to complete
await Promise.all(workers);
console.log("Password not found within the specified range.");
}
bruteForceZip();
} else {
// Worker thread: Executes the brute force logic for a given range of characters
const { startIndex, endIndex, charset, minLength, maxLength } = workerData;
(async () => {
// Loop through all possible password lengths
for (let length = minLength; length <= maxLength; length++) {
const passwordGenerator = generatePasswords(length, charset, startIndex, endIndex); // Generator for all possible passwords in the range
// Try each password
for (const password of passwordGenerator) {
try {
const found = await tryUnzipWithPassword(password); // Try to unzip the file with the current password
// If the password is correct, send the result back to the main thread
if (found) {
parentPort.postMessage({ found: true, password, triedPasswordCount });
return;
}
} catch (err) {
parentPort.postMessage({ found: false, error: err });
}
}
}
// If no password is found within this worker's range, exit
parentPort.postMessage({ found: false, triedPasswordCount });
})();
}
By using worker threads in Node.js, we can leverage multi-threading to speed up brute-force attacks significantly. While brute-forcing passwords can be time-intensive, adding worker threads makes the process faster and more efficient for larger password lists.
Always remember that brute-forcing should only be used ethically and legally, for cases where you own the data or have permission to attempt password recovery.