feat(des): implement basic encryption

This commit is contained in:
Kristofers Solo 2025-10-17 17:14:43 +03:00
parent 9e91e90303
commit 6175305641
Signed by: kristoferssolo
GPG Key ID: 8687F2D3EEE6F0ED
22 changed files with 866 additions and 295 deletions

View File

@ -1,7 +1,7 @@
use thiserror::Error;
#[derive(Debug, Error, Clone, Copy, PartialEq, Eq)]
pub enum CryptoError {
pub enum CipherError {
/// Invalid key size for the cipher
#[error("invalid key size: expected {expected} bytes, got {actual}")]
InvalidKeySize { expected: usize, actual: usize },
@ -23,7 +23,7 @@ pub enum CryptoError {
InvalidSize { expected: usize, actual: usize },
}
impl CryptoError {
impl CipherError {
/// Creates a key size error
#[inline]
#[must_use]
@ -97,4 +97,4 @@ impl CryptoError {
}
/// Type alias for clean Result types
pub type CryptoResult<T> = core::result::Result<T, CryptoError>;
pub type CipherResult<T> = core::result::Result<T, CipherError>;

View File

@ -1,5 +1,9 @@
mod error;
mod traits;
mod types;
pub use error::{CryptoError, CryptoResult};
pub use traits::{BlockCipher, BlockLike, CipherContext, KeyInit, KeyLike, StreamCipher};
pub use {
error::{CipherError, CipherResult},
traits::BlockCipher,
types::CipherAction,
};

View File

@ -1,192 +1,24 @@
use crate::CryptoResult;
use crate::{CipherAction, CipherError, CipherResult};
/// Minimal trait describing a fixed-size block-like value.
///
/// Concrete block types (e.g. `Block<const N: usize>`) should implement this.
pub trait BlockLike: Sized + Copy + Clone {
/// Size of the block in bytes.
const SIZE: usize;
/// Create from exactly SIZE bytes.
///
/// # Errors
///
/// Returns a `CryptoError::InvalidBlockSize` (or equivalent) when
/// `bytes.len() != Self::SIZE`.
fn from_bytes(bytes: &[u8]) -> CryptoResult<Self>;
/// Immutable view of the underlying bytes.
fn as_bytes(&self) -> &[u8];
/// Mutable view of underlying bytes.
fn as_bytes_mut(&mut self) -> &mut [u8];
/// Create a zeroed block value.
fn zeroed() -> Self;
}
/// 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 {
/// Size of the key in bytes.
const SIZE: usize;
/// Create key from exactly SIZE bytes.
///
/// # Errors
///
/// Returns a `CryptoError::InvalidKeySize` when `bytes.len() != Self::SIZE`.
fn from_bytes(bytes: &[u8]) -> CryptoResult<Self>;
/// Immutable view of the key bytes.
fn as_bytes(&self) -> &[u8];
}
/// Core block-cipher trait using [`KeyLike`] and [`BlockLike`].
///
/// The primary (performance-oriented) methods are the in-place variants.
/// Default owned-returning methods are implemented in terms of the in-place
/// ones and therefore require `BlockLike: Copy`.
pub trait BlockCipher: Sized {
/// Key and Block concrete associated types
type Key: KeyLike;
type Block: BlockLike;
const BLOCK_SIZE: usize;
/// Construct a cipher instance from a key.
///
/// # Errors
///
/// Returns a `CryptoError` if the key is invalid for the cipher.
fn new(key: Self::Key) -> CryptoResult<Self>;
fn transform_impl(&self, block: &[u8], action: CipherAction) -> CipherResult<Vec<u8>>;
/// Encrypt in-place (primary implementation target).
///
/// # Errors
///
/// Returns a `CryptoError` on failure (e.g. internal state issues).
fn encrypt_inplace(&self, block: &mut Self::Block) -> CryptoResult<()>;
/// Decrypt in-place (primary implementation target).
///
/// # Errors
///
/// Returns a `CryptoError` on failure (e.g. invalid padding after decrypt).
fn decrypt_inplace(&self, block: &mut Self::Block) -> CryptoResult<()>;
/// Encrypt returning a new block.
///
/// Default implementation copies the input block and calls
/// `encrypt_inplace`.
///
/// # Errors
///
/// Propagates errors returned by `encrypt_inplace`.
fn encrypt(&self, block: &Self::Block) -> CryptoResult<Self::Block> {
let mut out = *block;
self.encrypt_inplace(&mut out)?;
Ok(out)
fn transform(&self, block: &[u8], action: CipherAction) -> CipherResult<Vec<u8>> {
if block.len() != Self::BLOCK_SIZE {
return Err(CipherError::invalid_block_size(
Self::BLOCK_SIZE,
block.len(),
));
}
self.transform_impl(block, action)
}
/// Decrypt returning a new block.
///
/// Default implementation copies the input block and calls
/// `decrypt_inplace`.
///
/// # Errors
///
/// Propagates errors returned by `decrypt_inplace`.
fn decrypt(&self, block: &Self::Block) -> CryptoResult<Self::Block> {
let mut out = *block;
self.decrypt_inplace(&mut out)?;
Ok(out)
}
}
/// Helper trait: initialize a cipher from raw key bytes.
///
/// The default implementation converts the slice into `Self::Key` then calls
/// `Self::new`. Implementations may override to perform custom validation.
pub trait KeyInit: BlockCipher + Sized {
/// Construct the cipher from raw key bytes.
///
/// # Errors
///
/// Returns `CryptoError::InvalidKeySize` if the slice length doesn't match
fn new_from_slice(key_bytes: &[u8]) -> CryptoResult<Self> {
let key = <Self::Key as KeyLike>::from_bytes(key_bytes)?;
Self::new(key)
}
}
/// Stream-like cipher/mode trait (CTR, OFB, stream ciphers).
///
/// Implementations apply keystream to arbitrary-length buffers in-place.
pub trait StreamCipher {
/// XOR keystream with `data` in-place (encrypt == decrypt).
fn apply_keystream(&mut self, data: &mut [u8]);
}
/// Small convenience wrapper that stores a cipher and forwards single-block
/// operations using the associated Block type.
pub struct CipherContext<C>
where
C: BlockCipher,
{
cipher: C,
}
impl<C> CipherContext<C>
where
C: BlockCipher,
{
/// Wrap an existing cipher instance.
pub const fn new(cipher: C) -> Self {
Self { cipher }
}
/// Encrypt a block, returning a new block.
///
/// # Errors
///
/// Propagates errors from the cipher.
pub fn encrypt(&self, block: &C::Block) -> CryptoResult<C::Block> {
self.cipher.encrypt(block)
}
/// Encrypt a block in-place.
///
/// # Errors
///
/// Propagates errors from the cipher. /// Encrypt a block in-place.
pub fn encrypt_inplace(&self, block: &mut C::Block) -> CryptoResult<()> {
self.cipher.encrypt_inplace(block)
}
/// Decrypt a block, returning a new block.
///
/// # Errors
///
/// Propagates errors from the cipher. /// Decrypt a block, returning a new block.
pub fn decrypt(&self, block: &C::Block) -> CryptoResult<C::Block> {
self.cipher.decrypt(block)
}
/// Decrypt a block in-place.
///
/// # Errors
///
/// Propagates errors from the cipher. /// Decrypt a block in-place.
pub fn decrypt_inplace(&self, block: &mut C::Block) -> CryptoResult<()> {
self.cipher.decrypt_inplace(block)
}
/// Access underlying cipher
pub const fn cipher(&self) -> &C {
&self.cipher
}
/// Consume and return underlying cipher
pub fn into_inner(self) -> C {
self.cipher
fn encrypt(&self, plaintext: &[u8]) -> CipherResult<Vec<u8>> {
self.transform(plaintext, CipherAction::Encrypt)
}
fn decrypt(&self, ciphertext: &[u8]) -> CipherResult<Vec<u8>> {
self.transform(ciphertext, CipherAction::Decrypt)
}
}

5
cipher-core/src/types.rs Normal file
View File

@ -0,0 +1,5 @@
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CipherAction {
Encrypt,
Decrypt,
}

47
des/src/block/block32.rs Normal file
View File

@ -0,0 +1,47 @@
use std::ops::BitXor;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Block32(u32);
impl Block32 {
const MASK: u32 = 0xFFFF_FFFF;
#[inline]
#[must_use]
pub const fn new(value: u32) -> Self {
Self(value)
}
#[inline]
#[must_use]
pub const fn as_u32(self) -> u32 {
self.0
}
#[inline]
#[must_use]
pub const fn as_u64(self) -> u64 {
self.0 as u64
}
}
impl From<u32> for Block32 {
fn from(value: u32) -> Self {
Self(value)
}
}
impl From<u64> for Block32 {
fn from(value: u64) -> Self {
let mask = u64::from(Self::MASK);
let value = u32::try_from(value & mask).unwrap_or_default();
Self(value)
}
}
impl BitXor for Block32 {
type Output = Self;
fn bitxor(self, rhs: Self) -> Self::Output {
Self(self.0 ^ rhs.0)
}
}

71
des/src/block/block48.rs Normal file
View File

@ -0,0 +1,71 @@
use std::{array, ops::BitXor};
use crate::{block::Block6, key::Subkey};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Block48(u64);
impl Block48 {
const MASK: u64 = 0xFFFF_FFFF_FFFF;
#[inline]
#[must_use]
pub const fn new(value: u64) -> Self {
Self(value & Self::MASK)
}
#[inline]
#[must_use]
pub const fn as_u64(self) -> u64 {
self.0
}
#[must_use]
pub fn as_block6_array(self) -> [Block6; 8] {
array::from_fn(|idx| {
let start_bit = 42 - (u8::try_from(idx).expect("8-bit number") * 6); // S-box 0: bit 42, S-box 7: bit 5
let six_bits = u8::try_from((self.0 >> start_bit) & 0x3F).expect("6-bit number");
Block6::new(six_bits)
})
}
}
impl From<u64> for Block48 {
fn from(value: u64) -> Self {
Self::new(value)
}
}
impl BitXor for Block48 {
type Output = Self;
fn bitxor(self, rhs: Self) -> Self::Output {
Self(self.0 ^ rhs.0)
}
}
impl BitXor<Subkey> for Block48 {
type Output = Self;
fn bitxor(self, rhs: Subkey) -> Self::Output {
Self(self.0 ^ rhs.as_int())
}
}
impl BitXor<&Subkey> for Block48 {
type Output = Self;
fn bitxor(self, rhs: &Subkey) -> Self::Output {
Self(self.0 ^ rhs.as_int())
}
}
impl BitXor<Subkey> for &Block48 {
type Output = Block48;
fn bitxor(self, rhs: Subkey) -> Self::Output {
Block48(self.0 ^ rhs.as_int())
}
}
impl BitXor<&Subkey> for &Block48 {
type Output = Block48;
fn bitxor(self, rhs: &Subkey) -> Self::Output {
Block48(self.0 ^ rhs.as_int())
}
}

36
des/src/block/block6.rs Normal file
View File

@ -0,0 +1,36 @@
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Block6(u8);
impl Block6 {
const MASK: u8 = 0x3F;
#[inline]
#[must_use]
pub const fn new(value: u8) -> Self {
Self(value & Self::MASK)
}
#[inline]
#[must_use]
pub const fn zero() -> Self {
Self(0)
}
#[inline]
#[must_use]
pub const fn as_u8(self) -> u8 {
self.0
}
#[inline]
#[must_use]
pub const fn to_row(self) -> usize {
((self.0 >> 5) << 1 | (self.0 & 1)) as usize
}
#[inline]
#[must_use]
pub const fn to_col(self) -> usize {
((self.0 >> 1) & 0xF) as usize
}
}

76
des/src/block/block64.rs Normal file
View File

@ -0,0 +1,76 @@
use crate::block::lr::LR;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Block64(u64);
impl Block64 {
#[inline]
#[must_use]
pub const fn new(value: u64) -> Self {
Self(value)
}
#[inline]
#[must_use]
pub const fn as_u64(self) -> u64 {
self.0
}
#[inline]
#[must_use]
pub const fn from_be_bytes(bytes: [u8; 8]) -> Self {
Self(u64::from_be_bytes(bytes))
}
#[inline]
#[must_use]
pub const fn to_le_bytes(self) -> [u8; 8] {
self.0.to_le_bytes()
}
#[inline]
#[must_use]
pub const fn to_be_bytes(self) -> [u8; 8] {
self.0.to_be_bytes()
}
#[inline]
#[must_use]
pub fn split_lr(self) -> LR {
self.into()
}
}
impl From<u64> for Block64 {
fn from(value: u64) -> Self {
Self::new(value)
}
}
impl From<Block64> for LR {
fn from(block: Block64) -> Self {
let left = (block.0 >> 32) as u32;
let right = (block.0 & 0xFFFF_FFFF) as u32;
Self::new(left, right)
}
}
impl From<&Block64> for LR {
fn from(block: &Block64) -> Self {
let left = (block.0 >> 32) as u32;
let right = (block.0 & 0xFFFF_FFFF) as u32;
Self::new(left, right)
}
}
impl From<Block64> for Vec<u8> {
fn from(value: Block64) -> Self {
value.0.to_le_bytes().to_vec()
}
}
impl From<&Block64> for Vec<u8> {
fn from(value: &Block64) -> Self {
value.0.to_le_bytes().to_vec()
}
}

43
des/src/block/lr.rs Normal file
View File

@ -0,0 +1,43 @@
use crate::block::{block32::Block32, block64::Block64};
use std::mem::swap;
#[derive(Debug, Clone, Copy)]
pub struct LR {
pub(crate) left: Block32,
pub(crate) right: Block32,
}
impl LR {
#[must_use]
pub fn new(left: impl Into<Block32>, right: impl Into<Block32>) -> Self {
Self {
left: left.into(),
right: right.into(),
}
}
#[inline]
pub const fn swap(&mut self) {
swap(&mut self.left, &mut self.right);
}
#[inline]
#[must_use]
pub const fn left(self) -> Block32 {
self.left
}
#[inline]
#[must_use]
pub const fn right(self) -> Block32 {
self.right
}
}
impl From<LR> for Block64 {
fn from(lr: LR) -> Self {
let left = lr.left.as_u64() << 32;
let right = lr.right.as_u64();
Self::new(left | right)
}
}

7
des/src/block/mod.rs Normal file
View File

@ -0,0 +1,7 @@
mod block32;
mod block48;
mod block6;
mod block64;
mod lr;
pub use {block6::Block6, block32::Block32, block48::Block48, block64::Block64, lr::LR};

View File

@ -1,4 +1,21 @@
// DES Constants (from FIPS 46-3 spec)
/// Key Permutation table (64 to 56 bits).
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,
];
/// Compression Permutation table (56 to 48 bits).
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,
];
/// 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];
/// 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,

View File

@ -1,14 +1,216 @@
use crate::key::Subkeys;
use cipher_core::{CryptoResult, KeyLike};
use crate::{
block::{Block32, Block48, Block64, LR},
constants::{E_BOX, FP, IP, P_BOX, S_BOXES},
key::{Key, Subkey, Subkeys},
utils::permutate,
};
use cipher_core::{BlockCipher, CipherAction, CipherError, CipherResult};
#[derive(Debug)]
pub struct Des {
subkeys: Subkeys,
}
impl Des {
pub fn new(key: &impl KeyLike) -> CryptoResult<Self> {
let subkeys = Subkeys::from_key(key)?;
pub fn new(key: impl Into<Key>) -> CipherResult<Self> {
let subkeys = Subkeys::from_key(&key.into())?;
Ok(Self { subkeys })
}
}
impl BlockCipher for Des {
const BLOCK_SIZE: usize = 8;
fn transform_impl(
&self,
block: &[u8],
action: cipher_core::CipherAction,
) -> cipher_core::CipherResult<Vec<u8>> {
let block_arr: [u8; Self::BLOCK_SIZE] = block
.try_into()
.map_err(|_| CipherError::invalid_block_size(Self::BLOCK_SIZE, block.len()))?;
let block64 = Block64::from_be_bytes(block_arr);
let permutated_block = ip(block64);
let result = match action {
CipherAction::Encrypt => feistel_rounds(permutated_block, self.subkeys.iter()),
CipherAction::Decrypt => feistel_rounds(permutated_block, self.subkeys.iter_rev()),
};
let result = fp(result);
Ok(result.into())
}
}
#[inline]
#[must_use]
fn ip(block: Block64) -> Block64 {
permutate(block.as_u64(), 64, 64, &IP).into()
}
#[must_use]
fn feistel_rounds<'a, I>(block: Block64, subkeys: I) -> Block64
where
I: Iterator<Item = &'a Subkey>,
{
let mut lr = LR::from(block);
for subkey in subkeys {
feistel(&mut lr, subkey);
}
lr.into()
}
fn feistel(lr: &mut LR, subkey: &Subkey) {
let tmp = lr.right;
lr.right = lr.left ^ f_function(lr.right, subkey);
lr.left = tmp;
}
#[must_use]
fn f_function(right: Block32, subkey: &Subkey) -> Block32 {
let expanded = expansion_permutation(right);
let xored = expanded ^ subkey;
let sboxed = s_box_substitution(xored);
p_box_permutation(sboxed)
}
#[inline]
#[must_use]
fn expansion_permutation(right: Block32) -> Block48 {
permutate(right.as_u64(), 32, 48, &E_BOX).into()
}
#[must_use]
fn s_box_substitution(block: Block48) -> Block32 {
let six_bit_blocks = block.as_block6_array();
S_BOXES
.iter()
.zip(six_bit_blocks.iter())
.enumerate()
.fold(0, |acc, (idx, (s_box, block6))| {
let row = block6.to_row();
let col = block6.to_col();
let sbox_value = s_box[row][col];
let shift_amount = (7 - idx) * 4;
acc | (u32::from(sbox_value) << shift_amount)
})
.into()
}
#[inline]
#[must_use]
fn p_box_permutation(block: Block32) -> Block32 {
permutate(block.as_u64(), 32, 32, &P_BOX).into()
}
#[inline]
#[must_use]
fn fp(result: Block64) -> Block64 {
permutate(result.as_u64(), 64, 64, &FP).into()
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
const TEST_PLAINTEXT: u64 = 0x0123_4567_89AB_CDEF;
#[rstest]
#[case(TEST_PLAINTEXT, 0xCC00_CCFF_F0AA_F0AA)]
fn initial_permutation(#[case] input: u64, #[case] expected: u64) {
let result = ip(input.into()).as_u64();
assert_eq!(
result, expected,
"Initial permutation failed. Expected 0x{expected:016X}, got 0x{result:016X}"
);
}
#[rstest]
#[case(0xF0AA_F0AA, 0x7A15_557A_1555)] // Round 1
#[case(0xEF4A_6544, 0x75EA_5430_AA09)] // Round 2
#[case(0xCC01_7709, 0xE580_02BA_E853)] // Round 3
#[case(0xA25C_0BF4, 0x5042_F805_7FA9)] // Round 4
#[case(0x7722_0045, 0xBAE9_0400_020A)] // Round 5
#[case(0x8A4F_A637, 0xC542_5FD0_C1AF)] // Round 6
#[case(0xE967_CD69, 0xF52B_0FE5_AB53)] // Round 7
#[case(0x064A_BA10, 0x00C2_555F_40A0)] // Round 8
#[case(0xD569_4B90, 0x6AAB_52A5_7CA1)] // Round 9
#[case(0x247C_C67A, 0x1083_F960_C3F4)] // Round 10
#[case(0xB7D5_D7B2, 0x5AFE_ABEA_FDA5)] // Round 11
#[case(0xC578_3C78, 0x60AB_F01F_83F1)] // Round 12
#[case(0x75BD_1858, 0x3ABD_FA8F_02F0)] // Round 13
#[case(0x18C3_155A, 0x0F16_068A_AAF4)] // Round 14
#[case(0xC28C_960D, 0xE054_594A_C05B)] // Round 15
#[case(0x4342_3234, 0x206A_041A_41A8)] // Round 16
fn permutation_expansion(#[case] block: u32, #[case] expected: u64) {
let result = expansion_permutation(block.into()).as_u64();
assert_eq!(
result, expected,
"Expansion permutaion failed. Expected {expected:016X}, got {result:016X}"
);
}
#[rstest]
#[case(0x6117_BA86_6527, 0x5C82_B597)] // Round 1
#[case(0x0C44_8DEB_63EC, 0xF8D0_3AAE)] // Round 2
#[case(0xB07C_88F8_27CA, 0x2710_E16F)] // Round 3
#[case(0x22EF_2EDE_4AB4, 0x21ED_9F3A)] // Round 4
#[case(0xC605_03EB_51A2, 0x50C8_31EB)] // Round 5
#[case(0xA6E7_6180_BA80, 0x41F3_4C3D)] // Round 6
#[case(0x19AF_B813_B3EF, 0x1075_40AD)] // Round 7
#[case(0xF748_6F9E_7B5B, 0x6C18_7CAE)] // Round 8
#[case(0x8A70_B948_9B20, 0x110C_5777)] // Round 9
#[case(0xA170_BEDA_85BB, 0xDA04_5275)] // Round 10
#[case(0x7BA1_7834_2E23, 0x7305_D101)] // Round 11
#[case(0x15DA_058B_E418, 0x7B8B_2635)] // Round 12
#[case(0xAD78_2B75_B8B1, 0x9AD1_8B4F)] // Round 13
#[case(0x5055_B178_4DCE, 0x6479_9AF1)] // Round 14
#[case(0x5FC5_D477_FF51, 0xB2E8_8D3C)] // Round 15
#[case(0xEB57_8F14_565D, 0xA783_2429)] // Round 16
fn sbox_subsitution(#[case] block: u64, #[case] expected: u32) {
let result = s_box_substitution(block.into()).as_u32();
assert_eq!(
result, expected,
"S-BOX substituion failed. Expected {expected:08X}, got {result:08X}"
);
}
#[rstest]
#[case(0x5C82_B597, 0x234A_A9BB)] // Round 1
#[case(0xF8D0_3AAE, 0x3CAB_87A3)] // Round 2
#[case(0x2710_E16F, 0x4D16_6EB0)] // Round 3
#[case(0x21ED_9F3A, 0xBB23_774C)] // Round 4
#[case(0x50C8_31EB, 0x2813_ADC3)] // Round 5
#[case(0x41F3_4C3D, 0x9E45_CD2C)] // Round 6
#[case(0x1075_40AD, 0x8C05_1C27)] // Round 7
#[case(0x6C18_7CAE, 0x3C0E_86F9)] // Round 8
#[case(0x110C_5777, 0x2236_7C6A)] // Round 9
#[case(0xDA04_5275, 0x62BC_9C22)] // Round 10
#[case(0x7305_D101, 0xE104_FA02)] // Round 11
#[case(0x7B8B_2635, 0xC268_CFEA)] // Round 12
#[case(0x9AD1_8B4F, 0xDDBB_2922)] // Round 13
#[case(0x6479_9AF1, 0xB731_8E55)] // Round 14
#[case(0xB2E8_8D3C, 0x5B81_276E)] // Round 15
#[case(0xA783_2429, 0xC8C0_4F98)] // Round 16
fn permuation_pbox(#[case] block: u32, #[case] expected: u32) {
let result = p_box_permutation(block.into()).as_u32();
assert_eq!(
result, expected,
"P-BOX permutation failed. Expected {expected:08X}, got {result:08X}"
);
}
#[rstest]
#[case(0x0A4C_D995_4342_3234, 0x85E8_1354_0F0A_B405)]
fn final_permutation(#[case] input: u64, #[case] expected: u64) {
let result = fp(input.into()).as_u64();
assert_eq!(
result, expected,
"Final permutation failed. Expected 0x{expected:016X}, got 0x{result:016X}"
);
}
}

34
des/src/key/cd56.rs Normal file
View File

@ -0,0 +1,34 @@
use crate::key::{half28::Half28, key56::Key56};
use zeroize::ZeroizeOnDrop;
#[derive(ZeroizeOnDrop)]
pub struct CD56 {
pub c: Half28,
pub d: Half28,
}
impl CD56 {
pub fn new(c: impl Into<Half28>, d: impl Into<Half28>) -> Self {
Self {
c: c.into(),
d: d.into(),
}
}
pub fn rotate_left(&mut self, amount: u8) {
self.c = self.c.rotate_left(amount);
self.d = self.d.rotate_left(amount);
}
}
impl From<CD56> for Key56 {
fn from(value: CD56) -> Self {
Self::from_half28(&value.c, &value.d)
}
}
impl From<&CD56> for Key56 {
fn from(value: &CD56) -> Self {
Self::from_half28(&value.c, &value.d)
}
}

55
des/src/key/des_key.rs Normal file
View File

@ -0,0 +1,55 @@
use std::fmt::Debug;
use zeroize::ZeroizeOnDrop;
/// 64-bit Key for DES
#[derive(ZeroizeOnDrop)]
pub struct Key([u8; 8]);
impl Key {
#[inline]
#[must_use]
pub const fn from_array(bytes: [u8; 8]) -> Self {
Self(bytes)
}
#[inline]
#[must_use]
pub const fn as_array(&self) -> &[u8; 8] {
&self.0
}
#[inline]
#[must_use]
pub const fn as_u64(&self) -> u64 {
u64::from_be_bytes(self.0)
}
}
impl From<[u8; 8]> for Key {
fn from(bytes: [u8; 8]) -> Self {
Self(bytes)
}
}
impl From<Key> for [u8; 8] {
fn from(key: Key) -> Self {
key.0
}
}
impl AsRef<[u8]> for Key {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl From<u64> for Key {
fn from(key: u64) -> Self {
Self(key.to_le_bytes())
}
}
impl Debug for Key {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Key([REDACTED])")
}
}

View File

@ -1,4 +1,7 @@
use crate::{key::half28::Half28, secret_int};
use crate::{
key::{cd56::CD56, half28::Half28},
secret_int,
};
secret_int! {
/// 56-bit key after PC-1 (lower 56 bits used).
@ -7,10 +10,10 @@ secret_int! {
impl Key56 {
#[must_use]
pub fn split(&self) -> (Half28, Half28) {
pub fn split(&self) -> CD56 {
let c = ((self.0 >> 28) & 0x0FFF_FFFF) as u32;
let d = (self.0 & 0x0FFF_FFFF) as u32;
(c.into(), d.into())
CD56::new(c, d)
}
#[must_use]
@ -21,16 +24,14 @@ impl Key56 {
}
}
impl From<[Half28; 2]> for Key56 {
fn from(keys: [Half28; 2]) -> Self {
let [left, right] = keys;
Self::from_half28(&left, &right)
impl From<Key56> for CD56 {
fn from(key56: Key56) -> Self {
key56.split()
}
}
impl From<&[Half28; 2]> for Key56 {
fn from(keys: &[Half28; 2]) -> Self {
let [left, right] = keys;
Self::from_half28(left, right)
impl From<&Key56> for CD56 {
fn from(key56: &Key56) -> Self {
key56.split()
}
}

View File

@ -1,3 +1,5 @@
mod cd56;
mod des_key;
mod half28;
mod key56;
mod secret_int;
@ -5,4 +7,4 @@ mod subkey;
mod subkeys;
use crate::secret_int;
pub use subkeys::Subkeys;
pub use {des_key::Key, subkey::Subkey, subkeys::Subkeys};

View File

@ -24,8 +24,6 @@ macro_rules! secret_int {
$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;
@ -42,11 +40,6 @@ macro_rules! secret_int {
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)

View File

@ -4,3 +4,10 @@ secret_int! {
/// A single DES round subkey (48 bits stored in lower bits of u64).
pub struct Subkey(u64, 48, 0x0000_FFFF_FFFF_FFFF);
}
impl Subkey {
/// Zero value.
pub const fn zero() -> Self {
Self(0)
}
}

View File

@ -1,25 +1,15 @@
use crate::{
key::{key56::Key56, subkey::Subkey},
constants::{PC1, PC2, ROUND_ROTATIONS},
key::{Key, cd56::CD56, key56::Key56, subkey::Subkey},
utils::permutate,
};
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];
use cipher_core::CipherResult;
use std::{
fmt::Debug,
iter::Rev,
ops::Index,
slice::{Iter, IterMut},
};
/// Container for all 16 round subkeys; zeroized on drop.
pub struct Subkeys([Subkey; 16]);
@ -37,37 +27,22 @@ impl Subkeys {
&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();
let key_arr = key_bytes
.try_into()
.map_err(|_| CryptoError::invalid_key_size(8, key_len))?;
let key_be = u64::from_be_bytes(key_arr);
let cd_56 = pc1(key_be); // 56-bit: C0 + D0
let (c, d) = cd_56.split();
/// # Errors
/// # Panics
pub fn from_key(key: &Key) -> CipherResult<Self> {
let mut cd56 = pc1(key).split(); // 56-bit: C0 + D0
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)
cd56.rotate_left(shift_amount);
pc2(&cd56)
})
.collect::<Vec<Subkey>>()
.try_into()
@ -76,23 +51,63 @@ impl Subkeys {
Ok(Self(subkeys))
}
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
})
/// Borrowing forward iterator.
pub fn iter(&self) -> Iter<'_, Subkey> {
self.0.iter()
}
/// Borrowing reverse iterator.
pub fn iter_rev(&self) -> Rev<Iter<'_, Subkey>> {
self.0.iter().rev()
}
/// Mutable iterator if you need it.
pub fn iter_mut(&mut self) -> IterMut<'_, Subkey> {
self.0.iter_mut()
}
/// Consume `self` and return a new `Subkeys` with reversed order.
#[must_use]
pub const fn reversed(mut self) -> Self {
self.0.reverse();
self
}
}
fn pc1(key: u64) -> Key56 {
permutate(key, 64, 56, &PC1).into()
#[inline]
#[must_use]
fn pc1(key: &Key) -> Key56 {
permutate(key.as_u64(), 64, 56, &PC1).into()
}
fn pc2(key: &Key56) -> Subkey {
permutate(key.as_int(), 56, 48, &PC2).into()
#[inline]
#[must_use]
fn pc2(cd: &CD56) -> Subkey {
let key56 = Key56::from(cd);
permutate(key56.as_int(), 56, 48, &PC2).into()
}
impl<'a> IntoIterator for &'a Subkeys {
type Item = &'a Subkey;
type IntoIter = Iter<'a, Subkey>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<'a> IntoIterator for &'a mut Subkeys {
type Item = &'a mut Subkey;
type IntoIter = IterMut<'a, Subkey>;
fn into_iter(self) -> Self::IntoIter {
self.iter_mut()
}
}
impl Index<usize> for Subkeys {
type Output = Subkey;
fn index(&self, index: usize) -> &Self::Output {
&self.0[index]
}
}
impl Debug for Subkeys {
@ -113,18 +128,14 @@ mod tests {
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)]
#[case(TEST_KEY, 0x00F0_CCAA_F556_678F)]
fn pc1_permutaion_correct(#[case] key: u64, #[case] expected: u64) {
let result = pc1(key);
let result = pc1(&key.into()).as_int();
assert_eq!(
result.as_int(),
expected,
"PC1 permutation failed. Expected {expected:08X}, got {:08X}",
result.as_int()
result, expected,
"PC1 permutation failed. Expected {expected:08X}, got {result:08X}",
);
}
@ -146,8 +157,12 @@ mod tests {
#[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");
fn pc2_permutaion(#[case] before: u64, #[case] expected: u64) {
let key56 = Key56::from(before).split();
let result = pc2(&key56).as_int();
assert_eq!(
result, expected,
"PC2 permutation failed. Expected {expected:016X}, got {result:016X}"
);
}
}

View File

@ -1,3 +1,4 @@
mod block;
pub(crate) mod constants;
mod des;
mod key;

View File

@ -16,29 +16,13 @@ pub fn permutate(
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 input_bit_pos = input_bit_amount - u64::from(input_pos_1based);
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
let output_bit_pos = output_bit_amount - 1 - (idx as u64);
acc | (bit_value << output_bit_pos)
})
}

139
des/tests/des.rs Normal file
View File

@ -0,0 +1,139 @@
use cipher_core::BlockCipher;
use claims::assert_ok;
use des::Des;
use rstest::rstest;
const TEST_KEY: u64 = 0x1334_5779_9BBC_DFF1;
const TEST_PLAINTEXT: u64 = 0x0123_4567_89AB_CDEF;
const TEST_CIPHERTEXT: u64 = 0x85E8_1354_0F0A_B405;
#[rstest]
#[case(TEST_PLAINTEXT, TEST_CIPHERTEXT, TEST_KEY)]
#[case(0xC6F9_3D51_0757_A8FE, 0x3FDF_8951_0F38_A29B, 0x0655_A407_0623_10E3)]
#[case(0x4230_ECB4_5CD4_C924, 0xB7D1_E902_48E5_51CA, 0xAF3B_75E7_6DAD_49C8)]
#[case(0xE277_0441_8EE8_AE59, 0x4687_4321_3707_A4C7, 0x2FD7_2C95_90E1_712E)]
#[case(0x0BB5_293D_2EA8_1028, 0xB91E_FD54_0D09_FB25, 0xC495_4CCA_1BF1_8FE9)]
#[case(0xEE0C_8BED_F695_AB5F, 0x60F8_EE47_069F_989A, 0x350E_304F_E0F1_253D)]
#[case(0xF737_BBEB_28AA_31E1, 0xF2C1_27AD_7122_66A6, 0xA7F7_E1E3_CD93_F95D)]
#[case(0x1497_1184_AD78_EE19, 0x10C6_89E2_60BA_4B8E, 0x78D0_0A8E_63B1_4B5D)]
#[case(0x0F67_735D_621A_52C5, 0x1617_C981_8A62_7760, 0xAA48_FE46_0CB0_D436)]
#[case(0x81DD_1F9B_2E46_2061, 0xF2D1_E793_EB75_664A, 0x9901_A819_87CA_4782)]
#[case(0xEDBB_18A4_3E6D_8547, 0xD6A2_2519_CC7A_0D3C, 0x8297_9633_08D3_EBEE)]
#[case(0xF8B9_B259_5140_7D92, 0x16D7_B2DA_E12B_D8E7, 0x097A_23AF_A6E1_D116)]
#[case(0x1544_4A64_DA36_DAFC, 0xFA77_0F17_2532_AA14, 0x4F22_671C_8797_BDD1)]
#[case(0xEB95_CC8B_258D_D5C8, 0xBD6D_1004_24D4_8B48, 0xA886_0128_721F_FBB7)]
#[case(0xF1C2_A5DE_F978_0E45, 0x212D_A009_B107_EB47, 0x2383_25F5_E681_B9E5)]
#[case(0x21CA_7D34_9158_6F61, 0x75DE_1ED4_C454_3E04, 0xB353_ECE0_0C1D_C4AD)]
#[case(0xC314_4A6F_EAA8_3D91, 0xD98E_150A_66EB_BC33, 0xAC4F_E060_2751_2FF4)]
#[case(0xE845_420B_7C46_DBBB, 0x2E4D_9248_2585_44FF, 0xE997_37D4_27AE_B5F9)]
#[case(0x0284_5E3F_2EDC_4809, 0x0D22_6DC1_532F_3E4A, 0xD339_51A2_9FD6_C3DF)]
#[case(0x0471_1CC5_0218_A7B8, 0x0F34_EA09_A837_33C7, 0xBFC2_3D34_930A_D36D)]
#[case(0xE696_EBC5_7577_B9C4, 0x5F54_48A0_670D_2B41, 0x2200_AA0C_3F42_226C)]
#[case(0xCF61_8B74_BE29_1BD8, 0x254C_D92C_A2FB_043B, 0x36E6_82D4_9799_C6B3)]
#[case(0x7E18_6E5A_8E95_7527, 0x4462_82B2_1A70_0BDB, 0x4510_A566_7793_F488)]
#[case(0x2352_0EA6_C837_E224, 0x7C6C_616F_0726_F45E, 0xCCE7_EC8E_DFD6_2C95)]
#[case(0x802F_22E9_FAE5_71FE, 0x9532_CC17_3FD6_CAEF, 0x3070_C587_0D54_2905)]
#[case(0xE1AE_534B_DF81_0003, 0xCEFE_8EA8_40A0_A830, 0x0F58_5495_20E3_BDDD)]
#[case(0xD074_AD28_E24B_10B4, 0x4CA2_31ED_48FA_F5ED, 0x5D04_DEF0_4DB7_C05A)]
#[case(0x7D6D_7F72_8247_D60A, 0xB350_CE16_4A60_E35D, 0x82A2_5738_1871_519F)]
#[case(0x922D_5265_A0C1_54FC, 0xB615_C26C_8624_27A8, 0x07B3_9AFE_02EC_FBC9)]
#[case(0x7A38_2A2D_1ED0_1EC7, 0x96C4_819F_5BF1_099C, 0x6AF2_B49D_2EB0_055D)]
#[case(0x0E9B_3302_66FD_964C, 0x4F42_A745_0D30_20DB, 0x8AFA_83AA_3158_858D)]
#[case(0x6872_A230_96B8_0143, 0x2DC8_6E8E_00F1_7D2E, 0x660E_D819_E232_5E0F)]
#[case(0x0FF9_554F_AEAA_DDE6, 0x7AA3_B1D9_22B8_0BF1, 0x9B32_8887_35DE_BB5D)]
#[case(0x00FB_729A_2201_5603, 0x2506_2232_CF4B_E95C, 0x7104_806D_7257_2CB7)]
#[case(0x2E1C_C29A_E40B_4CCD, 0xFC32_0B3E_6058_59AE, 0x4541_D83F_E726_7CD2)]
#[case(0x4336_2A6C_CE6E_CA97, 0xB2C8_FDFE_0A47_2A81, 0xD730_064E_E820_3C33)]
#[case(0x03FB_C27D_74F3_4AC0, 0x4EEC_87D2_9024_5136, 0x361E_1CC7_4E4E_434F)]
#[case(0x5830_0AAC_75D9_FD76, 0x9C41_87B5_BAF5_FA65, 0x4929_7C9B_86E9_ECFC)]
#[case(0x6224_F54C_2C0E_34C4, 0x7002_F5D4_E72B_C942, 0x91B9_3300_0209_EEB0)]
#[case(0x12FE_DF40_FA59_9757, 0x2D24_8A52_638A_CAA4, 0x11D4_7CC2_683B_41A6)]
#[case(0x463C_FBD3_EBED_A824, 0x9CE8_D4F9_521F_E37D, 0xFF05_82BC_587C_747E)]
#[case(0x66A8_196A_3212_B1CB, 0x24B2_CDE9_6BD0_43D1, 0xFA30_B9CE_04F6_53F2)]
#[case(0xC235_5E16_B36F_A90B, 0x56B6_6B72_5995_4B0C, 0x00CE_4F24_5AB0_F566)]
#[case(0x2497_9DE0_EAB2_724C, 0xF868_B877_0B46_2BD1, 0x62A5_FCF5_4F91_2784)]
#[case(0x6356_B043_0AF2_858B, 0x6743_750C_91C7_2828, 0x113B_96B8_2748_CCBF)]
#[case(0x9AE0_16CF_9F33_B1B9, 0x3CEB_D74D_CACC_1721, 0x7B8E_6CAF_FCF4_4E3F)]
#[case(0x0284_35F7_B1BC_4866, 0x5780_E4F5_90C3_8ABF, 0x3D67_EA7B_9BB1_0A48)]
#[case(0xC654_D706_82CF_BC74, 0x2BE2_1130_FD89_6F81, 0x7891_2150_1135_CD28)]
#[case(0x1E59_779F_E127_197B, 0x3960_BE8A_4475_41EC, 0x49E0_8524_68BF_FF70)]
#[case(0x1133_3FE5_F5B6_E8A1, 0x341E_2C24_9E3C_33EC, 0x3A0E_8C24_36FD_CB7E)]
#[case(0x8ABC_A73D_578D_3D58, 0xE0F3_AEFA_24AC_72D8, 0x3D09_4F8F_B36B_2049)]
#[case(0x13D0_E14D_1B09_D4D0, 0xFD12_4B52_065E_9B1C, 0x0DAF_83A5_B78B_D17A)]
#[case(0xB95A_AA93_1E18_D4F2, 0x2E7F_D3E0_1537_4051, 0x21AE_1AF0_DF76_10E7)]
#[case(0x086B_DA84_4066_F31E, 0xAD08_AEBC_32B1_D7CF, 0xA56A_E683_333C_7C7C)]
#[case(0x3728_381E_85C2_E4CD, 0x0C63_24A6_6E9F_5BDB, 0xCEA9_9BFE_6644_87D2)]
#[case(0x531B_541A_095A_A042, 0x7F3B_CA73_FF24_3276, 0x63FF_C197_52E3_591A)]
#[case(0xF3B0_C406_3FD6_CEF2, 0x8EE3_2764_B52A_9608, 0xAF1E_2FC7_7631_5A9C)]
#[case(0xB804_7091_8C20_F2F9, 0x610D_74A9_5D2E_5D04, 0xF03D_BBB8_B4B5_259A)]
#[case(0x745C_E01E_C299_5117, 0x7224_AD1D_3CB6_CFAD, 0xC06D_2C9B_8AC1_8FC5)]
#[case(0x36A2_D3C1_10B9_8ECD, 0xB4FC_F673_35A2_A6AD, 0x88C8_8DEF_D4F6_7DE4)]
#[case(0x1BAB_8249_68A6_F8D4, 0x5C98_AA0A_09E2_CAFB, 0xD8EC_0A6D_76B3_874C)]
#[case(0x9F2E_8659_16C3_2FE4, 0x8C0F_B383_CCBC_48BF, 0x5821_47BE_1321_F990)]
#[case(0x6B97_A57F_E2D1_6610, 0xA3A9_9BB3_1874_E7A6, 0x85ED_1A64_3CDC_3DBD)]
#[case(0x1F58_1813_046C_9976, 0xBAF3_B9F6_BED0_557B, 0x7B98_AAC7_F7CD_D742)]
#[case(0xB9E3_CDF0_B644_5B66, 0xC19E_9B37_15FF_9DC9, 0x9B97_837A_CEDE_EBC8)]
#[case(0xD2F7_4580_0662_407E, 0xA61C_888C_93C5_D990, 0x8FFE_C142_1E0E_A2AE)]
#[case(0xC257_8AE8_B7D1_4A0C, 0x3E30_CAE2_4D01_C06E, 0x90D5_78F0_87BE_D051)]
#[case(0x0E4D_192E_5C18_55BC, 0xCF7E_E266_0BD0_241F, 0xC946_5D9E_8051_3EA8)]
#[case(0xB8C8_823F_8919_5087, 0x42E5_989B_91E4_5B1F, 0x3EC9_0C5F_9542_B379)]
#[case(0xA100_840A_C4F9_FF14, 0x7847_1AE8_EA4D_8582, 0x3E4D_E0AE_BADC_DF35)]
#[case(0x4F8D_2946_5ED5_6262, 0x3185_4E24_A13A_8C53, 0xB256_D848_5014_11DE)]
#[case(0x5767_670D_87F1_FBBA, 0xF892_A9F3_1F76_6F45, 0xDDC9_0B98_F555_6EC9)]
#[case(0xF3BF_F8CB_AADF_D721, 0xB3F4_3D4C_B949_BD28, 0xDED3_DF1E_28B6_AC81)]
#[case(0xC63C_CDE5_88BB_17DB, 0x64A4_6861_E0D8_9377, 0x4317_56A0_9A71_8206)]
#[case(0xDF15_2CCF_DDC8_ACE1, 0x47F3_FE5A_9CA3_6720, 0x17AA_23E0_2E70_76DA)]
#[case(0x09CE_FF80_D62B_A1F3, 0xD706_DEF3_6B3D_9AA8, 0x52D8_119A_736E_D25A)]
#[case(0x6F5C_650B_8C57_370B, 0xEA60_1EBE_B37B_E6B7, 0xE6FD_5BEE_E9F7_89FB)]
#[case(0x4409_4AB7_9244_7C0B, 0x6489_004E_EDD7_65EE, 0x27DB_193D_4410_4800)]
#[case(0x61EE_5199_5804_30FF, 0xE3B1_F245_EEF5_7A8C, 0x8943_B7F5_0EBC_D4AE)]
#[case(0x6F39_1336_84A1_CB12, 0x7D45_A03A_D0C0_2B7C, 0x0FD7_AF51_4C4D_630E)]
#[case(0x7E84_F4DD_2A64_D8A0, 0x3271_8C71_68E5_D4B8, 0x8C5B_0274_F1E1_49F5)]
#[case(0xB14B_F9D2_FD61_626C, 0xCA2F_A9C5_E35D_BD5E, 0xE9C2_47B6_7B90_CB78)]
#[case(0x7315_721B_8072_C12E, 0xCBA1_3DE3_91A3_8CBC, 0xCABD_F1D8_F8C9_7F36)]
#[case(0x9D03_5D2F_FF2E_03F8, 0xA945_5400_A9F9_EC23, 0xF9CD_D65B_5A90_3491)]
#[case(0x7AC5_1308_FBB8_8ADF, 0xB1BA_F5FB_B4E0_968B, 0x87B3_45FF_336E_2C36)]
#[case(0xFA3A_4A1A_1732_C319, 0xC157_E3B9_6D1E_98B8, 0xB691_0EEF_5239_2E0B)]
#[case(0x3B5F_3343_9D31_3650, 0xA207_535D_9E2A_34E9, 0x0932_5791_B0EA_B667)]
#[case(0x72CE_CFD2_29AF_B46D, 0x7DCC_33A1_8CFC_BDC4, 0x2EB8_4231_BADA_C0CD)]
#[case(0xA0C6_7EA3_7D59_BA9E, 0x67F2_D204_2570_7DF3, 0x48B6_42F7_8149_BEAE)]
#[case(0xFB63_1637_53AF_FE64, 0x2345_9280_7EE3_4BB5, 0x2910_9B12_6637_5CF8)]
#[case(0xCFC5_0524_89ED_B7F8, 0xE4E3_E7E9_4B51_86D3, 0xB5CF_CD1E_62A3_C916)]
#[case(0x5C82_EB3D_13D6_DA31, 0x7960_9644_E8F8_D9DC, 0x7BF8_500F_95AF_A68B)]
#[case(0xBB0C_417D_CC7A_0EF3, 0x7B15_FEC1_253A_C2D9, 0xB463_988E_A3B7_F9E4)]
#[case(0xC3B8_37D6_5170_011B, 0x1B23_EF3B_4853_D5C1, 0x7652_8A30_9374_056C)]
#[case(0xE5E0_5D4F_262B_E8B1, 0x4683_AF44_7BA4_1944, 0x0A85_FD6F_ED35_3C99)]
#[case(0x547A_21D5_61C7_A98F, 0xFA1B_8BE3_9447_7DC5, 0x6B20_A7AE_40D8_C631)]
#[case(0x735F_1D3A_17FF_3F12, 0x0C14_C532_CC28_846D, 0xE1B9_6382_D363_4E1B)]
#[case(0x87D2_05D5_16DA_CEA7, 0x5CE9_0A9E_6938_9B59, 0x6683_8ACA_6C09_2897)]
#[case(0x342E_98E4_E3DF_DE9F, 0x1DC2_022E_DE59_79F0, 0x33E1_BD93_DFB6_F058)]
#[case(0x1524_18ED_94BF_6739, 0x0BF0_7F9A_A463_30B8, 0x4781_2ED0_129E_BB99)]
#[case(0xA9CD_C911_F01F_E7C8, 0x8777_9C21_AD54_E116, 0x3E9D_7D47_8FFF_F687)]
fn encrypt_decrypt_roundtrip(
#[case] plaintext: u64,
#[case] expected_ciphertext: u64,
#[case] key: u64,
) {
let des = assert_ok!(Des::new(key), "Valid DES key");
let ciphertext = assert_ok!(des.encrypt(&plaintext.to_le_bytes()));
let dectrypted = assert_ok!(des.decrypt(&ciphertext));
let re_ciphertext = assert_ok!(des.encrypt(&dectrypted));
let ciphertext_u64 = u64::from_be_bytes(ciphertext.try_into().expect("8 bytes"));
let decrypted_u64 = u64::from_be_bytes(dectrypted.try_into().expect("8 bytes"));
let re_ciphertext_u64 = u64::from_be_bytes(re_ciphertext.try_into().expect("8 bytes"));
assert_eq!(
ciphertext_u64, expected_ciphertext,
"Encyption failed. Expected 0x{expected_ciphertext:016X}, got 0x{decrypted_u64:016X}"
);
assert_eq!(
decrypted_u64, plaintext,
"Decyption failed. Expected 0x{plaintext:016X}, got 0x{decrypted_u64:016X}"
);
assert_eq!(
re_ciphertext_u64, expected_ciphertext,
"Re-encyption failed. Expected 0x{expected_ciphertext:016X}, got 0x{re_ciphertext_u64:016X}"
);
}