-
-
Save RandyMcMillan/524a2d81ee8f0df35fcf2b08f0e1222c to your computer and use it in GitHub Desktop.
std_stego.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| use std::fs::File; | |
| use std::io::{Read, Write, BufWriter, BufReader}; | |
| use std::path::Path; | |
| pub struct StdStego { | |
| pub pixels: Vec<u8>, // Raw RGB data | |
| pub width: u32, | |
| pub height: u32, | |
| } | |
| impl StdStego { | |
| /// Create a dummy canvas (PPM format logic: 3 bytes per pixel for RGB) | |
| pub fn new_dummy(width: u32, height: u32, r: u8, g: u8, b: u8) -> Self { | |
| let size = (width * height * 3) as usize; | |
| let mut pixels = Vec::with_capacity(size); | |
| for _ in 0..(width * height) { | |
| pixels.push(r); | |
| pixels.push(g); | |
| pixels.push(b); | |
| } | |
| Self { pixels, width, height } | |
| } | |
| /// Encodes a message into the LSB of a specific channel (0=R, 1=G, 2=B) | |
| pub fn encode_lsb(&mut self, channel_offset: usize, message: &[u8]) { | |
| let mut bit_idx = 0; | |
| let bits: Vec<u8> = message.iter().flat_map(|&byte| { | |
| (0..8).rev().map(move |i| (byte >> i) & 1) | |
| }).collect(); | |
| // Loop through pixels and modify the chosen channel byte | |
| for i in 0..(self.pixels.len() / 3) { | |
| if bit_idx < bits.len() { | |
| let pixel_base = i * 3; | |
| let target_byte = &mut self.pixels[pixel_base + channel_offset]; | |
| // Apply bitmask: Clear LSB, then OR with our bit | |
| *target_byte = (*target_byte & 0xFE) | bits[bit_idx]; | |
| bit_idx += 1; | |
| } else { | |
| break; | |
| } | |
| } | |
| } | |
| /// Saves the image as a .ppm file (standard library only) | |
| pub fn save_ppm<P: AsRef<Path>>(&self, path: P) -> std::io::Result<()> { | |
| let file = File::create(path)?; | |
| let mut writer = BufWriter::new(file); | |
| // PPM Header: P6 means binary RGB, 255 is max color value | |
| let header = format!("P6\n{} {}\n255\n", self.width, self.height); | |
| writer.write_all(header.as_bytes())?; | |
| writer.write_all(&self.pixels)?; | |
| Ok(()) | |
| } | |
| /// Decodes the LSBs and saves a 1-bit "map" (PPM grayscale) | |
| pub fn decode_to_map_ppm<P: AsRef<Path>>(&self, channel_offset: usize, path: P) -> std::io::Result<()> { | |
| let mut map_pixels = Vec::with_capacity(self.pixels.len()); | |
| for i in 0..(self.pixels.len() / 3) { | |
| let lsb = self.pixels[i * 3 + channel_offset] & 1; | |
| let val = if lsb == 1 { 255 } else { 0 }; | |
| // Grayscale PPM uses 3 identical bytes per pixel | |
| map_pixels.push(val); map_pixels.push(val); map_pixels.push(val); | |
| } | |
| let mut stego_map = StdStego { pixels: map_pixels, width: self.width, height: self.height }; | |
| stego_map.save_ppm(path) | |
| } | |
| } | |
| fn main() -> std::io::Result<()> { | |
| // 1. New 100x100 "Kalshi Cyan" (R:0, G:255, B:255) | |
| let mut stego = StdStego::new_dummy(100, 100, 0, 255, 255); | |
| // 2. Hide payload | |
| let secret = b"BIP-64MOD"; | |
| stego.encode_lsb(2, secret); // Hide in Blue channel | |
| // 3. Save as PPM (Viewable in GIMP, Photoshop, or via 'feh') | |
| stego.save_ppm("stego.ppm")?; | |
| // 4. Reveal | |
| stego.decode_to_map_ppm(2, "revealed.ppm")?; | |
| println!("PPM-based steganography (std-only) complete."); | |
| Ok(()) | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=97a64bc648fef5d0211cc302d4b7ccb1