This commit is contained in:
Sam Hadow 2025-04-29 11:03:48 +02:00
parent 21008f63fe
commit 83f71b100b
4 changed files with 252 additions and 65 deletions

View File

@ -1,101 +1,94 @@
//! A Rust implementation of the Data Encryption Standard (DES) //! A Rust implementation of the Data Encryption Standard (DES)
const E: [u8; 48] = [ 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, 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,
16, 17, 18, 19, 20, 21, 20, 21, 22, 23, 24, 25, 24, 25, 26, 27, 28, 29, 28, 29, 30, 31, 32, 1, 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] = [ const P: [u8; 32] = [
16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23, 26, 5, 18, 31, 10, 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,
2, 8, 24, 14, 32, 27, 3, 9, 19, 13, 30, 6, 22, 11, 4, 25, 13, 30, 6, 22, 11, 4, 25,
]; ];
const IP: [u8; 64] = [ pub const IP: [u8; 64] = [
58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4, 58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6,
62, 54, 46, 38, 30, 22, 14, 6, 64, 56, 48, 40, 32, 24, 16, 8, 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,
57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7,
61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7,
]; ];
const IP_INV: [u8; 64] = [ const IP_INV: [u8; 64] = [
40, 8, 48, 16, 56, 24, 64, 32, 39, 7, 47, 15, 55, 23, 63, 31, 40, 8, 48, 16, 56, 24, 64, 32, 39, 7, 47, 15, 55, 23, 63, 31, 38, 6, 46, 14, 54, 22, 62, 30,
38, 6, 46, 14, 54, 22, 62, 30, 37, 5, 45, 13, 53, 21, 61, 29, 37, 5, 45, 13, 53, 21, 61, 29, 36, 4, 44, 12, 52, 20, 60, 28, 35, 3, 43, 11, 51, 19, 59, 27,
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, 34, 2, 42, 10, 50, 18, 58, 26, 33, 1, 41, 9, 49, 17, 57, 25,
]; ];
const PC1: [u8; 56] = [ pub const PC1: [u8; 56] = [
57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 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,
10, 2, 59, 51, 43, 35, 27, 19, 11, 3, 60, 52, 44, 36, 52, 44, 36, 63, 55, 47, 39, 31, 23, 15, 7, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29,
63, 55, 47, 39, 31, 23, 15, 7, 62, 54, 46, 38, 30, 22, 21, 13, 5, 28, 20, 12, 4,
14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 28, 20, 12, 4,
]; ];
const PC2: [u8; 48] = [ pub const PC2: [u8; 48] = [
14, 17, 11, 24, 1, 5, 3, 28, 15, 6, 21, 10, 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,
23, 19, 12, 4, 26, 8, 16, 7, 27, 20, 13, 2, 31, 37, 47, 55, 30, 40, 51, 45, 33, 48, 44, 49, 39, 56, 34, 53, 46, 42, 50, 36, 29, 32,
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] = [ const SHIFTS: [u8; 16] = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1];
1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1,
];
// S-boxes // 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], [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], [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], [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, 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], [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], [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], [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], [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], [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, 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], [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], [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], [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], [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], [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], [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], [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], [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], [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], [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], [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], [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], [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, 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], [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], [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], [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], [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], [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], [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], [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], [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`. /// 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 mut out = 0;
let len = table.len() as u8; let len = table.len() as u8;
for (i, &t) in table.iter().enumerate() { 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. /// 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) (((x >> 32) & 0xFFFFFFFF) as u32, (x & 0xFFFFFFFF) as u32)
} }

165
src/dfa.rs Normal file
View File

@ -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 Sboxes were affected by each fault
fn select_faults(correct: u64, faults: &[u64]) -> HashMap<usize, Vec<usize>> {
let mut map: HashMap<usize, Vec<usize>> = (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<u8>
fn common(lists: &[Vec<u8>]) -> u8 {
let mut set: std::collections::HashSet<u8> = 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 sbox `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 sbox `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 sbox 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 round16 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<u8>> = 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
}
/// Bruteforce the 8 missing bits to recover the 64bit 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
}

10
src/file_helper.rs Normal file
View File

@ -0,0 +1,10 @@
use std::fs;
/// Read multiple hexencoded u64s (one per line)
pub fn load_vec_u64_hex(path: &str) -> Vec<u64> {
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()
}

View File

@ -1,9 +1,28 @@
mod des; mod des;
mod dfa;
mod file_helper;
use crate::des::des_encrypt; use crate::des::des_encrypt;
use crate::dfa::{apply_parity, find_k16, find_k64};
use crate::file_helper::load_vec_u64_hex;
fn main() { fn main() {
let clear = 0x0123456789ABCDEF; let correct_values = load_vec_u64_hex("cleartextciphertext.txt");
let key = 0x0123456789ABCDEF; let clear = correct_values[0];
let cipher = des_encrypt(clear, key); let cipher = correct_values[1];
println!("0x{:016x}", cipher);
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);
} }