diff --git a/.gitignore b/.gitignore index d01bd1a..7434c91 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,6 @@ Cargo.lock # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ \ No newline at end of file +#.idea/ + +*.bin \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 084100b..135b75e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,4 +4,5 @@ version = "0.1.0" edition = "2021" [dependencies] +chrono = "0.4.39" opencv = "0.93.5" diff --git a/README.md b/README.md index 8440bdb..e23681d 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,50 @@ # ocelli: Camera-Based TRNG -**Ocelli** is a Rust application that uses a camera to generate high-quality random entropy by analyzing pixel intensity differences between consecutive frames. +**Ocelli** is a Rust application that generates high-quality entropy using a camera feed. The application supports two entropy generation methods (`get_entropy` and `chop_and_stack`) and optionally applies Van Neumann whitening for enhanced randomness. Generated entropy is saved as a binary file. ## Features -- **Camera-Based Entropy**: Leverages grayscale pixel intensity differences to produce entropy bits. -- **Shannon Entropy Validation**: Filters entropy data to ensure high randomness quality. -- **Van Neumann Whitening**: Optional bias removal for unbiased entropy output. -- **Dynamic Frame Handling**: Automatically captures frames based on resolution and entropy requirements. - -## Requirements - -- OpenCV (with Rust bindings) +- **Entropy Methods**: + - `get_entropy`: Compares pixel values between frames. + - `chop_and_stack`: Combines and processes pixel data with reversed rows. +- **Van Neumann Whitening**: Optional, enabled via the `-w` flag. +- **Shannon Entropy Test**: Ensures the randomness quality of generated entropy. ## Usage -Run the program with the desired parameters: - ```bash -cargo run -- [-w] -``` -### Parameters: -- ``: Number of bytes of entropy to generate. -- ``: Camera resolution width. -- ``: Camera resolution height. -- `-w`: (Optional) Enable Van Neumann whitening for unbiased entropy. - -### Example: -Generate 1000 bytes of entropy at 1920x1080 resolution with whitening enabled: -```bash -cargo run -- 1000 1920 1080 -w +cargo run --release -- [-w] ``` ---- +### Example + +```bash +cargo run --release -- 1024 640 480 -w +``` + +This generates 1024 bytes of whitened entropy using a 640x480 resolution. + +## Requirements + +- OpenCV 4.x + +## Installation + +1. Install Rust: https://www.rust-lang.org/tools/install +2. Install OpenCV: Follow the [official guide](https://docs.opencv.org/). +3. Clone the repository: + ```bash + git clone + cd ocelli-entropy-generator + ``` +4. Run the application: + ```bash + cargo run --release -- + ``` ## Output -- **Hexadecimal Entropy**: Generated entropy is output as a hexadecimal string. -- **Processing Time**: Reports how long the entropy generation took. -- **Shannon Entropy**: Displays the Shannon entropy of the final output. +Generated entropy files are saved in the current directory with a name format: +``` +[_whitened]_YYYYMMDD_HHMMSS.bin +``` \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index e68041d..aeb4393 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,9 @@ use std::collections::HashSet; use std::collections::HashMap; use std::env; use std::time::Instant; +use std::fs::File; +use std::io::Write; +use chrono::Local; struct Ocelli; @@ -38,6 +41,43 @@ impl Ocelli { entropy } + /// Use the chop & stack method on an array of grayscale values + fn chop_and_stack(&self, mut data: Vec) -> Vec { + // Ensure the input length is divisible by 4 by trimming excess bytes + let remainder = data.len() % 4; + if remainder != 0 { + data.truncate(data.len() - remainder); + } + + // Determine the length of each fold + let fold_len = data.len() / 4; + + // Split the data into four folds + let mut folds: Vec> = data + .chunks(fold_len) + .map(|chunk| chunk.to_vec()) + .collect(); + + // Reverse the second and fourth folds + if folds.len() > 1 { + folds[1].reverse(); // Reverse the second row + } + if folds.len() > 3 { + folds[3].reverse(); // Reverse the fourth row + } + + // Combine the folds by summing corresponding elements and applying modulo 256 + let mut combined: Vec = vec![0u8; fold_len]; + for i in 0..fold_len { + combined[i] = ((folds[0][i] as u16 + + folds[1][i] as u16 + + folds[2][i] as u16 + + folds[3][i] as u16) % 256) as u8; + } + + combined + } + /// Apply Van Neumann whitening to a vector of entropy bits fn whiten(&self, entropy: &Vec) -> Vec { let mut whitened_entropy = Vec::new(); @@ -90,17 +130,6 @@ impl Ocelli { }) } - /// Calculates the required number of frames for the desired entropy bytes - fn required_frames(&self, bytes: usize, width: usize, height: usize) -> usize { - let max_bytes = width * height / 8; - - if max_bytes == 0 { - panic!("Invalid resolution: too few pixels to generate entropy."); - } - - (bytes + max_bytes - 1) / max_bytes + 1 - } - /// Determines if the camera is covered based on the unique grayscale values fn is_covered(&self, grayscale: &[u8], threshold: usize) -> bool { let unique_values: HashSet<_> = grayscale.iter().copied().collect(); @@ -115,7 +144,6 @@ fn main() -> opencv::Result<()> { std::process::exit(1); } - // Check if the whitening flag is set let whiten_flag = args.contains(&String::from("-w")); let length: usize = args[1].parse().expect("Failed to parse entropy length as a number"); @@ -127,7 +155,6 @@ fn main() -> opencv::Result<()> { panic!("Failed to open the camera"); } - // Set camera resolution cam.set(opencv::videoio::CAP_PROP_FRAME_WIDTH, width as f64)?; cam.set(opencv::videoio::CAP_PROP_FRAME_HEIGHT, height as f64)?; @@ -140,25 +167,24 @@ fn main() -> opencv::Result<()> { cam.read(&mut frame)?; let mut gray_frame = core::Mat::default(); imgproc::cvt_color(&frame, &mut gray_frame, imgproc::COLOR_BGR2GRAY, 0)?; - let grayscale_data = gray_frame.data_bytes().expect("Failed to get grayscale data"); + let grayscale_data = gray_frame.data_bytes().expect("Failed to get grayscale data").to_vec(); - if !ocelli.is_covered(grayscale_data, 50) { - println!("Camera is not covered. Stopping..."); - // return Ok(()); - } + let uncovered = !ocelli.is_covered(&grayscale_data, 50); - // Start timing - let start_time = Instant::now(); + println!( + "Starting entropy generation... Using {}", + if uncovered { + "chop_and_stack" + } else { + "get_entropy" + } + ); - // Calculate required frames - let required_frames = ocelli.required_frames(length, width, height); - println!("Capturing {} frames to generate {} bytes of entropy...", required_frames, length); - - // Generate entropy let mut total_entropy = Vec::new(); let shannon_threshold = 4.0; + let mut previous_frame_data = grayscale_data.clone(); - let mut previous_frame_data = grayscale_data.to_vec(); + let start_time = Instant::now(); while total_entropy.len() < length { cam.read(&mut frame)?; @@ -166,42 +192,71 @@ fn main() -> opencv::Result<()> { imgproc::cvt_color(&frame, &mut gray_frame, imgproc::COLOR_BGR2GRAY, 0)?; let current_frame_data = gray_frame.data_bytes().expect("Failed to get grayscale data").to_vec(); - let entropy = ocelli.get_entropy(¤t_frame_data, &previous_frame_data); - let shannon_entropy = ocelli.shannon(&entropy); + let mut entropy: Vec = Vec::new(); + let mut shannon_entropy = 0.0; + + if uncovered { + // Process with chop_and_stack + entropy = ocelli.chop_and_stack(current_frame_data.clone()); + + } else { + // Process with get_entropy + entropy = ocelli.get_entropy(¤t_frame_data, &previous_frame_data); + + previous_frame_data = current_frame_data; + } + + if whiten_flag { + entropy = ocelli.whiten(&entropy); + } + + shannon_entropy = ocelli.shannon(&entropy); if shannon_entropy >= shannon_threshold { + total_entropy.extend(entropy); + } else { println!( - "Rejected entropy array (Shannon entropy: {:.3}). Retrying...", + "Rejected stacked entropy array (Shannon entropy: {:.3}). Retrying...", shannon_entropy ); } - previous_frame_data = current_frame_data; - } - - if whiten_flag { - total_entropy = ocelli.whiten(&total_entropy); + println!( + "Collected {} of {} bytes of entropy...", + total_entropy.len(), + length + ); } // Convert entropy to hex string - let entropy_hex = total_entropy - .iter() - .take(length) - .map(|byte| format!("{:02x}", byte)) - .collect::(); + // let entropy_hex = total_entropy + // .iter() + // .take(length) + // .map(|byte| format!("{:02x}", byte)) + // .collect::(); + // println!("Generated entropy (hex): {}", entropy_hex); - println!("Generated entropy (hex): {}", entropy_hex); - let total_shannon_entropy = ocelli.shannon(&total_entropy); - - // End timing + let elapsed_time = start_time.elapsed(); println!( "Process completed in {:.3} seconds.\nShannon Entropy {:.3}.", - elapsed_time.as_secs_f64(), total_shannon_entropy + elapsed_time.as_secs_f64(), + total_shannon_entropy ); + // Save the generated entropy to a binary file + let timestamp = Local::now().format("%Y%m%d_%H%M%S").to_string(); + let method = if uncovered { "chop_and_stack" } else { "get_entropy" }; + let whitened = if whiten_flag { "_whitened" } else { "" }; + let filename = format!("{}{}_{}.bin", method, whitened, timestamp); + + let mut file = File::create(&filename).expect("Failed to create file"); + file.write_all(&total_entropy).expect("Failed to write data to file"); + + println!("Entropy saved to file: {}", filename); + Ok(()) } \ No newline at end of file