mirror of
https://github.com/kristoferssolo/cipher-workshop.git
synced 2025-12-20 11:04:38 +00:00
feat: add secret_int macro
This commit is contained in:
parent
27b31d1fcc
commit
e75eff8cd5
@ -11,6 +11,7 @@ des = { path = "des" }
|
||||
rand = "0.9"
|
||||
rstest = "0.26"
|
||||
thiserror = "2"
|
||||
zeroize = { version = "1.8", features = ["derive"] }
|
||||
|
||||
[workspace.lints.clippy]
|
||||
pedantic = "warn"
|
||||
|
||||
@ -28,7 +28,7 @@ pub trait BlockLike: Sized + Copy + Clone {
|
||||
/// Minimal trait describing a fixed-size key-like value.
|
||||
///
|
||||
/// Concrete key types (e.g. `Secret<const N: usize>`) should implement this.
|
||||
pub trait KeyLike: Sized + Copy + Clone {
|
||||
pub trait KeyLike: Sized {
|
||||
/// Size of the key in bytes.
|
||||
const SIZE: usize;
|
||||
|
||||
|
||||
@ -5,6 +5,8 @@ authors = ["Kristofers Solo <dev@kristofers.xyz>"]
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
cipher-core.workspace = true
|
||||
zeroize.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
claims.workspace = true
|
||||
|
||||
86
des/src/constants.rs
Normal file
86
des/src/constants.rs
Normal file
@ -0,0 +1,86 @@
|
||||
// DES Constants (from FIPS 46-3 spec)
|
||||
/// Initial Permutation (IP) table.
|
||||
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,
|
||||
];
|
||||
|
||||
/// Inverse Initial Permutation (FP) table.
|
||||
pub const FP: [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,
|
||||
34, 2, 42, 10, 50, 18, 58, 26, 33, 1, 41, 9, 49, 17, 57, 25,
|
||||
];
|
||||
|
||||
/// Expansion permutation (32 to 48 bits).
|
||||
pub const E_BOX: [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,
|
||||
];
|
||||
|
||||
/// P-box permutation (32 bits).
|
||||
pub const P_BOX: [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,
|
||||
];
|
||||
|
||||
/// S-boxes: 8 boxes, each 4x16 (row x col) -> 0-15.
|
||||
pub const S_BOXES: [[[u8; 16]; 4]; 8] = [
|
||||
// S1
|
||||
[
|
||||
[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],
|
||||
],
|
||||
// S2
|
||||
[
|
||||
[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],
|
||||
],
|
||||
// S3
|
||||
[
|
||||
[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],
|
||||
],
|
||||
// S4
|
||||
[
|
||||
[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],
|
||||
],
|
||||
// S5
|
||||
[
|
||||
[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],
|
||||
],
|
||||
// S6
|
||||
[
|
||||
[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],
|
||||
],
|
||||
// S7
|
||||
[
|
||||
[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],
|
||||
],
|
||||
// S8
|
||||
[
|
||||
[13, 2, 8, 4, 6, 15, 11, 1, 19, 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],
|
||||
],
|
||||
];
|
||||
14
des/src/des.rs
Normal file
14
des/src/des.rs
Normal file
@ -0,0 +1,14 @@
|
||||
use crate::key::subkeys::Subkeys;
|
||||
use cipher_core::KeyLike;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Des {
|
||||
subkeys: Subkeys,
|
||||
}
|
||||
|
||||
impl Des {
|
||||
#[must_use]
|
||||
pub fn new(_key: impl KeyLike) -> Self {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
19
des/src/key/mod.rs
Normal file
19
des/src/key/mod.rs
Normal file
@ -0,0 +1,19 @@
|
||||
mod secret_int;
|
||||
pub 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);
|
||||
}
|
||||
84
des/src/key/secret_int.rs
Normal file
84
des/src/key/secret_int.rs
Normal file
@ -0,0 +1,84 @@
|
||||
/// Macro to generate a masked, zeroizable integer wrapper type.
|
||||
///
|
||||
/// Usage:
|
||||
/// ```
|
||||
/// secret_int! {
|
||||
/// /// docs...
|
||||
/// pub struct Subkey(u64, 48, 0x0000_FFFF_FFFF_FFFFu64);
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Optional `clone` token enables an explicit Clone impl:
|
||||
/// ```
|
||||
/// secret_int! { pub struct Foo(u32, 28, 0x0FFF_FFFFu32, clone); }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! secret_int {
|
||||
(
|
||||
$(#[$meta:meta])*
|
||||
$vis:vis struct $name:ident ( $int:ty, $bits:expr, $mask:expr $(, $opt:ident )? );
|
||||
) => {
|
||||
$(#[$meta])*
|
||||
#[derive(::zeroize::ZeroizeOnDrop, Default, Eq)]
|
||||
#[zeroize(drop)]
|
||||
$vis struct $name($int);
|
||||
|
||||
impl $name {
|
||||
/// Number of meaningful bits.
|
||||
pub const BITS: usize = $bits;
|
||||
/// Mask to restrict the underlying integer to valid bits.
|
||||
pub const MASK: $int = $mask;
|
||||
|
||||
/// Create from the given integer.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn from_int(key: $int) -> Self {
|
||||
Self(key & Self::MASK)
|
||||
}
|
||||
|
||||
/// Return as masked integer value;
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn as_int(&self) -> $int {
|
||||
self.0 & Self::MASK
|
||||
}
|
||||
|
||||
/// Zero value.
|
||||
pub const fn zero() -> Self {
|
||||
Self(0)
|
||||
}
|
||||
}
|
||||
|
||||
// Optionally add Clone if requested explicitly (discouraged for secrets)
|
||||
secret_int!(@maybe_add_clone $( $opt )? ; $name, $int);
|
||||
|
||||
impl ::std::fmt::Debug for $name {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||
f.write_str(concat!(stringify!($name), "[REDACTED]"))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$int> for $name {
|
||||
fn from(v: $int) -> Self {
|
||||
Self::from_int(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for $name {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.as_int() == other.as_int()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// helper arm: create Clone impl only when "clone" token present
|
||||
(@maybe_add_clone clone ; $name:ident, $int:ty) => {
|
||||
impl Clone for $name {
|
||||
fn clone(&self) -> Self {
|
||||
// explicit clone - intentionally duplicating secret
|
||||
Self::from_int(self.as_int())
|
||||
}
|
||||
}
|
||||
};
|
||||
(@maybe_add_clone ; $name:ident, $int:ty) => {};
|
||||
}
|
||||
154
des/src/key/subkeys.rs
Normal file
154
des/src/key/subkeys.rs
Normal file
@ -0,0 +1,154 @@
|
||||
use crate::key::Subkey;
|
||||
use cipher_core::{CryptoError, CryptoResult, KeyLike};
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Key Permutation table (64 to 56 bits).
|
||||
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,
|
||||
];
|
||||
|
||||
/// Compression Permutation table (56 to 48 bits).
|
||||
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,
|
||||
];
|
||||
|
||||
/// Number of Key Bits Shifted per Round
|
||||
const ROUND_ROTATIONS: [u8; 16] = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1];
|
||||
|
||||
/// Container for all 16 round subkeys; zeroized on drop.
|
||||
pub struct Subkeys([Subkey; 16]);
|
||||
|
||||
impl Subkeys {
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn new_empty() -> Self {
|
||||
Self([const { Subkey::zero() }; 16])
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn as_array(&self) -> &[Subkey; 16] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set(&mut self, idx: usize, sk: Subkey) {
|
||||
self.0[idx] = sk;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn get(&self, idx: usize) -> Option<&Subkey> {
|
||||
self.0.get(idx)
|
||||
}
|
||||
|
||||
pub fn from_key(key: &impl KeyLike) -> CryptoResult<Self> {
|
||||
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));
|
||||
}
|
||||
|
||||
// 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 mut c = bits_to_u28(&c_bits);
|
||||
let mut d = bits_to_u28(&d_bits);
|
||||
|
||||
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
|
||||
},
|
||||
);
|
||||
|
||||
Ok(Self(out))
|
||||
}
|
||||
|
||||
pub(crate) fn as_u64_array(&self) -> [u64; 16] {
|
||||
self.0
|
||||
.iter()
|
||||
.enumerate()
|
||||
.fold([0; 16], |mut out, (idx, sk)| {
|
||||
out[idx] = sk.as_int();
|
||||
out
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
impl Debug for Subkeys {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str("Subkeys[REDACTED]")
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Subkeys {
|
||||
fn default() -> Self {
|
||||
Self::new_empty()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
pub(crate) mod constants;
|
||||
mod des;
|
||||
mod key;
|
||||
|
||||
pub use des::Des;
|
||||
Loading…
Reference in New Issue
Block a user