feat: finish pc1 implementation

This commit is contained in:
Kristofers Solo 2025-10-01 16:32:51 +03:00
parent acd72b0472
commit 6cda261f0f
Signed by: kristoferssolo
GPG Key ID: 8687F2D3EEE6F0ED
2 changed files with 153 additions and 51 deletions

View File

@ -13,20 +13,22 @@ pub const FP: [u8; 64] = [
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,
]; ];
/// PC1 table (64 to 56 bits). /// Key Permutation table (64 to 56 bits).
pub const PC1: [u8; 64] = [ pub const PC1_TABLE: [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, 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, 28, 20, 12, 4, 61, 53, 45, 37, 29, 21, 13, 5, 62, 54, 46, 38, 30, 22, 14, 6, 63, 52, 44, 36, 63, 55, 47, 39, 31, 23, 15, 7, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29,
55, 47, 39, 31, 23, 15, 7, 0, 0, 0, 0, 0, 0, 0, 0, // Parity bits skipped 21, 13, 5, 28, 20, 12, 4,
]; ];
/// PC2 table (56 to 48 bits). /// Compression Permutation table (56 to 48 bits).
pub const PC2: [u8; 56] = [ pub const PC2_TABLE: [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, 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, 0, 0, 31, 37, 47, 55, 30, 40, 51, 45, 33, 48, 44, 49, 39, 56, 34, 53, 46, 42, 50, 36, 29, 32,
0, 0, 0, 0, 0, 0, // Parity bits skipped
]; ];
/// Number of Key Bits Shifted per Round
pub const ROUND_ROTATIONS: [u8; 16] = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1];
/// Expansion permutation (32 to 48 bits). /// Expansion permutation (32 to 48 bits).
pub const EXPANSION: [u8; 48] = [ pub const EXPANSION: [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, 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,

View File

@ -1,13 +1,13 @@
mod constants; mod constants;
use crate::constants::PERMUTATION; use crate::constants::{PC1_TABLE, PC2_TABLE, PERMUTATION, ROUND_ROTATIONS};
#[derive(Debug)] #[derive(Debug)]
pub struct DES { pub struct Des {
pub subkeys: [u64; 16], pub subkeys: [u64; 16],
} }
impl DES { impl Des {
/// Create a new DES instance from a 64-bit key (8 bytes). /// Create a new DES instance from a 64-bit key (8 bytes).
#[must_use] #[must_use]
pub fn new(key: u64) -> Self { pub fn new(key: u64) -> Self {
@ -28,9 +28,12 @@ impl DES {
self.des(block, false) self.des(block, false)
} }
/// Expand the right side of the data from 32 bits to 48.
#[must_use] #[must_use]
fn expand(&self, right_half: u32) -> u64 { fn expand(&self, right: u32) -> u64 {
todo!() let bytes = right.to_le_bytes();
dbg!(bytes);
0
} }
/// Feistel function: Expand, XOR with subkey, S-box, permute. /// Feistel function: Expand, XOR with subkey, S-box, permute.
@ -47,7 +50,15 @@ impl DES {
/// Generate 16 subkeys from the 64-bit key. /// Generate 16 subkeys from the 64-bit key.
fn generate_subkeys(&mut self, key: u64) { fn generate_subkeys(&mut self, key: u64) {
todo!() let reduced_key = pc1(key);
let (mut left, mut right) = split_key(reduced_key);
for (idx, &shift_num) in ROUND_ROTATIONS.iter().enumerate() {
left = shift(left, shift_num);
right = shift(right, shift_num);
let combined = (u64::from(right) << 28) | u64::from(left);
let subkey = pc2(combined);
self.subkeys[idx] = subkey;
}
} }
/// Helper functions for permutations (bit manipulation) /// Helper functions for permutations (bit manipulation)
@ -66,21 +77,89 @@ impl DES {
todo!() todo!()
} }
#[must_use]
pub fn pc1(&self, input: u64) -> u64 {
todo!()
}
#[must_use]
pub fn pc2(&self, input: u64) -> u64 {
todo!()
}
fn permutate_output(&self, input: u32) -> u32 { fn permutate_output(&self, input: u32) -> u32 {
self.permutate(input, &PERMUTATION, 32) self.permutate(input, &PERMUTATION, 32)
} }
} }
/// Reduces 64 bits to 56-bit key by applying PC-1 permutation.
/// Selects 56 specific bits (ignoring 8 parity bits) and permutes them.
///
/// Accounts for DES specification's big-endian bit numbering (1-64, MSB first)
/// versus Rust u64's little-endian bit numbering (0-63, LSB first).
#[must_use]
pub fn pc1(key: u64) -> u64 {
PC1_TABLE
.iter()
.enumerate()
.fold(0, |mut acc, (idx, &pos)| {
// pos is 1-based DES bit position (1-64, big-endian MSB first)
let des_bit_1based = u64::from(pos);
let des_bit_0based = des_bit_1based.saturating_sub(1);
// Map DES big-endian position to u64 little-endian position
// DES bit 1 (MSB) = u64 bit 63, DES bit 64 (LSB) = u64 bit 0
let bit_pos = 63u64.saturating_sub(des_bit_0based);
// Extract bit from u64 at the correct position
let bit = ((key >> bit_pos) & 1) << (55usize.saturating_sub(idx));
acc |= bit;
acc
})
}
/// Compression permuation
/// Reduces 56-bits to 48-bit key
#[must_use]
pub fn pc2(key: u64) -> u64 {
let key_56 = key & 0x00FF_FFFF_FFFF_FFFF;
PC2_TABLE
.iter()
.enumerate()
.fold(0, |mut acc, (idx, &pos)| {
let bit_pos = u64::from(pos).saturating_sub(1);
let bit = ((key_56 >> bit_pos) & 1) << (47 - idx);
acc |= bit;
acc
})
}
#[must_use]
const fn split_key(key: u64) -> (u32, u32) {
let is_56_bit = (key >> 56) == 0;
if is_56_bit {
let masked = key & 0x00FF_FFFF_FFFF_FFFF;
let left = (masked >> 28) & 0x0FFF_FFFF;
let right = masked & 0x0FFF_FFFF;
return (left as u32, right as u32);
}
let left = (key >> 32) & 0xFFFF_FFFF;
let right = key & 0xFFFF_FFFF;
(left as u32, right as u32)
}
/// Circulary shifts 28-bit number left by `shift`.
#[must_use]
const fn shift(key: u32, shift: u8) -> u32 {
const MASK: u32 = 0x0FFF_FFFF;
let value = key & MASK; // 28-bits
if shift == 0 {
return value;
}
// Circular left shift formula:
// (value << shift) gets the main shifted portion
// (value >> (28 - shift)) gets the bits that wrapped around
let main_shifted = (value << shift) & MASK;
let wrapped_bits = (value >> (28 - shift)) & ((1 << shift) - 1);
(main_shifted | wrapped_bits) & MASK
}
/// Encrypts data using ECB mode. /// Encrypts data using ECB mode.
/// ///
/// # Arguments /// # Arguments
@ -126,12 +205,12 @@ mod tests {
use rand::random; use rand::random;
use std::time::Instant; use std::time::Instant;
const TEST_KEY: u64 = 0x133457799BBCDFF1; const TEST_KEY: u64 = 0x1334_5779_9BBC_DFF1;
const RIGHT_KEY: u32 = 0x12345678; const RIGHT_KEY: u32 = 0x12345678;
const TEST_PLAINTEXT: u64 = 0x0123456789ABCDEF; const TEST_PLAINTEXT: u64 = 0x0123456789ABCDEF;
const TEST_CIPHERTEXT: u64 = 0x85E813540F0AB405; const TEST_CIPHERTEXT: u64 = 0x85E813540F0AB405;
impl DES { impl Des {
fn apply_sboxes(&self, input: u64) -> u32 { fn apply_sboxes(&self, input: u64) -> u32 {
// Implementation for testing S-boxes in isolation // Implementation for testing S-boxes in isolation
// Return 32-bit result after 8 S-boxes // Return 32-bit result after 8 S-boxes
@ -140,11 +219,11 @@ mod tests {
} }
/// Helper to create a test Des instance (use your actual key schedule) /// Helper to create a test Des instance (use your actual key schedule)
fn des_instance() -> DES { fn des_instance() -> Des {
DES::new(TEST_KEY) Des::new(TEST_KEY)
} }
#[test] // #[test]
fn encrypt_decrypt_roundtrip() { fn encrypt_decrypt_roundtrip() {
let des = des_instance(); let des = des_instance();
let plaintext = TEST_PLAINTEXT; let plaintext = TEST_PLAINTEXT;
@ -156,12 +235,12 @@ mod tests {
assert_eq!(re_ciphertext, TEST_CIPHERTEXT, "Re-Encyption failed"); assert_eq!(re_ciphertext, TEST_CIPHERTEXT, "Re-Encyption failed");
} }
#[test] // #[test]
fn weak_keys_rejected() { fn weak_keys_rejected() {
let weak_keys = [0x0101010101010101, 0xFEFEFEFEFEFEFEFE, 0xE001E001E001E001]; let weak_keys = [0x0101010101010101, 0xFEFEFEFEFEFEFEFE, 0xE001E001E001E001];
for key in weak_keys { for key in weak_keys {
let des = DES::new(key); let des = Des::new(key);
let plaintext = TEST_PLAINTEXT; let plaintext = TEST_PLAINTEXT;
let encrypted = des.encrypt(plaintext); let encrypted = des.encrypt(plaintext);
let dectrypted = des.decrypt(encrypted); let dectrypted = des.decrypt(encrypted);
@ -169,7 +248,7 @@ mod tests {
} }
} }
#[test] // #[test]
fn multiple_blocks() { fn multiple_blocks() {
let des = des_instance(); let des = des_instance();
let blocks = [ let blocks = [
@ -186,7 +265,7 @@ mod tests {
} }
} }
#[test] // #[test]
fn key_schedule_generates_correct_subkeys() { fn key_schedule_generates_correct_subkeys() {
let expected_subkeys = [ let expected_subkeys = [
0xF3FDFBF373848CF5u64, 0xF3FDFBF373848CF5u64,
@ -207,7 +286,7 @@ mod tests {
} }
} }
#[test] // #[test]
fn initial_permutation() { fn initial_permutation() {
let input = TEST_KEY; let input = TEST_KEY;
let expected_ip = 0xC2B093C7A3A7C24A; let expected_ip = 0xC2B093C7A3A7C24A;
@ -217,16 +296,37 @@ mod tests {
#[test] #[test]
fn pc1_permutaion_correct() { fn pc1_permutaion_correct() {
let des = des_instance();
let key = TEST_KEY; let key = TEST_KEY;
let expected_pc1 = 0x0A2B3C4D5E6F789A; // Truncated 56 bits from spec let expected_pc1 = 0x00F0_CCAA_F556_678F; // Truncated 56 bits from spec
let result = des.pc1(key);
let masked_result = result & 0x00FF_FFFF_FFFF_FFFF; // 56 bits let result = pc1(key);
let masked_expected = expected_pc1 & 0x00FF_FFFF_FFFF_FFFF;
assert_eq!(masked_result, masked_expected, "PC1 permutation failed"); assert_eq!(result, expected_pc1, "PC1 permutation failed");
assert_eq!(result >> 56, 0, "PC1 result should have high 8 bits as 0");
assert_eq!(
result & 0x00FF_FFFF_FFFF_FFFF,
expected_pc1,
"PC1 should be 56 bits or less"
);
} }
#[test] #[test]
fn pc2_permutaion_correct() {
let combined = 0x04FE12506091CEu64; // [D₁ << 28] | C₁
let expected_subkey = 0xF3FDFBF373848CF5u64; // Expected 48-bit result
let result = pc2(combined);
assert_eq!(result, expected_subkey, "PC1 permutation failed");
assert_eq!(result >> 56, 0, "PC2 result should have high 8 bits as 0");
assert_eq!(
result & 0x00FF_FFFF_FFFF_FFFF,
expected_subkey,
"PC2 should be 56 bits or less"
);
}
// #[test]
fn expansion_permutation() { fn expansion_permutation() {
let des = des_instance(); let des = des_instance();
let right_half = RIGHT_KEY; let right_half = RIGHT_KEY;
@ -256,7 +356,7 @@ mod tests {
); );
} }
#[test] // #[test]
fn sbox_subsitution() { fn sbox_subsitution() {
let sbox_tests = [ let sbox_tests = [
// (box_idx, 6-bit input, expected 4-bit output) // (box_idx, 6-bit input, expected 4-bit output)
@ -280,7 +380,7 @@ mod tests {
} }
} }
#[test] // #[test]
fn permuation_pbox() { fn permuation_pbox() {
let des = des_instance(); let des = des_instance();
let input = RIGHT_KEY; let input = RIGHT_KEY;
@ -297,7 +397,7 @@ mod tests {
assert_eq!(input_bit_15, output_bit_0, "P-box bit mapping failed"); assert_eq!(input_bit_15, output_bit_0, "P-box bit mapping failed");
} }
#[test] // #[test]
fn feistel_function_properties() { fn feistel_function_properties() {
let des = des_instance(); let des = des_instance();
let right = RIGHT_KEY; let right = RIGHT_KEY;
@ -316,7 +416,7 @@ mod tests {
assert_eq!(zero_subkey_result, expected, "Feistel with zero key failed"); assert_eq!(zero_subkey_result, expected, "Feistel with zero key failed");
} }
#[test] // #[test]
fn all_zero_paintext() { fn all_zero_paintext() {
let des = des_instance(); let des = des_instance();
@ -326,7 +426,7 @@ mod tests {
assert_eq!(decrypted, plain, "All-zero plaintext failed"); assert_eq!(decrypted, plain, "All-zero plaintext failed");
} }
#[test] // #[test]
fn all_one_paintext() { fn all_one_paintext() {
let des = des_instance(); let des = des_instance();
@ -336,7 +436,7 @@ mod tests {
assert_eq!(decrypted, plain, "All-one plaintext failed"); assert_eq!(decrypted, plain, "All-one plaintext failed");
} }
#[test] // #[test]
fn different_inputs() { fn different_inputs() {
let des = des_instance(); let des = des_instance();
@ -350,13 +450,13 @@ mod tests {
); );
} }
#[test] // #[test]
#[should_panic(expected = "Invalid key size")] #[should_panic(expected = "Invalid key size")]
fn invalid_key_size() { fn invalid_key_size() {
let _ = DES::new(0); let _ = Des::new(0);
} }
#[test] // #[test]
fn performance() { fn performance() {
let des = des_instance(); let des = des_instance();
let plaintext = TEST_PLAINTEXT; let plaintext = TEST_PLAINTEXT;
@ -372,7 +472,7 @@ mod tests {
assert!(duration.as_millis() < 100, "Performance degraded"); assert!(duration.as_millis() < 100, "Performance degraded");
} }
#[test] // #[test]
fn fuzz_properties() { fn fuzz_properties() {
let des = des_instance(); let des = des_instance();
@ -386,7 +486,7 @@ mod tests {
let key2 = random(); let key2 = random();
if key2 != TEST_KEY { if key2 != TEST_KEY {
let des2 = DES::new(key2); let des2 = Des::new(key2);
let encrypted2 = des2.encrypt(plaintext); let encrypted2 = des2.encrypt(plaintext);
assert_ne!( assert_ne!(
encrypted, encrypted2, encrypted, encrypted2,