Hamming code steganography

⚓ Rust    📅 2026-05-29    👤 surdeus    👁️ 1      

surdeus

Info

This post is auto-generated from RSS feed The Rust Programming Language Forum - Latest topics. Source: Hamming code steganography

Here is an implementation of steganography using Hamming codes and an xor cipher:

use std::io::Write;
use std::fs::File;
use std::io;
use rand::{rng, RngExt};

static WITHOUT_POWERS_OF_TWO: &[u8] = &[0, 3, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15];

fn string_to_fmt_u16s(s: String) -> Vec<u16> {
    let string = s.chars().filter(|c| *c == '0' || *c == '1').collect::<String>();
    let mut slices = Vec::new();
    let out_len = string.len() / 16;
    for i in 0..out_len {
        slices.push(u16::from_str_radix(&string[i*16..(i+1)*16], 2).expect("Input not a valid binary number."));
    }
    slices
}

fn string_to_fmt_bytes(s: String) -> Vec<u8> {
    let string = s.chars().filter(|c| *c == '0' || *c == '1').collect::<String>();
    let mut slices = Vec::new();
    let out_len = string.len() / 8;
    for i in 0..out_len {
        slices.push(u8::from_str_radix(&string[i*8..(i+1)*8], 2).expect("Input not a valid binary number."));
    }
    slices
}


const fn get_bit_at(v: u16, i: u8) -> u8 {
    ((v >> i) & 1) as u8
}

fn set_bit_at(v: &mut u16, i: u8, val: bool) {
    let cleared_bit = *v & !(1 << i);
    if val {
        *v = cleared_bit | (1 << i)
    } else {
        *v = cleared_bit
    }
}

fn toggle_bit_at(v: u16, i: u8) -> u16 {
    v ^ (1 << i)
}

fn bools_to_u8(b: [bool; 8]) -> u8 {
    let mut answer: u8 = 0;
    for (i, boolean) in b.iter().enumerate() {
        answer |= (*boolean as u8) << (7 - i as u8)
    };
    answer
}

fn arr_bool_to_arr_u8(bools: Vec<bool>) -> Vec<u8> {
    let mut bools_copy = bools.clone();
    let len_u8s = bools_copy.len() / 8 + if bools_copy.len() % 8 > 0 { 1 } else { 0 };
    let mut false_bools_space = vec![false; bools_copy.len() % 8];
    bools_copy.append(&mut false_bools_space);
    let mut answer = Vec::new();
    for i in 0..len_u8s {
        answer.push(bools_to_u8(bools_copy[i*8..(i+1)*8].try_into().unwrap()));
    };
    answer.into()
}

fn remove_by_value<T: PartialEq>(xs: &mut Vec<T>, value: T) {
    let index = match xs.iter().position(|x| *x == value) {
        Some(i) => i,
        None => return
    };
    xs.remove(index);
}

fn spread_u8_to_u16(bits: u8, how_to_spread: &[u8]) -> u16 {
    let mut ans = 0;
    for i in 0..how_to_spread.len() {
        ans |= ((bits >> i & 1) as u16) << how_to_spread[i] as u16;
    }
    ans
}

fn unspread_u16_to_u8(bits: u16, how_to_unspread: &[u8]) -> u8 {
    let mut ans = 0;
    for i in 0..how_to_unspread.len() {
        ans |= get_bit_at(bits, how_to_unspread[i]) << i;
    }
    ans
}

fn hamming(data_block: u16) -> u16 {
    let mut code = data_block;
    // We calculate 4 parity bits (1, 2, 4, 8)
    for i in 0..4 {
        let parity_bit_pos = 1 << i;
        let mut parity = 0;

        for j in 1..16 {
            if (j & parity_bit_pos) != 0 {
                parity ^= get_bit_at(code, j as u8);
            }
        }
        if parity != 0 {
            code = toggle_bit_at(code, parity_bit_pos as u8);
        }
    }
    code
}

fn reverse_hamming(received: u16) -> u8 {
    let mut error_pos = 0;
    for i in 0..4 {
        let parity_bit_pos = 1 << i;
        let mut parity = 0;
        for j in 1..16 {
            if (j & parity_bit_pos) != 0 {
                parity ^= get_bit_at(received, j as u8);
            }
        }
        if parity != 0 {
            error_pos += parity_bit_pos;
        }
    }
    error_pos as u8
}

fn hamming_encrypt(string: &str,  bytes_flag: bool) -> Vec<u16> {
    let mut rng = rng();
    let bytes = if bytes_flag { &string_to_fmt_bytes(string.into()) } else { string.as_bytes() };
    let mut error_bits = Vec::new();
    let mut key = Vec::new();
    for _ in 0..bytes.len() {
        error_bits.push(rng.random_range(0..16));
        key.push(rng.random::<bool>());
    }
    let key_u8 = arr_bool_to_arr_u8(key.repeat(8));
    let encrypted: Vec<_> = bytes.iter().enumerate().map(|(i, b)| *b ^ key_u8[i]).collect();
    let encrypted_u16 = encrypted.iter().enumerate().map(|(i, b)| {
        let mut spread_indexes = WITHOUT_POWERS_OF_TWO.to_vec();
        remove_by_value(&mut spread_indexes, error_bits[i]);
        let mut b = spread_u8_to_u16(*b, &spread_indexes[..8]);
        set_bit_at(&mut b, error_bits[i], !key[i]);
        b
    }).collect::<Vec<_>>();
    let hamminged_encrypted_u16 = encrypted_u16.iter().enumerate().map(|(i, b)| {
        let hamminged = hamming(*b);
        toggle_bit_at(if (get_bit_at(hamminged, error_bits[i]) != 0) == key[i] {
            hamming(hamminged | 1 << 15)
        } else {
            hamminged
        }, error_bits[i])
    }).collect::<Vec<_>>();
    hamminged_encrypted_u16
}

fn hamming_decrypt(encrypted: Vec<u16>, bytes_flag: bool) -> String {
    let hamming_key_locations = encrypted.iter().map(|x| reverse_hamming(*x)).collect::<Vec<_>>();
    let unspread_bits = encrypted.iter().enumerate().map(|(i, x)| {
        let mut unspread_indexes = WITHOUT_POWERS_OF_TWO.to_vec();
        remove_by_value(&mut unspread_indexes, hamming_key_locations[i]);
        unspread_u16_to_u8(*x, &unspread_indexes[..8])
    }).collect::<Vec<_>>();
    let key = encrypted.iter().enumerate().map(|(i, x)| get_bit_at(*x, hamming_key_locations[i]) != 0).collect::<Vec<_>>();
    let key_u8 = arr_bool_to_arr_u8(key.repeat(8));
    let bytes = unspread_bits.iter().enumerate().map(|(i, x)| x ^ key_u8[i]).collect::<Vec<_>>();
    if bytes_flag {
        bytes.iter().map(|&b| format!("{:08b}", b)).collect::<Vec<_>>().join(" ")
    } else {
        match String::from_utf8(bytes.clone()) {
            Ok(string) => string,
            Err(_) => {
                let mut err_str = String::from("Failed to convert to string, raw bytes provided below:\n");
                err_str.push_str(&*bytes.iter().map(|&b| format!("{:08b}", b)).collect::<Vec<_>>().join(" "));
                err_str
            }
        }
    }
}

enum Output {
    File(File),
    Stdout,
}

impl Write for Output {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        match self {
            Output::File(f) => f.write(buf),
            Output::Stdout => io::stdout().write(buf),
        }
    }

    fn flush(&mut self) -> io::Result<()> {
        match self {
            Output::File(f) => f.flush(),
            Output::Stdout => io::stdout().flush(),
        }
    }
}

fn main() {
    let args = std::env::args().collect::<Vec<_>>();
    if args.len() < 3 {
        eprintln!("Usage: hamming [bytes] encrypt/decrypt <text> [<output file>]");
    } else {
        let mut output = if (args.len() == 4 && args[1] != "bytes") || (args.len() == 5 && args[1] == "bytes"){ Output::File(File::create(&args[args.len()-1]).expect("Your choice of file is invalid. Please choose another file."))} else { Output::Stdout };
        match args[1].as_str() {
            "bytes" => match args[2].as_str() {
                "encrypt" => writeln!(output, "{}", hamming_encrypt(&args[3], true).iter().map(|x| format!("{:016b}", x).trim_start_matches("0b").to_string() ).collect::<Vec<_>>().join(" ")).expect("Could not write to specified file."),
                "decrypt" => writeln!(output, "{}", hamming_decrypt(string_to_fmt_u16s(args[3].clone()), true)).expect("Could not write to specified file."),
                _ => eprintln!("Usage: hamming [bytes] encrypt/decrypt <text> [<output file>]"),
            }
            "encrypt" => writeln!(output, "{}", hamming_encrypt(&args[2], false).iter().map(|x| format!("{:016b}", x).trim_start_matches("0b").to_string() ).collect::<Vec<_>>().join(" ")).expect("Could not write to specified file."),
            "decrypt" => writeln!(output, "{}", hamming_decrypt(string_to_fmt_u16s(args[2].clone()), false)).expect("Could not write to specified file."),
            _ => eprintln!("Usage: hamming [bytes] encrypt/decrypt <text> [<output file>]"),
        }
    }
}

It uses a CLI and supports bytes and text as well as an output file.

3 posts - 3 participants

Read full topic

🏷️ Rust_feed