mirror of
https://github.com/kristoferssolo/cipher-workshop.git
synced 2025-12-20 11:04:38 +00:00
feat: add Key56 and Half28
This commit is contained in:
parent
e75eff8cd5
commit
9e91e90303
@ -1,5 +1,5 @@
|
||||
use crate::key::subkeys::Subkeys;
|
||||
use cipher_core::KeyLike;
|
||||
use crate::key::Subkeys;
|
||||
use cipher_core::{CryptoResult, KeyLike};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Des {
|
||||
@ -7,8 +7,8 @@ pub struct Des {
|
||||
}
|
||||
|
||||
impl Des {
|
||||
#[must_use]
|
||||
pub fn new(_key: impl KeyLike) -> Self {
|
||||
todo!()
|
||||
pub fn new(key: &impl KeyLike) -> CryptoResult<Self> {
|
||||
let subkeys = Subkeys::from_key(key)?;
|
||||
Ok(Self { subkeys })
|
||||
}
|
||||
}
|
||||
|
||||
65
des/src/key/half28.rs
Normal file
65
des/src/key/half28.rs
Normal file
@ -0,0 +1,65 @@
|
||||
use crate::key::secret_int;
|
||||
|
||||
secret_int! {
|
||||
/// 28-bit half (C or D), stored in lower 28 bits of u32.
|
||||
pub struct Half28(u32, 28, 0x0FFF_FFFF);
|
||||
}
|
||||
|
||||
impl Half28 {
|
||||
#[must_use]
|
||||
pub const fn rotate_left(&self, amount: u8) -> Self {
|
||||
let value = self.0;
|
||||
let main_shifted = (value << amount) & Self::MASK;
|
||||
let wrapped_bits = (value >> (28 - amount)) & ((1 << amount) - 1);
|
||||
Self::from_int(main_shifted | wrapped_bits)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rstest::rstest;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[rstest]
|
||||
#[case(0x0F0C_CAAF, 0x0E19_955F, 1)] // C_1
|
||||
#[case(0x0E19_955F, 0x0C33_2ABF, 1)] // C_2
|
||||
#[case(0x0C33_2ABF, 0x00CC_AAFF, 2)] // C_3
|
||||
#[case(0x00CC_AAFF, 0x0332_ABFC, 2)] // C_4
|
||||
#[case(0x0332_ABFC, 0x0CCA_AFF0, 2)] // C_5
|
||||
#[case(0x0CCA_AFF0, 0x032A_BFC3, 2)] // C_6
|
||||
#[case(0x032A_BFC3, 0x0CAA_FF0C, 2)] // C_7
|
||||
#[case(0x0CAA_FF0C, 0x02AB_FC33, 2)] // C_8
|
||||
#[case(0x02AB_FC33, 0x0557_F866, 1)] // C_9
|
||||
#[case(0x0557_F866, 0x055F_E199, 2)] // C_10
|
||||
#[case(0x055F_E199, 0x057F_8665, 2)] // C_11
|
||||
#[case(0x057F_8665, 0x05FE_1995, 2)] // C_12
|
||||
#[case(0x05FE_1995, 0x07F8_6655, 2)] // C_13
|
||||
#[case(0x07F8_6655, 0x0FE1_9955, 2)] // C_14
|
||||
#[case(0x0FE1_9955, 0x0F86_6557, 2)] // C_15
|
||||
#[case(0x0F86_6557, 0x0F0C_CAAF, 1)] // C_16
|
||||
#[case(0x0556_678F, 0x0AAC_CF1E, 1)] // D_1
|
||||
#[case(0x0AAC_CF1E, 0x0559_9E3D, 1)] // D_2
|
||||
#[case(0x0559_9E3D, 0x0566_78F5, 2)] // D_3
|
||||
#[case(0x0566_78F5, 0x0599_E3D5, 2)] // D_4
|
||||
#[case(0x0599_E3D5, 0x0667_8F55, 2)] // D_5
|
||||
#[case(0x0667_8F55, 0x099E_3D55, 2)] // D_6
|
||||
#[case(0x099E_3D55, 0x0678_F556, 2)] // D_7
|
||||
#[case(0x0678_F556, 0x09E3_D559, 2)] // D_8
|
||||
#[case(0x09E3_D559, 0x03C7_AAB3, 1)] // D_9
|
||||
#[case(0x03C7_AAB3, 0x0F1E_AACC, 2)] // D_10
|
||||
#[case(0x0F1E_AACC, 0x0C7A_AB33, 2)] // D_11
|
||||
#[case(0x0C7A_AB33, 0x01EA_ACCF, 2)] // D_12
|
||||
#[case(0x01EA_ACCF, 0x07AA_B33C, 2)] // D_13
|
||||
#[case(0x07AA_B33C, 0x0EAA_CCF1, 2)] // D_14
|
||||
#[case(0x0EAA_CCF1, 0x0AAB_33C7, 2)] // D_15
|
||||
#[case(0x0AAB_33C7, 0x0556_678F, 1)] // D_16
|
||||
fn half28_rotation(#[case] key: u32, #[case] expected: u32, #[case] amount: u8) {
|
||||
let result = Half28::from_int(key).rotate_left(amount).as_int();
|
||||
|
||||
assert_eq!(
|
||||
result, expected,
|
||||
"shift(0x{key:08X}, {amount}) should equal 0x{expected:08X}"
|
||||
);
|
||||
}
|
||||
}
|
||||
36
des/src/key/key56.rs
Normal file
36
des/src/key/key56.rs
Normal file
@ -0,0 +1,36 @@
|
||||
use crate::{key::half28::Half28, secret_int};
|
||||
|
||||
secret_int! {
|
||||
/// 56-bit key after PC-1 (lower 56 bits used).
|
||||
pub struct Key56(u64, 56, 0x00FF_FFFF_FFFF_FFFF);
|
||||
}
|
||||
|
||||
impl Key56 {
|
||||
#[must_use]
|
||||
pub fn split(&self) -> (Half28, Half28) {
|
||||
let c = ((self.0 >> 28) & 0x0FFF_FFFF) as u32;
|
||||
let d = (self.0 & 0x0FFF_FFFF) as u32;
|
||||
(c.into(), d.into())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn from_half28(left: &Half28, right: &Half28) -> Self {
|
||||
let left = u64::from(left.as_int());
|
||||
let right = u64::from(right.as_int());
|
||||
Self::from_int((left << 28) | right)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[Half28; 2]> for Key56 {
|
||||
fn from(keys: [Half28; 2]) -> Self {
|
||||
let [left, right] = keys;
|
||||
Self::from_half28(&left, &right)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[Half28; 2]> for Key56 {
|
||||
fn from(keys: &[Half28; 2]) -> Self {
|
||||
let [left, right] = keys;
|
||||
Self::from_half28(left, right)
|
||||
}
|
||||
}
|
||||
@ -1,19 +1,8 @@
|
||||
mod half28;
|
||||
mod key56;
|
||||
mod secret_int;
|
||||
pub mod subkeys;
|
||||
mod subkey;
|
||||
mod subkeys;
|
||||
|
||||
use crate::secret_int;
|
||||
|
||||
secret_int! {
|
||||
/// A single DES round subkey (48 bits stored in lower bits of u64).
|
||||
pub struct Subkey(u64, 48, 0x0000_FFFF_FFFF_FFFF);
|
||||
}
|
||||
|
||||
secret_int! {
|
||||
/// 56-bit key after PC-1 (lower 56 bits used).
|
||||
pub struct Key56(u64, 56, 0x00FF_FFFF_FFFF_FFFF);
|
||||
}
|
||||
|
||||
secret_int! {
|
||||
/// 28-bit half (C or D), stored in lower 28 bits of u32.
|
||||
pub struct Half28(u32, 28, 0x0FFF_FFFF);
|
||||
}
|
||||
pub use subkeys::Subkeys;
|
||||
|
||||
6
des/src/key/subkey.rs
Normal file
6
des/src/key/subkey.rs
Normal file
@ -0,0 +1,6 @@
|
||||
use crate::key::secret_int;
|
||||
|
||||
secret_int! {
|
||||
/// A single DES round subkey (48 bits stored in lower bits of u64).
|
||||
pub struct Subkey(u64, 48, 0x0000_FFFF_FFFF_FFFF);
|
||||
}
|
||||
@ -1,4 +1,7 @@
|
||||
use crate::key::Subkey;
|
||||
use crate::{
|
||||
key::{key56::Key56, subkey::Subkey},
|
||||
utils::permutate,
|
||||
};
|
||||
use cipher_core::{CryptoError, CryptoResult, KeyLike};
|
||||
use std::fmt::Debug;
|
||||
|
||||
@ -49,39 +52,28 @@ impl Subkeys {
|
||||
let key_bytes = key.as_bytes();
|
||||
let key_len = key_bytes.len();
|
||||
|
||||
if key_len != 8 {
|
||||
return Err(CryptoError::invalid_key_size(8, key_len));
|
||||
}
|
||||
let key_arr = key_bytes
|
||||
.try_into()
|
||||
.map_err(|_| CryptoError::invalid_key_size(8, key_len))?;
|
||||
|
||||
// Produce 56 bits after PC-1 as two 28-bit halves C0 and D0 (MSB-first bit order)
|
||||
let (c_bits, d_bits) = PC1.iter().enumerate().fold(
|
||||
([0; 28], [0; 28]),
|
||||
|(mut c_bits, mut d_bits), (idx, &pos)| {
|
||||
let bit = get_bit_be(key_bytes, pos);
|
||||
if idx < 28 {
|
||||
c_bits[idx] = bit;
|
||||
} else {
|
||||
d_bits[idx - 28] = bit;
|
||||
}
|
||||
(c_bits, d_bits)
|
||||
},
|
||||
);
|
||||
let key_be = u64::from_be_bytes(key_arr);
|
||||
|
||||
let mut c = bits_to_u28(&c_bits);
|
||||
let mut d = bits_to_u28(&d_bits);
|
||||
let cd_56 = pc1(key_be); // 56-bit: C0 + D0
|
||||
let (c, d) = cd_56.split();
|
||||
|
||||
let out = ROUND_ROTATIONS.iter().enumerate().fold(
|
||||
[const { Subkey::zero() }; 16],
|
||||
|mut acc, (round, &shifts)| {
|
||||
c = rotate_left_28(c, shifts.into());
|
||||
d = rotate_left_28(d, shifts.into());
|
||||
let sub48 = pc2_from_cd(c, d);
|
||||
acc[round] = Subkey::from(sub48);
|
||||
acc
|
||||
},
|
||||
);
|
||||
let subkeys = ROUND_ROTATIONS
|
||||
.iter()
|
||||
.map(|&shift_amount| {
|
||||
let cn = c.rotate_left(shift_amount); // C_(n-1) -> C_n
|
||||
let dn = d.rotate_left(shift_amount); // D_(n-1) -> D_n
|
||||
let combined = [cn, dn].into();
|
||||
pc2(&combined)
|
||||
})
|
||||
.collect::<Vec<Subkey>>()
|
||||
.try_into()
|
||||
.expect("Exactly 16 subkeys expected");
|
||||
|
||||
Ok(Self(out))
|
||||
Ok(Self(subkeys))
|
||||
}
|
||||
|
||||
pub(crate) fn as_u64_array(&self) -> [u64; 16] {
|
||||
@ -95,50 +87,12 @@ impl Subkeys {
|
||||
}
|
||||
}
|
||||
|
||||
fn pc2_from_cd(c: u32, d: u32) -> u64 {
|
||||
let combined: [u8; 56] = (0..28)
|
||||
.flat_map(|idx| {
|
||||
let bit_idx = 27 - idx;
|
||||
[((c >> bit_idx) & 1) as u8, ((d >> bit_idx) & 1) as u8]
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.try_into()
|
||||
.expect("");
|
||||
|
||||
let out = PC2.iter().fold(0, |acc, &pos| {
|
||||
let bit = u64::from(combined[(pos as usize).saturating_sub(1)]);
|
||||
(acc << 1) | bit
|
||||
});
|
||||
|
||||
out & 0xFFFF_FFFF_FFFF
|
||||
fn pc1(key: u64) -> Key56 {
|
||||
permutate(key, 64, 56, &PC1).into()
|
||||
}
|
||||
|
||||
const U28_MASK: u32 = 0x0FFF_FFFF;
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
const fn get_bit_be(bytes: &[u8], pos: u8) -> u8 {
|
||||
let p = (pos as usize).saturating_sub(1);
|
||||
let byte_idx = p / 8;
|
||||
let bit_idx = 7 - (p % 8);
|
||||
(bytes[byte_idx] >> bit_idx) & 1
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
fn bits_to_u28(bits: &[u8; 28]) -> u32 {
|
||||
let mut v = 0;
|
||||
for &b in bits {
|
||||
v = (v << 1) | (u32::from(b));
|
||||
}
|
||||
v & U28_MASK
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
const fn rotate_left_28(v: u32, n: u32) -> u32 {
|
||||
let v = v & U28_MASK;
|
||||
((v << n) | (v >> (28 - n))) & U28_MASK
|
||||
fn pc2(key: &Key56) -> Subkey {
|
||||
permutate(key.as_int(), 56, 48, &PC2).into()
|
||||
}
|
||||
|
||||
impl Debug for Subkeys {
|
||||
@ -152,3 +106,48 @@ impl Default for Subkeys {
|
||||
Self::new_empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rstest::rstest;
|
||||
|
||||
const TEST_KEY: u64 = 0x1334_5779_9BBC_DFF1;
|
||||
const TEST_PC1_RESULT: u64 = 0x00F0_CCAA_F556_678F;
|
||||
|
||||
#[rstest]
|
||||
#[case(TEST_KEY, TEST_PC1_RESULT)]
|
||||
fn pc1_permutaion_correct(#[case] key: u64, #[case] expected: u64) {
|
||||
let result = pc1(key);
|
||||
|
||||
assert_eq!(
|
||||
result.as_int(),
|
||||
expected,
|
||||
"PC1 permutation failed. Expected {expected:08X}, got {:08X}",
|
||||
result.as_int()
|
||||
);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(0x00F0_CCAA_F556_678F, 0xCB3D_8B0E_17F5)] // K_0
|
||||
#[case(0x00E1_9955_FAAC_CF1E, 0x1B02_EFFC_7072)] // K_1
|
||||
#[case(0x00C3_32AB_F559_9E3D, 0x79AE_D9DB_C9E5)] // K_2
|
||||
#[case(0x000C_CAAF_F566_78F5, 0x55FC_8A42_CF99)] // K_3
|
||||
#[case(0x0033_2ABF_C599_E3D5, 0x72AD_D6DB_351D)] // K_4
|
||||
#[case(0x00CC_AAFF_0667_8F55, 0x7CEC_07EB_53A8)] // K_5
|
||||
#[case(0x0032_ABFC_399E_3D55, 0x63A5_3E50_7B2F)] // K_6
|
||||
#[case(0x00CA_AFF0_C678_F556, 0xEC84_B7F6_18BC)] // K_7
|
||||
#[case(0x002A_BFC3_39E3_D559, 0xF78A_3AC1_3BFB)] // K_8
|
||||
#[case(0x0055_7F86_63C7_AAB3, 0xE0DB_EBED_E781)] // K_9
|
||||
#[case(0x0055_FE19_9F1E_AACC, 0xB1F3_47BA_464F)] // K_10
|
||||
#[case(0x0057_F866_5C7A_AB33, 0x215F_D3DE_D386)] // K_11
|
||||
#[case(0x005F_E199_51EA_ACCF, 0x7571_F594_67E9)] // K_12
|
||||
#[case(0x007F_8665_57AA_B33C, 0x97C5_D1FA_BA41)] // K_13
|
||||
#[case(0x00FE_1995_5EAA_CCF1, 0x5F43_B7F2_E73A)] // K_14
|
||||
#[case(0x00F8_6655_7AAB_33C7, 0xBF91_8D3D_3F0A)] // K_15
|
||||
#[case(0x00F0_CCAA_F556_678F, 0xCB3D_8B0E_17F5)] // K_16
|
||||
fn pc2_permutaion(#[case] before: u64, #[case] after: u64) {
|
||||
let result = pc2(&before.into());
|
||||
assert_eq!(result.as_int(), after, "PC2 permutation failed");
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
pub(crate) mod constants;
|
||||
mod des;
|
||||
mod key;
|
||||
pub(crate) mod utils;
|
||||
|
||||
pub use des::Des;
|
||||
pub use {des::Des, key::Subkeys};
|
||||
|
||||
44
des/src/utils.rs
Normal file
44
des/src/utils.rs
Normal file
@ -0,0 +1,44 @@
|
||||
/// Generic bit permutation for DES specification.
|
||||
/// DES uses 1-based positioning where bit 1 is the leftmost (MSB) and bit 64 is the rightmost (LSB).
|
||||
/// This function handles this correctly for arbitrary input/output sizes.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `input` - The input value (treated as a bitfield of `input_bits` size)
|
||||
/// - `input_bits` - Number of meaningful bits in the input (1-64)
|
||||
/// - `output_bits` - Number of bits in the output (1-64)
|
||||
/// - `position_table` - Table of 1-based positions (1 to `input_bits`) where each output bit comes from.
|
||||
/// The table should have `output_bits` entries. For each entry i (0-based), `position_table`[i] indicates
|
||||
/// which input bit (1-based) should go to the i-th output bit position (from MSB to LSB).
|
||||
#[must_use]
|
||||
pub fn permutate(
|
||||
input: u64,
|
||||
input_bit_amount: u64,
|
||||
output_bit_amount: u64,
|
||||
position_table: &[u8],
|
||||
) -> u64 {
|
||||
debug_assert!(position_table.len() as u64 == output_bit_amount);
|
||||
debug_assert!(output_bit_amount <= 64);
|
||||
debug_assert!(input_bit_amount <= 64);
|
||||
|
||||
position_table
|
||||
.iter()
|
||||
.enumerate()
|
||||
.fold(0, |acc, (idx, &input_pos_1based)| {
|
||||
// Convert 1-based DES position to 0-based input position (MSB first)
|
||||
let input_pos_from_msb_0based = u64::from(input_pos_1based).saturating_sub(1);
|
||||
let input_bit_pos = input_bit_amount
|
||||
.saturating_sub(1)
|
||||
.saturating_sub(input_pos_from_msb_0based);
|
||||
|
||||
// Extract bit from input
|
||||
let bit_value = (input >> input_bit_pos) & 1;
|
||||
|
||||
// Extract bit from u64 at the correct position
|
||||
let output_bit_pos = output_bit_amount
|
||||
.saturating_sub(1)
|
||||
.saturating_sub(idx as u64);
|
||||
|
||||
let shifted_bit = bit_value << output_bit_pos;
|
||||
acc | shifted_bit
|
||||
})
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user