first commit
This commit is contained in:
+439
@@ -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" }
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user