Skip to content

Instantly share code, notes, and snippets.

@rust-play
Created May 8, 2026 18:39
Show Gist options
  • Select an option

  • Save rust-play/97a64bc648fef5d0211cc302d4b7ccb1 to your computer and use it in GitHub Desktop.

Select an option

Save rust-play/97a64bc648fef5d0211cc302d4b7ccb1 to your computer and use it in GitHub Desktop.
Code shared from the Rust Playground
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