From 710ad55d6de3aea50a52731d250cc9653b8cb365 Mon Sep 17 00:00:00 2001 From: Sam Hadow Date: Thu, 23 Apr 2026 14:19:20 +0200 Subject: [PATCH] first commit --- .gitignore | 1 + Cargo.lock | 7 + Cargo.toml | 6 + LICENSE | 13 ++ README.md | 0 src/main.rs | 439 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 466 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..81d4163 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "gea-1" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..7fb10dc --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "gea-1" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1bd8e7f --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2026 sam.hadow. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + 4. Redistributions of any form whatsoever must retain the following acknowledgment: 'This product includes software developed by "Sam Hadow" (http://hadow.fr/).' + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..f93836f --- /dev/null +++ b/src/main.rs @@ -0,0 +1,439 @@ +use std::cmp::Ordering; +use std::time::Instant; + +//-------------------------- PART 1: LFSR & GEA-1 -------------------------- +#[derive(Debug)] +struct Lfsr { + state: u64, + mask: u64, +} + +// Feedback polynomials +const MASK_A: u64 = 0x2C7646EE; +const MASK_B: u64 = 0x510781C7; +const MASK_C: u64 = 0x245F670A; + +// Truth table of function f (7-bit input -> 1-bit output) +const TRUTH_TABLE_F: [u8; 128] = [ + 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, + 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, + 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, +]; + +// Taps for GEA-1 +const F_A: [usize; 7] = [8, 30, 17, 9, 5, 28, 23]; +const F_B: [usize; 7] = [19, 4, 31, 30, 2, 10, 26]; +const F_C: [usize; 7] = [22, 2, 0, 29, 13, 32, 28]; + +/// Evaluate Boolean function f on 7-bit input (only lower 7 bits used) +fn f(input: u8) -> u8 { + TRUTH_TABLE_F[(input & 0x7F) as usize] +} + +/// Print a u64 as comma-separated binary bits (only `nb_bits` bits) +fn print_binaire(x: u64, nb_bits: usize) { + for i in (0..nb_bits).rev() { + print!("{}", (x >> i) & 1); + if i > 0 { + print!(","); + } + } +} + +/// Update one LFSR (length 31, 32 or 33) +fn update_lfsr(lfsr: &mut Lfsr, length: usize, input: u64) { + let msb = (lfsr.state >> (length - 1)) & 1; + let bit = msb ^ (input & 1); + if bit == 1 { + lfsr.state ^= lfsr.mask; + } + lfsr.state = ((lfsr.state << 1) | bit) ^ (msb << length); +} + +/// Initialise the three LFSRs with secret S +fn init(s: u64, a: &mut Lfsr, b: &mut Lfsr, c: &mut Lfsr) { + a.state = 0; + b.state = 0; + c.state = 0; + a.mask = MASK_A; + b.mask = MASK_B; + c.mask = MASK_C; + + let sb = s.rotate_left(16); + let sc = s.rotate_right(32); + + for i in (0..64).rev() { + update_lfsr(a, 31, (s >> i) & 1); + update_lfsr(b, 32, (sb >> i) & 1); + update_lfsr(c, 33, (sc >> i) & 1); + } +} + +/// Build 7-bit input for f from LFSR state and tap positions +fn input_f(state: u64, taps: &[usize; 7]) -> u8 { + ((state >> taps[0]) & 1) as u8 + | (((state >> taps[1]) & 1) as u8) << 1 + | (((state >> taps[2]) & 1) as u8) << 2 + | (((state >> taps[3]) & 1) as u8) << 3 + | (((state >> taps[4]) & 1) as u8) << 4 + | (((state >> taps[5]) & 1) as u8) << 5 + | (((state >> taps[6]) & 1) as u8) << 6 +} + +/// Generate 64 bits of key stream +fn stream(a: &mut Lfsr, b: &mut Lfsr, c: &mut Lfsr) -> u64 { + let mut out: u64 = 0; + for i in 0..64 { + let fa = f(input_f(a.state, &F_A)); + let fb = f(input_f(b.state, &F_B)); + let fc = f(input_f(c.state, &F_C)); + let z = fa ^ fb ^ fc; + out |= (z as u64) << i; + update_lfsr(a, 31, 0); + update_lfsr(b, 32, 0); + update_lfsr(c, 33, 0); + } + out +} + +/// Pack 8 ASCII chars (little-endian) into a u64 +fn pack8(s: &[u8; 8]) -> u64 { + let mut x = 0u64; + for (i, &byte) in s.iter().enumerate() { + x |= (byte as u64) << (8 * i); + } + x +} + +/// Unpack a u64 into 8 chars (little-endian) + null terminator (stored in slice) +fn unpack8(x: u64, out: &mut [u8; 9]) { + for (i, byte) in out.iter_mut().take(8).enumerate() { + *byte = ((x >> (8 * i)) & 0xFF) as u8; + } + out[8] = 0; +} + +/// Print the 64×31, 64×32, 64×33 representation matrices +fn print_representation() { + let mut la = [0u64; 64]; + let mut lb = [0u64; 64]; + let mut lc = [0u64; 64]; + + for j in 0..64 { + let s = 1u64 << j; + let (mut a, mut b, mut c) = ( + Lfsr { state: 0, mask: 0 }, + Lfsr { state: 0, mask: 0 }, + Lfsr { state: 0, mask: 0 }, + ); + init(s, &mut a, &mut b, &mut c); + + for i in 0..31 { + if ((a.state >> i) & 1) == 1 { + la[j] |= 1 << i; + } + } + for i in 0..32 { + if ((b.state >> i) & 1) == 1 { + lb[j] |= 1 << i; + } + } + for i in 0..33 { + if ((c.state >> i) & 1) == 1 { + lc[j] |= 1 << i; + } + } + } + + println!("Representation LA (64 x 31)"); + for row in la.iter() { + print_binaire(*row, 31); + println!(); + } + println!("\nRepresentation LB (64 x 32)"); + for row in lb.iter() { + print_binaire(*row, 32); + println!(); + } + println!("\nRepresentation LC (64 x 33)"); + for row in lc.iter() { + print_binaire(*row, 33); + println!(); + } +} + +/// Encrypt/decrypt a 64-bit block using secret S +fn encrypt_decrypt(text: u64, s: u64) -> u64 { + let (mut a, mut b, mut c) = ( + Lfsr { state: 0, mask: 0 }, + Lfsr { state: 0, mask: 0 }, + Lfsr { state: 0, mask: 0 }, + ); + init(s, &mut a, &mut b, &mut c); + let keystream = stream(&mut a, &mut b, &mut c); + text ^ keystream +} + +//-------------------------- PART 2: ATTACK -------------------------- +#[derive(Debug, Clone, Copy)] +struct Tab { + output: u64, + input: u64, +} + +// Precomputed tables (KerB and TAC) +const KER_B: [u64; 32] = [ + 0x7c65e94100000001, + 0xdd593e6e00000002, + 0xc6d7959d00000004, + 0xd4582e9700000008, + 0xd4d5b46f00000010, + 0xf05c6d7300000020, + 0x9cdd33a700000040, + 0x45df8e0f00000080, + 0xae2df0f200000100, + 0x5ace449000000200, + 0x2ecb247e00000400, + 0x5d9648fc00000800, + 0xbb2c91f800001000, + 0x2fae265d00002000, + 0x5f5c4cba00004000, + 0xbeb8997400008000, + 0x2486374500010000, + 0x490c6e8a00020000, + 0x9218dd1400040000, + 0x7dc6bf8500080000, + 0xde1f93e600100000, + 0xe5c8226100200000, + 0xb7f5ad8300400000, + 0x361c5eab00800000, + 0x6c38bd5601000000, + 0xfde3964002000000, + 0xa230292d04000000, + 0x3805bb1b08000000, + 0x55999ada10000000, + 0x8ea1d95820000000, + 0x44b4b71d40000000, + 0xacfb82d680000000, +]; + +const TAC: [u64; 24] = [ + 0xabe419cf39000001, + 0x30c8339e0b000002, + 0x6190673c16000004, + 0xc320ce782c000008, + 0xe1419cf021000010, + 0xa58339e03b000020, + 0x2c0673c00f000040, + 0x580ce7801e000080, + 0xb019cf003c000100, + 0x7339e0001000200, + 0xe673c0002000400, + 0x1cce780004000800, + 0x399cf00008001000, + 0x7339e00010002000, + 0xe673c00020004000, + 0xabe7800039008000, + 0x30cf00000b010000, + 0x619e000016020000, + 0xc33c00002c040000, + 0xe178000021080000, + 0xa5f000003b100000, + 0x2ce000000f200000, + 0x59c000001e400000, + 0xb38000003c800000, +]; + +/// Binary search in a sorted slice of Tab by `output` field +fn binary_search_tab(table: &[Tab], target: u64) -> Option<&Tab> { + table + .binary_search_by(|entry| { + if entry.output < target { + Ordering::Less + } else if entry.output > target { + Ordering::Greater + } else { + Ordering::Equal + } + }) + .ok() + .map(|idx| &table[idx]) +} + +/// Full meet-in-the-middle attack +fn attack(z: u64) -> u64 { + const LOG_SIZE_TAC: u32 = 24; + const LOG_SIZE_KERB: u32 = 32; + let size_l = 1usize << LOG_SIZE_TAC; + + // Build table L (u -> stream( S = u XOR truev ) ) + let mut a0 = Lfsr { state: 0, mask: 0 }; + let mut b0 = Lfsr { state: 0, mask: 0 }; + let mut c0 = Lfsr { state: 0, mask: 0 }; + init(0, &mut a0, &mut b0, &mut c0); + let stream0 = stream(&mut a0, &mut b0, &mut c0); + println!("[*] stream(S=0) = 0x{:016X}", stream0); + + let mut l: Vec = Vec::with_capacity(size_l); + + let truev: u64 = 0; + + println!("[*] building L ({} entries)...", size_l); + + let mut current_u = 0u64; + // First entry i=0 + { + let (mut a, mut b, mut c) = ( + Lfsr { state: 0, mask: 0 }, + Lfsr { state: 0, mask: 0 }, + Lfsr { state: 0, mask: 0 }, + ); + init(current_u ^ truev, &mut a, &mut b, &mut c); + l.push(Tab { + output: stream(&mut a, &mut b, &mut c), + input: current_u, + }); + } + + for i in 1u64..size_l as u64 { + // Jump to next u using Gray-code XOR + current_u ^= TAC[i.trailing_zeros() as usize]; + + let (mut a, mut b, mut c) = ( + Lfsr { state: 0, mask: 0 }, + Lfsr { state: 0, mask: 0 }, + Lfsr { state: 0, mask: 0 }, + ); + init(current_u ^ truev, &mut a, &mut b, &mut c); + l.push(Tab { + output: stream(&mut a, &mut b, &mut c), + input: current_u, + }); + + if i & 0xFFFFF == 0 { + println!(" {} / {}", i, size_l); + } + } + + println!("[*] Sorting L"); + l.sort_by(|a, b| { + let cmp = a.output.cmp(&b.output); + if cmp != Ordering::Equal { + cmp + } else { + a.input.cmp(&b.input) + } + }); + println!("[*] Sort finished."); + + println!("[*] Search in KerB"); + let mut current_w = 0u64; + let mut result = 0u64; + let mut found = false; + + // First iteration w=0 + { + let (mut a, mut b, mut c) = ( + Lfsr { state: 0, mask: 0 }, + Lfsr { state: 0, mask: 0 }, + Lfsr { state: 0, mask: 0 }, + ); + init(current_w ^ truev, &mut a, &mut b, &mut c); + let sw = stream(&mut a, &mut b, &mut c); + let target = z ^ sw ^ stream0; + if let Some(hit) = binary_search_tab(&l, target) { + result = hit.input ^ current_w ^ truev; + found = true; + println!("[!] Correlation found ! w=0, u=0x{:016X}", hit.input); + } + } + + for i in 1u64..(1u64 << LOG_SIZE_KERB) { + if found { + break; + } + current_w ^= KER_B[i.trailing_zeros() as usize]; + + let (mut a, mut b, mut c) = ( + Lfsr { state: 0, mask: 0 }, + Lfsr { state: 0, mask: 0 }, + Lfsr { state: 0, mask: 0 }, + ); + init(current_w ^ truev, &mut a, &mut b, &mut c); + let sw = stream(&mut a, &mut b, &mut c); + let target = z ^ sw ^ stream0; + + if let Some(hit) = binary_search_tab(&l, target) { + result = hit.input ^ current_w ^ truev; + found = true; + println!("[!] Correlation found at i={}", i); + println!(" w = 0x{:016X}", current_w); + println!(" u = 0x{:016X}", hit.input); + } + } + + if !found { + println!("[-] Secret not found."); + 0 + } else { + result + } +} + +//-------------------------- MAIN -------------------------- +fn main() { + let s: u64 = 1; + println!("0x{:08X}", s); + + let mut a = Lfsr { state: 0, mask: 0 }; + let mut b = Lfsr { state: 0, mask: 0 }; + let mut c = Lfsr { state: 0, mask: 0 }; + init(s, &mut a, &mut b, &mut c); + println!("0x{:08X}", a.state); + println!("0x{:08X}", b.state); + println!("0x{:08X}", c.state); + + let z = stream(&mut a, &mut b, &mut c); + println!("0x{:08X}", z); + + println!("key S = 0x{:016X}\n", s); + print_representation(); + + let plain: [u8; 8] = [b'H', b'E', b'L', b'L', b'O', b' ', b'W', b'O']; + let message = pack8(&plain); + let cipher = encrypt_decrypt(message, s); + let recovered = encrypt_decrypt(cipher, s); + let mut recovered_text = [0u8; 9]; + unpack8(recovered, &mut recovered_text); + + println!("\ncleartext : 0x{:016X}", message); + println!("ciphertext : 0x{:016X}", cipher); + println!("decrypted : 0x{:016X}", recovered); + println!( + "decrypted message : {}", + std::str::from_utf8(&recovered_text[..8]).unwrap() + ); + + let z_challenge: u64 = 0x2D0E339CFD588D1B; + println!("=== Meet-in-the-Middle attack==="); + println!("Target cipher stream : 0x{:016X}\n", z_challenge); + + let t0 = Instant::now(); + let secret = attack(z_challenge); + let elapsed = t0.elapsed().as_secs_f64(); + + println!("\nSecret found : S = 0x{:016X}", secret); + println!(" Time : {:.2} s\n", elapsed); + + // Verification + init(secret, &mut a, &mut b, &mut c); + let z_check = stream(&mut a, &mut b, &mut c); + println!("=== Check ==="); + println!("stream(S_found) = 0x{:016X}", z_check); + println!("z_challenge = 0x{:016X}", z_challenge); + println!( + "Correlation : {}", + if z_check == z_challenge { "YES" } else { "NO" } + ); +}