diff --git a/enforcer.py b/enforcer.py index ad5a319..926af9f 100644 --- a/enforcer.py +++ b/enforcer.py @@ -1,83 +1,81 @@ -import sys -import time from multiprocessing import Pool from hashlib import sha256 +import time +import sys -# Function to calculate the hash for the given modified file content -def calculate_hash(file_lines, num_chars): - return sha256("\n".join(file_lines).encode()).hexdigest()[-num_chars:] +# Function to calculate the hash for the given file content with proper newline handling +def calculate_hash(file_lines_with_newline, num_chars): + # Join file lines with '\n' to ensure correct structure + file_content = "".join(file_lines_with_newline) + return sha256(file_content.encode()).hexdigest()[-num_chars:] -# Function to generate a modified file by adding spaces based on the bit pattern +# Function to generate a modified fake file by adding spaces based on the bit pattern def modify_and_hash(args): - real_og, fake_og, num_chars, bit_pattern = args + fake_og, num_chars, bit_pattern, real_hash = args - # Modify the real and fake files based on the bit pattern - real_modified = [ - line + " " * ((bit_pattern >> idx) & 1) - for idx, line in enumerate(real_og) - ] + # Modify the fake file based on the bit pattern fake_modified = [ - line + " " * ((bit_pattern >> idx) & 1) + line.rstrip() + " " * ((bit_pattern >> idx) & 1) + "\n" for idx, line in enumerate(fake_og) ] - # Calculate hashes for both modified files - real_hash = calculate_hash(real_modified, num_chars) + # Calculate the hash for the modified fake file fake_hash = calculate_hash(fake_modified, num_chars) - return (real_hash, fake_hash, real_modified, fake_modified) + # Return if the hashes match + return (fake_hash == real_hash, fake_hash, fake_modified) def main(real_file, fake_file, num_chars): - # Read the original files - with open(real_file) as f: - real_og = f.read().splitlines() - with open(fake_file) as f: - fake_og = f.read().splitlines() + # Read the original real file and retain newline characters + with open(real_file, 'r') as f: + real_og = f.readlines() # This keeps newlines intact + + # Read the original fake file and retain newline characters + with open(fake_file, 'r') as f: + fake_og = f.readlines() # This keeps newlines intact - all_real_hashes = {} - all_fake_hashes = {} + # Calculate the hash of the real file (unmodified) + real_hash = calculate_hash(real_og, num_chars) + print(f"Real file hash: {real_hash}") - found_collision = False - total_hashes = 0 - batch_size = 100 # Number of combinations to process in parallel - start_time = time.time() # Start time to measure hashes per second + hash_counter = 0 # To track the number of hashes calculated + start_time = time.time() # Start the timer # Use multiprocessing Pool with Pool() as pool: i = 0 + batch_size = 100 + found_collision = False + while not found_collision: # Prepare a batch of bit patterns to process in parallel - bit_patterns = [(real_og, fake_og, num_chars, pattern) for pattern in range(i, i + batch_size)] + bit_patterns = [(fake_og, num_chars, pattern, real_hash) for pattern in range(i, i + batch_size)] # Process the batch in parallel results = pool.map(modify_and_hash, bit_patterns) - # Update the total count of hashes processed - total_hashes += len(results) + # Update the hash counter + hash_counter += batch_size - # Check the results for a hash collision - for real_hash, fake_hash, real_modified, fake_modified in results: - all_real_hashes[real_hash] = real_modified - all_fake_hashes[fake_hash] = fake_modified + # Display progress on the same line + elapsed_time = time.time() - start_time + sys.stdout.write(f"\rProcessed {hash_counter} hashes in {elapsed_time:.2f} seconds.") + sys.stdout.flush() - if real_hash in all_fake_hashes or fake_hash in all_real_hashes: - collision_hash = real_hash if real_hash in all_fake_hashes else fake_hash - print(f"\n[+] Collision found! {real_file}.out and {fake_file}.out have the same hash: {collision_hash}") + # Check the results to see if a collision was found + for match, fake_hash, fake_modified in results: + if match: + elapsed_time = time.time() - start_time + print(f"\nCollision found! The fake file's hash matches the real file's hash: {real_hash}") + print(f"Total hashes processed: {hash_counter} in {elapsed_time:.2f} seconds.") - with open(f"{real_file}.out", 'w') as f_out: - f_out.writelines("\n".join(all_real_hashes[collision_hash])) + # Write the modified fake file to the output with open(f"{fake_file}.out", 'w') as f_out: - f_out.writelines("\n".join(all_fake_hashes[collision_hash])) - + f_out.writelines(fake_modified) + found_collision = True break - # Update progress every batch - elapsed_time = time.time() - start_time - hashes_per_sec = total_hashes / elapsed_time - print(f"\rProcessed {total_hashes} hashes in {elapsed_time:.2f} seconds, {hashes_per_sec:.2f} H/s", end='') - - # Increment the bit pattern range i += batch_size if __name__ == "__main__": diff --git a/hash_collision/src/main.rs b/hash_collision/src/main.rs deleted file mode 100644 index 2c8e06f..0000000 --- a/hash_collision/src/main.rs +++ /dev/null @@ -1,122 +0,0 @@ -use rayon::prelude::*; -use sha2::{Digest, Sha256}; -use std::fs::{self, File}; -use std::io::{BufRead, BufReader}; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Arc}; -use std::time::Instant; - -// Function to calculate the hash for the given file content -fn calculate_hash(file_content: &[String], num_chars: usize) -> String { - let concatenated = file_content.concat(); // Concatenate without joining with newlines - let hash = Sha256::digest(concatenated.as_bytes()); - format!("{:x}", hash)[64 - num_chars..].to_string() -} - -// Function to generate a modified file by adding spaces based on the bit pattern -fn modify_fake_file(fake_og: &[String], bit_pattern: u64) -> Vec { - fake_og - .iter() - .enumerate() - .map(|(idx, line)| format!("{}{}", line, " ".repeat(((bit_pattern >> idx) & 1) as usize))) - .collect() -} - -// Function to read a file while preserving line endings -fn read_file_preserving_newlines(file_path: &str) -> Vec { - let file = File::open(file_path).expect("Failed to open file"); - let reader = BufReader::new(file); - - reader - .lines() - .map(|line| { - let mut line = line.expect("Failed to read line"); - line.push('\n'); // Ensure the newline is preserved - line - }) - .collect() -} - -fn main() { - // Command line arguments - let args: Vec = std::env::args().collect(); - if args.len() != 4 { - eprintln!("Usage: {} ", args[0]); - std::process::exit(1); - } - - let real_file = &args[1]; - let fake_file = &args[2]; - let num_chars: usize = args[3].parse().expect("Invalid number of characters"); - - // Read the original files while preserving line endings - let real_og = read_file_preserving_newlines(real_file); - let fake_og = read_file_preserving_newlines(fake_file); - - // Calculate the target hash of the original real file - let target_real_hash = calculate_hash(&real_og, num_chars); - println!("Target hash (real file): {}", target_real_hash); - - // Atomic flag to stop threads once a hash match is found - let found_collision = Arc::new(AtomicBool::new(false)); - - let mut total_hashes = 0; - let batch_size = 10_000; - let start_time = Instant::now(); - - // Main loop to modify the fake file until its hash matches the real file's hash - let mut i = 0; - while !found_collision.load(Ordering::SeqCst) { - let bit_patterns: Vec = (i..i + batch_size).collect(); - - // Parallel processing using Rayon - bit_patterns.into_par_iter().for_each(|bit_pattern| { - if found_collision.load(Ordering::SeqCst) { - return; // Stop processing if a collision is found - } - - // Modify the fake file based on the current bit pattern - let fake_modified = modify_fake_file(&fake_og, bit_pattern); - - // Calculate the hash of the modified fake file - let fake_hash = calculate_hash(&fake_modified, num_chars); - - // Check if the modified fake file's hash matches the real file's hash - if fake_hash == target_real_hash { - // Print collision message - println!( - "\n[+] Collision found! The modified fake file matches the hash of the real file: {}", - target_real_hash - ); - - // Write the real and fake output files - let real_output = format!("{}.out", real_file); - let fake_output = format!("{}.out", fake_file); - - // Write the real output (original real file) - fs::write(&real_output, real_og.concat()) - .expect("Failed to write real output file"); - - // Write the fake output (modified fake file) - fs::write(&fake_output, fake_modified.concat()) - .expect("Failed to write fake output file"); - - // Set the flag to stop further processing - found_collision.store(true, Ordering::SeqCst); - return; // Stop further processing - } - }); - - total_hashes += batch_size; - - // Update progress - let elapsed = start_time.elapsed(); - let hashes_per_sec = total_hashes as f64 / elapsed.as_secs_f64(); - print!( - "\rProcessed {} hashes in {:.2?}, {:.2} H/s", - total_hashes, elapsed, hashes_per_sec - ); - - i += batch_size; - } -} diff --git a/hash_collision/Cargo.toml b/rust/Cargo.toml similarity index 67% rename from hash_collision/Cargo.toml rename to rust/Cargo.toml index 09c3385..0714d70 100644 --- a/hash_collision/Cargo.toml +++ b/rust/Cargo.toml @@ -1,9 +1,9 @@ [package] -name = "hash_collision" +name = "rust" version = "0.1.0" edition = "2021" [dependencies] rayon = "1.5" -sha2 = "0.9" +sha2 = "0.10" diff --git a/rust/confession_fake.txt b/rust/confession_fake.txt new file mode 100644 index 0000000..ce68fe6 --- /dev/null +++ b/rust/confession_fake.txt @@ -0,0 +1,30 @@ +This is the secret confession of Richard Buckland +to be revealed by anonymous email if I should +mysteriously vanish. I have left the last few hex +digits of the SHA256 hash of this message with my +trusted solicitor, Dennis Denuto, which will verify +that this is indeed my intended and unaltered +confession written by me Richard Buckland. + +Dennis has not seen this confession he has only seen +the last few digits of the hash. I have also sent copies +of the last few digits to my bank manager and to my priest +Father Brown. + +On the 10th of February I saw Mark Zukerberg near my +house and we struck up a conversation. He explained all +the things he was doing to ensure that Facebook respects +privacy - both of its users and of others. It was very +impressive. + +I feel awful that I have been criticising Facebook publicly +for so long. I apologised to him in our conversation and +now I want to confess to the world that actually Facebook +has more than enough privacy features, and that the reason +I spend so much time criticising Facebook is that I am +envious of Mark and wish I was a clever and smart and wise +as he is. I feel so bad for having been so mean to him for +so many years that I am considering retreating to the outback. +I may well cut off all contact with the world and live as a +hermit from now on. So do not worry if I vanish it is just +that I feel so guilty that I have been so unfair to Facebook. diff --git a/rust/confession_real.txt b/rust/confession_real.txt new file mode 100644 index 0000000..73f0654 --- /dev/null +++ b/rust/confession_real.txt @@ -0,0 +1,22 @@ +This is the secret confession of Richard Buckland +to be revealed by anonymous email if I should +mysteriously vanish. I have left the last few hex +digits of the SHA256 hash of this message with my +trusted solicitor, Dennis Denuto, which will verify +that this is indeed my intended and unaltered +confession written by me Richard Buckland. + +Dennis has not seen this confession he has only seen +the last few digits of the hash. I have also sent copies +of the last few digits to my bank manager and to my priest +Father Brown. + +On the 10th of February I saw Mark Zukerberg peeping +through my window and recording my private and personal +conversation with my friend. + +I confronted him and he was very embarrassed. He +promised to pay me $1 million a year if I would stay +silent and not tell anyone I had seen him do this. I +agreed but now I worry that it would be cheaper for him +to make me vanish than to keep paying me. diff --git a/rust/src/main.rs b/rust/src/main.rs new file mode 100644 index 0000000..b9a6d32 --- /dev/null +++ b/rust/src/main.rs @@ -0,0 +1,124 @@ +use rayon::prelude::*; +use sha2::{Digest, Sha256}; +use std::fs; +use std::io::Write; +use std::time::Instant; +use std::env; + +// Function to calculate the hash for the given file content +fn calculate_hash(file_lines: &[String], num_chars: usize) -> String { + let content = file_lines.join(""); // Join lines with no separator (newlines are preserved) + let mut hasher = Sha256::new(); + hasher.update(content.as_bytes()); + let result = hasher.finalize(); + format!("{:x}", result)[64 - num_chars..].to_string() // Get the last `num_chars` hex digits +} + +// Function to generate a modified fake file by adding spaces based on the bit pattern +fn modify_and_hash( + fake_og: &[String], + num_chars: usize, + bit_pattern: u64, + real_hash: &str, +) -> Option> { + let fake_modified: Vec = fake_og + .iter() + .enumerate() + .map(|(idx, line)| { + let trimmed_line = line.trim_end(); // Remove trailing whitespace + let spaces_to_add = if (bit_pattern >> idx) & 1 == 1 { " " } else { "" }; + format!("{}{}\n", trimmed_line, spaces_to_add) + }) + .collect(); + + // Calculate the hash for the modified fake file + let fake_hash = calculate_hash(&fake_modified, num_chars); + + // Return the modified file if the hash matches + if fake_hash == real_hash { + Some(fake_modified) + } else { + None + } +} + +fn main() { + // Get command line arguments + let args: Vec = env::args().collect(); + if args.len() != 4 { + eprintln!("Usage: {} ", args[0]); + std::process::exit(1); + } + + let real_file = &args[1]; + let fake_file = &args[2]; + let num_chars: usize = args[3].parse().expect("Invalid number of characters"); + + // Read the real and fake files + let real_og: Vec = fs::read_to_string(real_file) + .expect("Failed to read real file") + .lines() + .map(|line| format!("{}\n", line)) // Preserve newlines + .collect(); + + let fake_og: Vec = fs::read_to_string(fake_file) + .expect("Failed to read fake file") + .lines() + .map(|line| format!("{}\n", line)) // Preserve newlines + .collect(); + + // Calculate the hash of the real file (unmodified) + let real_hash = calculate_hash(&real_og, num_chars); + println!("Real file hash: {}", real_hash); + + let mut found_collision = false; + let mut hash_counter = 0; + let start_time = Instant::now(); // Start the timer + + // Search for a matching hash in parallel + while !found_collision { + // Generate a batch of bit patterns + let batch_size = 100; + let bit_patterns: Vec = (hash_counter..hash_counter + batch_size as u64).collect(); + + // Process the batch in parallel + let results: Vec>> = bit_patterns + .par_iter() + .map(|&pattern| modify_and_hash(&fake_og, num_chars, pattern, &real_hash)) + .collect(); + + // Check if a collision was found + for result in results { + if let Some(fake_modified) = result { + let elapsed_time = start_time.elapsed(); + println!( + "\nCollision found! The fake file's hash matches the real file's hash: {}", + real_hash + ); + println!( + "Total hashes processed: {} in {:.2?} seconds.", + hash_counter, elapsed_time + ); + + // Write the modified fake file to the output + let out_file = format!("{}.out", fake_file); + let mut file = fs::File::create(out_file).expect("Failed to create output file"); + for line in fake_modified { + file.write_all(line.as_bytes()).expect("Failed to write to file"); + } + + found_collision = true; + break; + } + } + + // Update the hash counter + hash_counter += batch_size; + let elapsed_time = start_time.elapsed(); + print!( + "\rProcessed {} hashes in {:.2?} seconds.", + hash_counter, elapsed_time + ); + std::io::stdout().flush().unwrap(); + } +}