From 83f71b100bf7d44a3ab00c2e9eb26354afdc1256 Mon Sep 17 00:00:00 2001 From: Sam Hadow Date: Tue, 29 Apr 2025 11:03:48 +0200 Subject: [PATCH] DFA --- src/des.rs | 115 +++++++++++++++---------------- src/dfa.rs | 165 +++++++++++++++++++++++++++++++++++++++++++++ src/file_helper.rs | 10 +++ src/main.rs | 27 ++++++-- 4 files changed, 252 insertions(+), 65 deletions(-) create mode 100644 src/dfa.rs create mode 100644 src/file_helper.rs diff --git a/src/des.rs b/src/des.rs index fcdb4c5..9d0490e 100644 --- a/src/des.rs +++ b/src/des.rs @@ -1,101 +1,94 @@ //! A Rust implementation of the Data Encryption Standard (DES) -const E: [u8; 48] = [ - 32, 1, 2, 3, 4, 5, 4, 5, 6, 7, 8, 9, 8, 9, 10, 11, 12, 13, 12, 13, 14, 15, 16, 17, - 16, 17, 18, 19, 20, 21, 20, 21, 22, 23, 24, 25, 24, 25, 26, 27, 28, 29, 28, 29, 30, 31, 32, 1, +pub const E: [u8; 48] = [ + 32, 1, 2, 3, 4, 5, 4, 5, 6, 7, 8, 9, 8, 9, 10, 11, 12, 13, 12, 13, 14, 15, 16, 17, 16, 17, 18, + 19, 20, 21, 20, 21, 22, 23, 24, 25, 24, 25, 26, 27, 28, 29, 28, 29, 30, 31, 32, 1, ]; const P: [u8; 32] = [ - 16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23, 26, 5, 18, 31, 10, - 2, 8, 24, 14, 32, 27, 3, 9, 19, 13, 30, 6, 22, 11, 4, 25, + 16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23, 26, 5, 18, 31, 10, 2, 8, 24, 14, 32, 27, 3, 9, 19, + 13, 30, 6, 22, 11, 4, 25, ]; -const IP: [u8; 64] = [ - 58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4, - 62, 54, 46, 38, 30, 22, 14, 6, 64, 56, 48, 40, 32, 24, 16, 8, - 57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3, - 61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7, +pub const IP: [u8; 64] = [ + 58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6, + 64, 56, 48, 40, 32, 24, 16, 8, 57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3, 61, + 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7, ]; const IP_INV: [u8; 64] = [ - 40, 8, 48, 16, 56, 24, 64, 32, 39, 7, 47, 15, 55, 23, 63, 31, - 38, 6, 46, 14, 54, 22, 62, 30, 37, 5, 45, 13, 53, 21, 61, 29, - 36, 4, 44, 12, 52, 20, 60, 28, 35, 3, 43, 11, 51, 19, 59, 27, + 40, 8, 48, 16, 56, 24, 64, 32, 39, 7, 47, 15, 55, 23, 63, 31, 38, 6, 46, 14, 54, 22, 62, 30, + 37, 5, 45, 13, 53, 21, 61, 29, 36, 4, 44, 12, 52, 20, 60, 28, 35, 3, 43, 11, 51, 19, 59, 27, 34, 2, 42, 10, 50, 18, 58, 26, 33, 1, 41, 9, 49, 17, 57, 25, ]; -const PC1: [u8; 56] = [ - 57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, - 10, 2, 59, 51, 43, 35, 27, 19, 11, 3, 60, 52, 44, 36, - 63, 55, 47, 39, 31, 23, 15, 7, 62, 54, 46, 38, 30, 22, - 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 28, 20, 12, 4, +pub const PC1: [u8; 56] = [ + 57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, 27, 19, 11, 3, 60, + 52, 44, 36, 63, 55, 47, 39, 31, 23, 15, 7, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, + 21, 13, 5, 28, 20, 12, 4, ]; -const PC2: [u8; 48] = [ - 14, 17, 11, 24, 1, 5, 3, 28, 15, 6, 21, 10, - 23, 19, 12, 4, 26, 8, 16, 7, 27, 20, 13, 2, - 41, 52, 31, 37, 47, 55, 30, 40, 51, 45, 33, 48, - 44, 49, 39, 56, 34, 53, 46, 42, 50, 36, 29, 32, +pub const PC2: [u8; 48] = [ + 14, 17, 11, 24, 1, 5, 3, 28, 15, 6, 21, 10, 23, 19, 12, 4, 26, 8, 16, 7, 27, 20, 13, 2, 41, 52, + 31, 37, 47, 55, 30, 40, 51, 45, 33, 48, 44, 49, 39, 56, 34, 53, 46, 42, 50, 36, 29, 32, ]; -const SHIFTS: [u8; 16] = [ - 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1, -]; +const SHIFTS: [u8; 16] = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]; // S-boxes -const SBOX: [[[u8; 16]; 4]; 8] = [ +pub const SBOX: [[[u8; 16]; 4]; 8] = [ [ - [14,4,13,1,2,15,11,8,3,10,6,12,5,9,0,7], - [0,15,7,4,14,2,13,1,10,6,12,11,9,5,3,8], - [4,1,14,8,13,6,2,11,15,12,9,7,3,10,5,0], - [15,12,8,2,4,9,1,7,5,11,3,14,10,0,6,13], + [14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7], + [0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8], + [4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0], + [15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13], ], [ - [15,1,8,14,6,11,3,4,9,7,2,13,12,0,5,10], - [3,13,4,7,15,2,8,14,12,0,1,10,6,9,11,5], - [0,14,7,11,10,4,13,1,5,8,12,6,9,3,2,15], - [13,8,10,1,3,15,4,2,11,6,7,12,0,5,14,9], + [15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10], + [3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5], + [0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15], + [13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9], ], [ - [10,0,9,14,6,3,15,5,1,13,12,7,11,4,2,8], - [13,7,0,9,3,4,6,10,2,8,5,14,12,11,15,1], - [13,6,4,9,8,15,3,0,11,1,2,12,5,10,14,7], - [1,10,13,0,6,9,8,7,4,15,14,3,11,5,2,12], + [10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8], + [13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1], + [13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7], + [1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12], ], [ - [7,13,14,3,0,6,9,10,1,2,8,5,11,12,4,15], - [13,8,11,5,6,15,0,3,4,7,2,12,1,10,14,9], - [10,6,9,0,12,11,7,13,15,1,3,14,5,2,8,4], - [3,15,0,6,10,1,13,8,9,4,5,11,12,7,2,14], + [7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15], + [13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9], + [10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4], + [3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14], ], [ - [2,12,4,1,7,10,11,6,8,5,3,15,13,0,14,9], - [14,11,2,12,4,7,13,1,5,0,15,10,3,9,8,6], - [4,2,1,11,10,13,7,8,15,9,12,5,6,3,0,14], - [11,8,12,7,1,14,2,13,6,15,0,9,10,4,5,3], + [2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9], + [14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6], + [4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14], + [11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3], ], [ - [12,1,10,15,9,2,6,8,0,13,3,4,14,7,5,11], - [10,15,4,2,7,12,9,5,6,1,13,14,0,11,3,8], - [9,14,15,5,2,8,12,3,7,0,4,10,1,13,11,6], - [4,3,2,12,9,5,15,10,11,14,1,7,6,0,8,13], + [12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11], + [10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8], + [9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6], + [4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13], ], [ - [4,11,2,14,15,0,8,13,3,12,9,7,5,10,6,1], - [13,0,11,7,4,9,1,10,14,3,5,12,2,15,8,6], - [1,4,11,13,12,3,7,14,10,15,6,8,0,5,9,2], - [6,11,13,8,1,4,10,7,9,5,0,15,14,2,3,12], + [4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1], + [13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6], + [1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2], + [6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12], ], [ - [13,2,8,4,6,15,11,1,10,9,3,14,5,0,12,7], - [1,15,13,8,10,3,7,4,12,5,6,11,0,14,9,2], - [7,11,4,1,9,12,14,2,0,6,10,13,15,3,5,8], - [2,1,14,7,4,10,8,13,15,12,9,0,3,5,6,11], + [13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7], + [1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2], + [7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8], + [2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11], ], ]; /// Permute bits from `input` (of size `input_size`) according to `table`. -fn permute(input: u64, table: &[u8], input_size: u8) -> u64 { +pub fn permute(input: u64, table: &[u8], input_size: u8) -> u64 { let mut out = 0; let len = table.len() as u8; for (i, &t) in table.iter().enumerate() { @@ -106,7 +99,7 @@ fn permute(input: u64, table: &[u8], input_size: u8) -> u64 { } /// Split a 64-bit chunk into two 32-bit halves. -fn cut_in_halves(x: u64) -> (u32, u32) { +pub fn cut_in_halves(x: u64) -> (u32, u32) { (((x >> 32) & 0xFFFFFFFF) as u32, (x & 0xFFFFFFFF) as u32) } diff --git a/src/dfa.rs b/src/dfa.rs new file mode 100644 index 0000000..df11cb2 --- /dev/null +++ b/src/dfa.rs @@ -0,0 +1,165 @@ +//! Differential Fault Analysis (DFA) attack on DES. + +use crate::des::{cut_in_halves, des_encrypt, permute, E, IP, SBOX}; +use std::collections::HashMap; + +/// Inverse of P-permutation (32 -> 32) +const P_INV: [u8; 32] = [ + 9, 17, 23, 31, 13, 28, 2, 18, 24, 16, 30, 6, 26, 20, 10, 1, 8, 14, 25, 3, 4, 29, 11, 19, 32, + 12, 22, 7, 5, 27, 15, 21, +]; + +/// Inverse PC1 (64 -> 56) +const PC1_INV: [u8; 64] = [ + 8, 16, 24, 56, 52, 44, 36, 0, 7, 15, 23, 55, 51, 43, 35, 0, 6, 14, 22, 54, 50, 42, 34, 0, 5, + 13, 21, 53, 49, 41, 33, 0, 4, 12, 20, 28, 48, 40, 32, 0, 3, 11, 19, 27, 47, 39, 31, 0, 2, 10, + 18, 26, 46, 38, 30, 0, 1, 9, 17, 25, 45, 37, 29, 0, +]; + +/// Inverse PC2 (56 -> 48) +const PC2_INV: [u8; 56] = [ + 5, 24, 7, 16, 6, 10, 20, 18, 0, 12, 3, 15, 23, 1, 9, 19, 2, 0, 14, 22, 11, 0, 13, 4, 0, 17, 21, + 8, 47, 31, 27, 48, 35, 41, 0, 46, 28, 0, 39, 32, 25, 44, 0, 37, 34, 43, 29, 36, 38, 45, 33, 26, + 42, 0, 30, 40, +]; + +/// Determine which S‐boxes were affected by each fault +fn select_faults(correct: u64, faults: &[u64]) -> HashMap> { + let mut map: HashMap> = (0..8).map(|i| (i, Vec::new())).collect(); + for (i, &f) in faults.iter().enumerate() { + let diff = correct ^ f; + let perm = permute(diff, &IP, 64); + let (_l16, r16) = cut_in_halves(perm); + let expanded = permute(r16 as u64, &E, 32); + // split into 8 chunks of 6 bits + for s in 0..8 { + let block = ((expanded >> (42 - 6 * s)) & 0x3F) as u8; + if block != 0 { + map.get_mut(&s).unwrap().push(i); + } + } + } + map +} + +/// returns the single common element of the intersection of multiple Vec +fn common(lists: &[Vec]) -> u8 { + let mut set: std::collections::HashSet = lists[0].iter().copied().collect(); + for lst in &lists[1..] { + set = set + .intersection(&lst.iter().copied().collect()) + .copied() + .collect(); + } + assert!(set.len() == 1, "expected exactly one common solution"); + *set.iter().next().unwrap() +} + +/// 48-bit mask to isolate the 6 bits for s‐box `s` +fn mask48(s: usize) -> u64 { + let start = 6 * (7 - s); + let mask: u64 = 0x3Fu64 << start; + mask +} + +/// 32-bit mask to isolate the 4-bit output of s‐box `s` +fn mask32(s: usize) -> u32 { + let start = 4 * (7 - s); + 0xFu32 << start +} + +/// Given expanded R15 and a candidate subkey `k6`, compute (row, col) +fn calc_line_col(s: usize, expanded: u64, k6: u8) -> (usize, usize) { + let mut six = ((expanded & mask48(s)) >> (6 * (7 - s))) as u8; + six ^= k6; + let row = (((six & 0x20) >> 4) | (six & 0x01)) as usize; + let col = ((six & 0x1E) >> 1) as usize; + (row, col) +} + +/// Check if candidate k6 is valid for this fault in s‐box s +fn solution_valid( + s: usize, + p1_diff: u32, + row: usize, + col: usize, + row_f: usize, + col_f: usize, +) -> bool { + let out_diff = (p1_diff & mask32(s)) >> (4 * (7 - s)); + let x = SBOX[s][row][col] ^ SBOX[s][row_f][col_f]; + out_diff == x as u32 +} + +/// Find the 48-bit round‐16 subkey K16 +pub fn find_k16(correct: u64, faults: &[u64]) -> u64 { + let sboxes = select_faults(correct, faults); + // get L16 and R15 from correct ciphertext + let perm = permute(correct, &IP, 64); + let (l16, r15) = cut_in_halves(perm); + + let mut k16: u64 = 0; + for s in 0..8 { + let idxs = &sboxes[&s]; + // for each fault index for this sbox, collect the faulty ciphertext + let mut sol_lists: Vec> = Vec::new(); + let expanded = permute(r15 as u64, &E, 32); + for &i in idxs.iter().take(6) { + let perm_f = permute(faults[i], &IP, 64); + let (l16f, r15f) = cut_in_halves(perm_f); + let p1_diff = permute((l16 ^ l16f) as u64, &P_INV, 32) as u32; + let expf = permute(r15f as u64, &E, 32); + // try all 64 candidates + let mut cands = Vec::new(); + for k6 in 0u8..64u8 { + let (r0, c0) = calc_line_col(s, expanded, k6); + let (rf, cf) = calc_line_col(s, expf, k6); + if solution_valid(s, p1_diff, r0, c0, rf, cf) { + cands.push(k6); + } + } + sol_lists.push(cands); + } + let sol = common(&sol_lists) as u64; + k16 = (k16 << 6) | sol; + } + k16 +} + +/// Brute‐force the 8 missing bits to recover the 64‐bit master key (parity bits are set to 0 for padding) +pub fn find_k64(clear: u64, cipher: u64, k16: u64) -> u64 { + // invert PC2 then PC1 + let k56 = permute(permute(k16, &PC2_INV, 48), &PC1_INV, 56); + // 8 unknown bits + let positions = [50, 49, 45, 44, 13, 10, 6, 4]; + for mask in 0u16..(1 << 8) { + let mut candidate = k56; + for (i, &position) in positions.iter().enumerate() { + if (mask & (1 << i)) != 0 { + candidate |= 1u64 << position; + } + } + if des_encrypt(clear, candidate) == cipher { + return candidate; + } + } + panic!("Failed to recover full key"); +} + +/// Set odd parity on each byte +pub fn apply_parity(mut k: u64) -> u64 { + for byte in 0..8 { + let mut count = 0; + for b in 1..8 { + if (k & (1u64 << (byte * 8 + b))) != 0 { + count += 1; + } + } + if count % 2 == 0 { + k |= 1u64 << (byte * 8); + } else { + k &= !(1u64 << (byte * 8)); + } + } + k +} diff --git a/src/file_helper.rs b/src/file_helper.rs new file mode 100644 index 0000000..b5c137b --- /dev/null +++ b/src/file_helper.rs @@ -0,0 +1,10 @@ +use std::fs; + +/// Read multiple hex‐encoded u64s (one per line) +pub fn load_vec_u64_hex(path: &str) -> Vec { + fs::read_to_string(path) + .expect("failed to read file") + .lines() + .map(|l| u64::from_str_radix(l.trim_start_matches("0x").trim(), 16).unwrap()) + .collect() +} diff --git a/src/main.rs b/src/main.rs index b52e2ed..5436093 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,28 @@ mod des; +mod dfa; +mod file_helper; + use crate::des::des_encrypt; +use crate::dfa::{apply_parity, find_k16, find_k64}; +use crate::file_helper::load_vec_u64_hex; fn main() { - let clear = 0x0123456789ABCDEF; - let key = 0x0123456789ABCDEF; - let cipher = des_encrypt(clear, key); - println!("0x{:016x}", cipher); + let correct_values = load_vec_u64_hex("cleartextciphertext.txt"); + let clear = correct_values[0]; + let cipher = correct_values[1]; + + let faults = load_vec_u64_hex("faults.txt"); + + let k16 = find_k16(cipher, &faults); + println!("Recovered K16 = 0x{:012x}", k16); + + let k64_no_parity = find_k64(clear, cipher, k16); + let k64 = apply_parity(k64_no_parity); + println!("Full DES key = 0x{:016x}", k64); + + // verification + let test = des_encrypt(clear, k64); + println!("Verification: got ciphertext 0x{:016x}", test); + println!("correct ciphertext was 0x{:016x}", cipher); + println!("matching ciphertexts: {}", test == cipher); }