first commit

This commit is contained in:
2026-04-23 14:19:20 +02:00
commit 710ad55d6d
6 changed files with 466 additions and 0 deletions
+439
View File
@@ -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<Tab> = 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" }
);
}