This commit is contained in:
2024-11-18 17:58:13 +11:00
parent 279664fc84
commit 702447b8d1
6 changed files with 223 additions and 171 deletions

View File

@@ -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__":

View File

@@ -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<String> {
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<String> {
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<String> = std::env::args().collect();
if args.len() != 4 {
eprintln!("Usage: {} <real_file> <fake_file> <num_chars>", 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<u64> = (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;
}
}

View File

@@ -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"

30
rust/confession_fake.txt Normal file
View File

@@ -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.

22
rust/confession_real.txt Normal file
View File

@@ -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.

124
rust/src/main.rs Normal file
View File

@@ -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<Vec<String>> {
let fake_modified: Vec<String> = 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<String> = env::args().collect();
if args.len() != 4 {
eprintln!("Usage: {} <real_file> <fake_file> <num_chars>", 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<String> = 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<String> = 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<u64> = (hash_counter..hash_counter + batch_size as u64).collect();
// Process the batch in parallel
let results: Vec<Option<Vec<String>>> = 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();
}
}