-
-
Save rust-play/97a64bc648fef5d0211cc302d4b7ccb1 to your computer and use it in GitHub Desktop.
Code shared from the Rust Playground
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(()) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment