Logo

index : openmessage

---

  • summary
  • about
  • tree
  • log
  • branches
<< path: root/public/openmessage.git/html/src/obfuscator.rs blob: 98ccdf4913f8b032bf426caf86c4c764c4075c05 [raw] [clear marker]

        
0use rand::Rng;
1use std::fs::File;
2use std::io::{Write, Read};
3use std::path::PathBuf;
4use std::process::exit;
5use std::usize;
6
7use flate2::read::DeflateDecoder;
8use flate2::write::DeflateEncoder;
9use flate2::Compression;
10
11use crate::{CHUNK_SIZE, MAGIC};
12
13
14/// Implements the Steinhaus-Johnson-Trotter algorithm for generating permutations of `n`
15/// https://en.wikipedia.org/wiki/Steinhaus%E2%80%93Johnson%E2%80%93Trotter_algorithm
16fn permutation(n: usize) -> Vec<Vec<usize>> {
17 let mut perm: Vec<usize> = (0..n).collect();
18 let mut direction: Vec<i32> = vec![-1; n];
19 let mut result: Vec<Vec<usize>> = Vec::new();
20
21 let factorial = (1..=n).product();
22
23 result.push(perm.clone());
24
25 for _ in 0..factorial {
26 let mut mobile_index = None;
27
28 for i in 0..n {
29 if (direction[i] == -1 && i > 0 && perm[i] > perm[i - 1])
30 || (direction[i] == 1 && i < n - 1 && perm[i] > perm[i + 1])
31 {
32 if mobile_index.is_none() || perm[i] > perm[mobile_index.unwrap()] {
33 mobile_index = Some(i);
34 }
35 }
36 }
37
38 let mobile_index = match mobile_index {
39 Some(index) => index,
40 None => break,
41 };
42
43 let new_index = (mobile_index as isize + direction[mobile_index] as isize) as usize;
44 perm.swap(mobile_index, new_index);
45 direction.swap(mobile_index, new_index);
46
47 let moved_value = perm[new_index];
48
49 for i in 0..n {
50 if perm[i] > moved_value {
51 direction[i] *= -1;
52 }
53 }
54 result.push(perm.clone());
55 }
56 result
57}
58
59
60fn exit_err_fmt(error_code: i32, message: String) -> ! {
61 eprintln!("[Error]: {}", message);
62 exit(error_code);
63}
64
65fn exit_err(error_code: i32, message: &str) -> ! {
66 eprintln!("[Error]: {}", message);
67 exit(error_code);
68}
69
70
71enum ChunkType<'a> {
72 StringData(String),
73 ByteData(&'a [u8]),
74}
75
76
77/// Splits strings into chunks, where the exact size is determined by a constant
78trait Chunks {
79 fn new(v: ChunkType) -> Self;
80
81 fn convert_to_chunks(v: String) -> Vec<Vec<u8>> {
82 v.into_bytes().chunks(CHUNK_SIZE).map(|c| Self::pad_vec(c.to_vec())).collect()
83 }
84
85 fn pad_vec(mut v: Vec<u8>) -> Vec<u8> {
86 if v.len() < CHUNK_SIZE {
87 v.resize(CHUNK_SIZE, 0);
88 }
89 v
90 }
91}
92
93
94#[derive(Debug)]
95struct Key {
96 chunks: Vec<Vec<u8>>,
97}
98
99impl Chunks for Key {
100 fn new(key: ChunkType) -> Key {
101 let key = match key {
102 ChunkType::ByteData(b) => b,
103 _ => exit_err(1, "wrong data type (Key)")
104 };
105
106 let chunks: Vec<Vec<u8>> = key.chunks(CHUNK_SIZE)
107 .map(|c| c.to_vec())
108 .collect();
109
110 Key {
111 chunks,
112 }
113 }
114}
115
116/// Contains a collection of non-padded, and chunked strings. This structure will be used
117/// to xor it against the container structure.
118#[derive(Debug)]
119struct Message {
120 msg: String,
121 chunks: Vec<Vec<u8>>,
122}
123
124impl Chunks for Message {
125 fn new(msg: ChunkType) -> Message {
126 let msg = match msg {
127 ChunkType::StringData(s) => s,
128 _ => exit_err(1, "wrong data type (Messsage)")
129 };
130 Message {
131 chunks: Self::convert_to_chunks(msg.clone()),
132 msg,
133 }
134 }
135}
136
137/// A container is a collection of padded, and chunked strings to "store" the user provided message in it.
138#[derive(Debug)]
139struct Container {
140 con: String,
141 chunks: Vec<Vec<u8>>,
142}
143
144impl Chunks for Container {
145 fn new(con: ChunkType) -> Container {
146 let con = match con {
147 ChunkType::StringData(s) => s,
148 _ => exit_err(1, "wrong data type (Container)")
149 };
150
151 if con.len() < CHUNK_SIZE {
152 exit_err_fmt(1, format!("Container too small. Is: {} bytes. Needs: {} bytes", con.len(), CHUNK_SIZE));
153 }
154
155 Container {
156 chunks: Self::convert_to_chunks(con.clone()),
157 con,
158 }
159 }
160}
161
162
163/// Encodes and decodes the provided message.
164///
165/// # Examples
166/// Encoding a message
167/// ```
168/// let message = Some(String::from("Some message I want to hide"));
169/// let container = String::from("Hello World");
170/// let key_fp = PathBuf::from("key.om");
171///
172/// Obfuscator::new(message, container, key_fp, false).encode();
173/// ```
174///
175/// Decoding a message
176/// ```
177/// let container = String::from("Hello World");
178/// let key_fp = PathBuf::from("key.om");
179///
180/// let message = Obfuscator::new(None, container, key_fp, false).decode();
181/// println("{message}");
182/// ```
183/// Output: `> Some message I want to hide`
184#[derive(Debug)]
185pub struct Obfuscator {
186 message: Message,
187 container: Container,
188 permutations: Vec<Vec<usize>>,
189 key_file: PathBuf,
190 verbose: bool,
191}
192
193
194impl Obfuscator {
195 pub fn new(message: Option<String>, container: String, key_file: PathBuf, verbose: bool) -> Obfuscator {
196 let m = Message::new(ChunkType::StringData(message.unwrap_or(String::new())));
197 let c = Container::new(ChunkType::StringData(container));
198 let p = permutation(CHUNK_SIZE);
199
200 Obfuscator {
201 message: m,
202 container: c,
203 permutations: p,
204 key_file,
205 verbose,
206 }
207 }
208
209 pub fn encode(&self) -> &Self {
210 println!("--| Encoding Message |--");
211 self.verbose(format!("--| Message |-----------------------\n{}\n\n{:?}\n", &self.message.msg, &self.message.chunks));
212 self.verbose(format!("\n--| Container |---------------------\n{}\n\n{:?}\n", &self.container.con, &self.container.chunks));
213
214 let chunk_size = Self::calc_needed_chunks(self.permutations.len(), self.message.chunks.len());
215 self.check_capacity(chunk_size);
216
217 let position = Self::choose_position(self.container.chunks.len(), chunk_size);
218 self.verbose(format!("\n--| Lengths |-----------------------\nChunk Size: {:?}\nPosition: {:?}", &chunk_size, &position));
219
220 self.verbose("\n--| Permutations & XOR |------------".to_string());
221
222 let key = self.generate_key(position, chunk_size);
223 self.verbose(format!("\n--| XORed Key |---------------------\n{:?}", &key));
224
225 let header = self.prefix_header(position, key.0, key.1);
226 self.verbose(format!("\n--| Prefixed Header |---------------\n{:?}", &header));
227
228 let compressed_key = self.deflate_compress(header);
229 self.verbose(format!("\n--| DEFLATE Compression |-----------\n{:?}", &compressed_key));
230
231 self.save_to_file(&compressed_key);
232 self
233 }
234
235 pub fn decode(&self) -> String {
236 println!("--| Decoded Message |--");
237 self.verbose(format!("--| Container |---------------------\n{}\n\n{:?}", &self.container.con, &self.container.chunks));
238
239 let key_file = self.read_from_file();
240 let key = self.deflate_decompress(&key_file);
241
242 let pos_data = &key[0..8];
243 let con_len = &key[8..16];
244 let magic = &key[16..24];
245 let data = &key[24..];
246 self.verbose(format!("\n--| Extracted |---------------------\nPosition: {:?}\nCLen: {:?}\nMagic: {:?}\nData: {:?}", &pos_data, &con_len, &magic, &data));
247
248
249 if magic != MAGIC {
250 exit_err(1, "file might be corrupt (magic value missing)");
251 }
252
253 if data.len() % CHUNK_SIZE != 0 {
254 exit_err(1, "file might be corrupt (bad chunk size)");
255 }
256
257 let position = usize::from_le_bytes(pos_data.try_into().unwrap());
258 let container_len = usize::from_le_bytes(con_len.try_into().unwrap());
259 let chunks = Key::new(ChunkType::ByteData(data));
260 self.verbose(format!("\n--| Calc |--------------------------\nPosition: {:?}\nCLen: {:?}\nChunks: {:?}\n", &position, &container_len, &chunks));
261
262 let xored = self.generate_message(position, container_len, &chunks);
263 self.verbose(format!("\n--| XORed Message |-----------------\n{:?}", &xored));
264
265 let message = self.convert_from_chunks(xored);
266 self.verbose(format!("\n--| Message |-----------------------\n{:?}\n", &message));
267
268 message.trim().trim_end_matches("\0").to_string()
269 }
270
271 /// Calculates how many chunks from `container` are needed to fit the `message` chunks in it
272 fn calc_needed_chunks(permutation_len: usize, msg_chunks_len: usize) -> usize {
273 (msg_chunks_len + permutation_len - 1) / permutation_len
274 }
275
276 /// Chooses a random position inside the `container` chunks
277 fn choose_position(c_chunks_len: usize, m_chunk_size: usize) -> usize {
278 let upper_bound = c_chunks_len - m_chunk_size;
279 let mut rng = rand::rng();
280 let pos: usize = rng.random_range(0..=upper_bound);
281 pos
282 }
283
284 fn check_capacity(&self, chunk_size: usize) -> () {
285 if chunk_size > self.container.chunks.len() {
286 exit_err(1, "message doesn't fit into container");
287 }
288 }
289
290 fn convert_from_chunks(&self, chunks: Vec<Vec<u8>>) -> String {
291 let flat: Vec<u8> = chunks.into_iter().flatten().collect();
292 String::from_utf8(flat).expect("invalid utf-8 data")
293 }
294
295 fn deflate_compress(&self, data: Vec<u8>) -> Vec<u8> {
296 let mut encoder = DeflateEncoder::new(Vec::new(), Compression::best());
297 encoder.write_all(&data).unwrap();
298 encoder.finish().unwrap()
299 }
300
301 fn deflate_decompress(&self, compressed: &[u8]) -> Vec<u8> {
302 let mut decoder = DeflateDecoder::new(compressed);
303 let mut decompressed = Vec::new();
304 decoder.read_to_end(&mut decompressed).unwrap();
305 decompressed
306 }
307
308 fn save_to_file(&self, compressed_data: &[u8]) {
309 let mut file = File::create(&self.key_file).unwrap();
310 file.write_all(compressed_data).unwrap();
311 println!("Saved to '{}'", &self.key_file.display());
312 }
313
314 fn read_from_file(&self) -> Vec<u8> {
315 let mut file = File::open(&self.key_file).unwrap();
316 let mut data = Vec::new();
317 file.read_to_end(&mut data).unwrap();
318 data
319 }
320
321 /// Creates and prefixes the header to the `key`.
322 ///
323 /// Header
324 ///
325 /// 8 bytes 8 bytes 8 bytes
326 /// | | |
327 /// +--------------------+------------------+----------------+
328 /// | Container Position | Container Length | Identification |
329 /// +--------------------+------------------+----------------+
330 /// | Header Data |
331 /// +--------------------------------------------------------+
332 ///
333 fn prefix_header(&self, position: usize, container_len: usize, xored_data: Vec<Vec<u8>>) -> Vec<u8> {
334 let mut flat: Vec<u8> = xored_data.into_iter().flatten().collect();
335 let position_data_le = position.to_le_bytes();
336 let container_info_le = container_len.to_le_bytes();
337
338 let mut header = Vec::with_capacity(&position_data_le.len() + container_info_le.len() + MAGIC.len());
339 header.extend_from_slice(&position_data_le);
340 header.extend_from_slice(&container_info_le);
341 header.extend_from_slice(&MAGIC);
342
343 flat.reserve(header.len());
344 flat.splice(0..0, header);
345 flat
346 }
347
348 /// Decodes the `message` from the `container` with XOR, using the `key`
349 fn generate_message(&self, position: usize, container_len: usize, key: &Key) -> Vec<Vec<u8>> {
350 let mut key = key.chunks.iter().cloned();
351 let cont_len = container_len;
352 let mut xored = vec![];
353
354 if self.verbose {
355 dbg!(&position, &cont_len, cont_len + position, self.container.chunks.len());
356 }
357
358 for idx in position..(cont_len + position) {
359 let chunk = match self.container.chunks.get(idx) {
360 Some(v) => v,
361 None => exit_err_fmt(1, format!("[Decoder]: Out of bounds at container chunks. Index: {}", idx)),
362 };
363
364 for perm in &self.permutations {
365 let perm_chunk: Vec<u8> = perm.iter().map(|&i| chunk[i]).collect();
366
367 match self.xor(perm_chunk, &mut key) {
368 Some(v) => xored.push(v),
369 None => break,
370 }
371 }
372 }
373 xored
374 }
375
376 /// Encodes the `message` using the `container` with XOR, and outputs the `key`
377 fn generate_key(&self, position: usize, chunk_size: usize) -> (usize, Vec<Vec<u8>>) {
378 let mut msg = self.message.chunks.iter().cloned();
379 let mut xored = vec![];
380
381 for idx in position..(chunk_size + position) {
382 let chunk = match self.container.chunks.get(idx) {
383 Some(v) => v,
384 None => exit_err_fmt(1, format!("[Decoder]: Out of bounds at container chunks. Index: {}", idx)),
385 };
386
387 for perm in &self.permutations {
388 let perm_chunk: Vec<u8> = perm.iter().map(|&i| chunk[i]).collect();
389
390 match self.xor(perm_chunk, &mut msg) {
391 Some(v) => xored.push(v),
392 None => break,
393 }
394 }
395 }
396 (chunk_size, xored)
397 }
398
399 fn xor<I>(&self, c: Vec<u8>, m: &mut I) -> Option<Vec<u8>>
400 where
401 I: Iterator<Item = Vec<u8>>,
402 {
403 if let Some(v) = m.next() {
404 let result: Vec<u8> = c.iter()
405 .zip(v.iter())
406 .map(|(&x, &y)| x ^ y)
407 .collect();
408 self.verbose(format!("Permutation: {:?}\tMessage: {:?}\tXORed: {:?}", &c, &v, &result));
409 Some(result)
410 } else {
411 None
412 }
413 }
414
415 fn verbose(&self, msg: String) -> () {
416 if self.verbose {
417 println!("{}", msg);
418 }
419 }
420}
421
Copyright 2026  E766CB298A6D1E64 | Git-Thing heavily inspired by cgit