Skip to content

Commit

Permalink
Refactor lib functions
Browse files Browse the repository at this point in the history
  • Loading branch information
lhvy committed Nov 4, 2023
1 parent dfc00c5 commit 387ba22
Show file tree
Hide file tree
Showing 6 changed files with 291 additions and 292 deletions.
5 changes: 2 additions & 3 deletions src/bin/bench.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use rand::RngCore;
use sneaky::{lsb_raw_decode, lsb_raw_encode};

fn main() {
let mut payload = vec![0u8; 1000 * 1000];
Expand All @@ -16,7 +15,7 @@ fn main() {
let time = std::time::Instant::now();
for _ in 0..TESTS {
#[allow(clippy::unit_arg)]
std::hint::black_box(lsb_raw_encode(&payload, &mut carrier, n));
std::hint::black_box(sneaky::lsb::raw_encode(&payload, &mut carrier, n));
}
let elapsed = time.elapsed().as_secs_f64();
let processed = payload.len() as f64 * TESTS as f64 / (1000.0 * 1000.0);
Expand All @@ -27,7 +26,7 @@ fn main() {
for n in 1..=8 {
let time = std::time::Instant::now();
for _ in 0..TESTS {
std::hint::black_box(lsb_raw_decode(&carrier, n));
std::hint::black_box(sneaky::lsb::raw_decode(&carrier, n));
}
let elapsed = time.elapsed().as_secs_f64();
let processed = payload.len() as f64 * TESTS as f64 / (1000.0 * 1000.0);
Expand Down
4 changes: 2 additions & 2 deletions src/cipher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ use std::str;
// }
// }

pub(crate) fn string_rot(message: &mut str, n: u8) {
pub fn string_rot(message: &mut str, n: u8) {
alphabetic_rot(unsafe { message.as_bytes_mut() }, n);
debug_assert!(str::from_utf8(message.as_bytes()).is_ok());
}

pub(crate) fn alphabetic_rot(bytes: &mut [u8], n: u8) {
pub fn alphabetic_rot(bytes: &mut [u8], n: u8) {
for byte in bytes {
if byte.is_ascii_alphabetic() {
if byte.is_ascii_uppercase() {
Expand Down
282 changes: 3 additions & 279 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,279 +1,3 @@
use image::ImageBuffer;
use rand::seq::SliceRandom;
use rand::SeedableRng;

pub fn lsb_encode(message: String, mut image: image::RgbImage, n_bits: u8, key: Option<u64>) {
// Convert the message to a vector of bytes
let mut message = message.as_bytes().to_vec();
// Add another byte for optimisation purposes (required or data will be lost 💀)
message.push(0);

// Ensure that the message will fit in the image
let max_bytes = (image.width() * image.height() * 3) / (8 / n_bits) as u32 - 4;
if message.len() > max_bytes as usize {
println!(
"Message is too long to fit in the image. Maximum message length is {} bytes, including the null byte",
max_bytes
);
return;
}

let flat_image = &mut image.as_flat_samples_mut();
let carrier = flat_image.as_mut_slice();

match key {
Some(key) => {
let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(key);

let mut index_map = (0..carrier.len()).collect::<Vec<_>>();
index_map.shuffle(&mut rng);

let mut shuffled_carrier = Vec::with_capacity(carrier.len());
for i in &index_map {
shuffled_carrier.push(carrier[*i]);
}

lsb_raw_encode(&message, &mut shuffled_carrier, n_bits as usize);

for (i, shuffled_carrier) in index_map.into_iter().zip(shuffled_carrier) {
carrier[i] = shuffled_carrier;
}
}
None => {
lsb_raw_encode(&message, carrier, n_bits as usize);
}
}

// Save the image
image.save("out.png").unwrap();
println!("Saved image to ./out.png");
}

pub fn lsb_raw_encode(payload: &[u8], carrier: &mut [u8], n_bits: usize) {
let len = u32::try_from(payload.len() - 1).unwrap();

// Write the length of the message to the LSB of the first 32 bytes
for (i, byte) in carrier[..32].iter_mut().enumerate() {
let bit = (len >> i) & 1;
*byte = (*byte & 0xFE) | bit as u8;
}

let len = len as usize;

let mut payload_bit_index: usize = 0;
for carrier_byte in &mut carrier[32..] {
if payload_bit_index >= len * 8 {
break;
}

let payload_byte_index = payload_bit_index / 8;
let offset = payload_bit_index % 8;
let payload_bytes = unsafe {
(*payload.get_unchecked(payload_byte_index) as usize) << 8
| (*payload.get_unchecked(payload_byte_index + 1) as usize)
};

let mask = (1 << n_bits) - 1;
let shift_amount = 16 - n_bits - offset;

let payload_bits = (payload_bytes >> shift_amount) & mask;
payload_bit_index += n_bits;

let carrier_bits = *carrier_byte as usize & (0xFF << n_bits);
*carrier_byte = (carrier_bits | payload_bits) as u8;
}
}

pub fn lsb_decode(image: image::RgbImage, n_bits: u8, key: Option<u64>) {
// Decode the message from the image
let message = match key {
Some(key) => {
let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(key);

let flat_image = image.as_flat_samples();
let mut carrier = flat_image.as_slice().to_vec();

carrier.shuffle(&mut rng);

lsb_raw_decode(&carrier, n_bits as usize)
}
None => lsb_raw_decode(image.as_flat_samples().as_slice(), n_bits as usize),
};

// Convert the message to a string
let message = String::from_utf8(message).unwrap();
println!("Message: {}", message);
}

pub fn lsb_raw_decode(carrier: &[u8], n_bits: usize) -> Vec<u8> {
// Read the length of the message from the LSB of the first 32 bytes
let mut len = 0;
for (i, byte) in carrier[..32].iter().enumerate() {
let bit = (*byte & 1) as usize;
len |= bit << i;
}

// Decode the message from the image
let mut payload = Vec::with_capacity(len);
let mut byte = 0;
let mut bits_read = 0;

for carrier_byte in &carrier[32..] {
// Extract n_bits from the image, but don't go past the end of the byte
let bits_to_extract = n_bits.min(8 - bits_read);

// Create a mask to extract the bits
let mask = (1_usize << bits_to_extract) - 1;
let extracted_bits = (*carrier_byte as usize) >> (n_bits - bits_to_extract) & mask;

// Write the bits to the message
byte = (byte << bits_to_extract) | extracted_bits;
bits_read += bits_to_extract;

if bits_read == 8 {
payload.push(byte as u8);
if payload.len() == len {
break;
}
byte = 0;
bits_read = 0;
}

// Read the remaining bits for the next byte
if bits_to_extract < n_bits {
bits_read = n_bits - bits_to_extract;
let mask = (1_usize << bits_read) - 1;
byte = (*carrier_byte as usize) & mask;
}
}

payload
}

pub fn extract_bit_planes(image: image::RgbImage) {
let mut planes = vec![ImageBuffer::new(image.width(), image.height()); 24].into_boxed_slice();
let mut plane_pixels = planes
.iter_mut()
.map(|p| p.pixels_mut())
.collect::<Vec<_>>();
for src in image.pixels() {
for channel in 0..3 {
for bit in 0..8 {
let dst = plane_pixels[channel * 8 + bit].next().unwrap();
*dst = if src[channel] & (1 << bit) == 0 {
image::Luma([255_u8])
} else {
image::Luma([0])
};
}
}
}

// Save into output folder, labelled R, G, B 0-7
// Folder first
std::fs::create_dir_all("output").unwrap();
// Then save the images
for (i, plane) in planes.iter().enumerate() {
let color = match i / 8 {
0 => "R",
1 => "G",
2 => "B",
_ => unreachable!(),
};
plane
.save(format!("output/{}{}.png", color, i % 8))
.unwrap();
}
println!("Saved images to ./output");
}

pub fn bytes_to_wav(bytes: &[u8]) {
let spec = hound::WavSpec {
channels: 1,
sample_rate: 44100,
bits_per_sample: 16,
sample_format: hound::SampleFormat::Int,
};

let mut writer = hound::WavWriter::create("out.wav", spec).unwrap();

for byte in bytes {
for i in 0..8 {
let bit = (byte >> (7 - i)) & 1;
for i in (1..i16::MAX).step_by(32) {
match bit {
0 => {
writer.write_sample(-i).unwrap();
writer.write_sample(i).unwrap();
}
1 => {
writer.write_sample(1).unwrap();
writer.write_sample(1).unwrap();
}
_ => unreachable!(),
}
}
for _ in 0..1 {
match bit {
0 => {
writer.write_sample(i16::MIN).unwrap();
writer.write_sample(i16::MAX).unwrap();
}
1 => {
writer.write_sample(0).unwrap();
writer.write_sample(0).unwrap();
}
_ => unreachable!(),
}
}
for i in (1..i16::MAX).rev().step_by(32) {
match bit {
0 => {
writer.write_sample(-i).unwrap();
writer.write_sample(i).unwrap();
}
1 => {
writer.write_sample(1).unwrap();
writer.write_sample(1).unwrap();
}
_ => unreachable!(),
}
}
}
}
}

pub fn wav_to_bytes(bytes: &[u8]) -> Vec<u8> {
let mut reader = hound::WavReader::new(bytes).unwrap();

let mut bytes = Vec::new();
let mut byte = 0;
let mut bits_read = 0;
// Loop over sets of 4 samples and detect 0 or 1
for chunk in reader
.samples::<i16>()
.collect::<Result<Vec<i16>, _>>()
.unwrap()
.chunks(2)
{
match chunk {
[i16::MIN, i16::MAX] => {
byte <<= 1;
bits_read += 1;
}
[0, 0] => {
byte = (byte << 1) | 1;
bits_read += 1;
}
_ => {}
}

if bits_read == 8 {
bytes.push(byte);
byte = 0;
bits_read = 0;
}
}

bytes
}
pub mod cipher;
pub mod lsb;
pub mod wav;
Loading

0 comments on commit 387ba22

Please sign in to comment.