mirror of
https://github.com/kristoferssolo/des-rs.git
synced 2025-12-20 11:04:38 +00:00
Initial commit
This commit is contained in:
commit
ad4a888af8
19
.gitignore
vendored
Normal file
19
.gitignore
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
#--------------------------------------------------#
|
||||
# The following was generated with gitignore.nvim: #
|
||||
#--------------------------------------------------#
|
||||
# Gitignore for the following technologies: Rust
|
||||
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||
Cargo.lock
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
164
Cargo.lock
generated
Normal file
164
Cargo.lock
generated
Normal file
@ -0,0 +1,164 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
|
||||
|
||||
[[package]]
|
||||
name = "claims"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bba18ee93d577a8428902687bcc2b6b45a56b1981a1f6d779731c86cc4c5db18"
|
||||
|
||||
[[package]]
|
||||
name = "des"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"claims",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.176"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
||||
dependencies = [
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.14.7+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c"
|
||||
dependencies = [
|
||||
"wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.1+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.46.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
16
Cargo.toml
Normal file
16
Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "des"
|
||||
version = "0.1.0"
|
||||
authors = ["Kristofers Solo <dev@kristofers.xyz>"]
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
[dev-dependencies]
|
||||
claims = "0.8"
|
||||
rand = "0.9"
|
||||
|
||||
[lints.clippy]
|
||||
pedantic = "warn"
|
||||
nursery = "warn"
|
||||
unwrap_used = "warn"
|
||||
371
src/lib.rs
Normal file
371
src/lib.rs
Normal file
@ -0,0 +1,371 @@
|
||||
#[derive(Debug)]
|
||||
pub struct DES {
|
||||
pub subkeys: [u64; 16],
|
||||
_s_boxes: Vec<Vec<Vec<u8>>>,
|
||||
}
|
||||
|
||||
impl DES {
|
||||
#[must_use]
|
||||
pub fn new(key: u64) -> Self {
|
||||
let mut des = Self {
|
||||
subkeys: [0; 16],
|
||||
_s_boxes: Vec::new(),
|
||||
};
|
||||
des.generate_subkeys(key);
|
||||
des
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn encrypt(&self, _plaintext: u64) -> u64 {
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn decrypt(&self, _plaintext: u64) -> u64 {
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn ip(&self, _input: u64) -> u64 {
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn pc1(&self, _key: u64) -> u64 {
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn expand(&self, _right_half: u32) -> u64 {
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn permutate_output(&self, _input: u32) -> u32 {
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn feistel(&self, _right: u32, _subkey: u64) -> u32 {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn generate_subkeys(&mut self, _key: u64) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
/// Encrypts data using ECB mode.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `data` - Plaintext bytes (must be multiple of 8 for ECB)
|
||||
/// - `key` - 8-byte DES key
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Ciphertext as Vec<u8>, same length as input
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If data length is not multiple of 8 bytes
|
||||
#[must_use]
|
||||
pub fn encrypt_ecb(_data: &[u8], _key: &[u8; 8]) -> Vec<u8> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Decrypts ECB-encrypted data.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `data` - Plaintext bytes (must be multiple of 8 for ECB)
|
||||
/// - `key` - 8-byte DES key
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Ciphertext as Vec<u8>, same length as input
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If data length is not multiple of 8 bytes
|
||||
#[must_use]
|
||||
pub fn decrypt_ecb(_data: &[u8], _key: &[u8; 8]) -> Vec<u8> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use claims::assert_le;
|
||||
use rand::random;
|
||||
use std::time::Instant;
|
||||
|
||||
const TEST_KEY: u64 = 0x133457799BBCDFF1;
|
||||
const RIGHT_KEY: u32 = 0x12345678;
|
||||
const TEST_PLAINTEXT: u64 = 0x0123456789ABCDEF;
|
||||
const TEST_CIPHERTEXT: u64 = 0x85E813540F0AB405;
|
||||
|
||||
impl DES {
|
||||
fn apply_sboxes(&self, _input: u64) -> u32 {
|
||||
// Implementation for testing S-boxes in isolation
|
||||
// Return 32-bit result after 8 S-boxes
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper to create a test Des instance (use your actual key schedule)
|
||||
fn des_instance() -> DES {
|
||||
DES::new(TEST_KEY)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encrypt_decrypt_roundtrip() {
|
||||
let des = des_instance();
|
||||
let plaintext = TEST_PLAINTEXT;
|
||||
let ciphertext = des.encrypt(plaintext);
|
||||
let dectrypted = des.decrypt(plaintext);
|
||||
let re_ciphertext = des.encrypt(dectrypted);
|
||||
|
||||
assert_eq!(ciphertext, TEST_CIPHERTEXT, "Encyption failed");
|
||||
assert_eq!(re_ciphertext, TEST_CIPHERTEXT, "Re-Encyption failed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn weak_keys_rejected() {
|
||||
let weak_keys = [0x0101010101010101, 0xFEFEFEFEFEFEFEFE, 0xE001E001E001E001];
|
||||
|
||||
for key in weak_keys {
|
||||
let des = DES::new(key);
|
||||
let plaintext = TEST_PLAINTEXT;
|
||||
let encrypted = des.encrypt(plaintext);
|
||||
let dectrypted = des.decrypt(encrypted);
|
||||
assert_eq!(dectrypted, plaintext, "Weak key {key} failed roundtrip");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_blocks() {
|
||||
let des = des_instance();
|
||||
let blocks = [
|
||||
(0x0123456789ABCDEFu64, 0x85E813540F0AB405u64),
|
||||
(0xFEDCBA9876543210u64, 0xC08BF0FF627D3E6Fu64), // Another test vector
|
||||
(0x0000000000000000u64, 0x474D5E3B6F8A07F8u64), // Zero block
|
||||
];
|
||||
for (plaintext, expected) in blocks {
|
||||
let encrypted = des.encrypt(plaintext);
|
||||
assert_eq!(encrypted, expected, "Failed on plaintext: {plaintext:016X}");
|
||||
|
||||
let dectrypted = des.decrypt(encrypted);
|
||||
assert_eq!(dectrypted, plaintext, "Roundtrip failed on block");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn key_schedule_generates_correct_subkeys() {
|
||||
let expected_subkeys = [
|
||||
0xF3FDFBF373848CF5u64,
|
||||
0xF3738CF548C4F3F5u64,
|
||||
0x848C4F3F5F373848u64,
|
||||
];
|
||||
|
||||
let des = des_instance();
|
||||
let generated = des.subkeys;
|
||||
|
||||
for (idx, &expected) in expected_subkeys.iter().enumerate() {
|
||||
let masked_gen = generated[idx];
|
||||
let masked_exp = expected;
|
||||
assert_eq!(
|
||||
masked_gen, masked_exp,
|
||||
"Subkey {idx} mismatch: expected {masked_exp:012X}, got {masked_gen:012X}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn initial_permutation() {
|
||||
let input = TEST_KEY;
|
||||
let expected_ip = 0xC2B093C7A3A7C24A;
|
||||
let result = des_instance().ip(input);
|
||||
assert_eq!(result, expected_ip, "Initial permulation failed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pc1_permutaion_correct() {
|
||||
let des = des_instance();
|
||||
let key = TEST_KEY;
|
||||
let expected_pc1 = 0x0A2B3C4D5E6F789A; // Truncated 56 bits from spec
|
||||
let result = des.pc1(key);
|
||||
let masked_result = result & 0x00FF_FFFF_FFFF_FFFF; // 56 bits
|
||||
let masked_expected = expected_pc1 & 0x00FF_FFFF_FFFF_FFFF;
|
||||
assert_eq!(masked_result, masked_expected, "PC1 permutation failed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expansion_permutation() {
|
||||
let des = des_instance();
|
||||
let right_half = RIGHT_KEY;
|
||||
let expanded = des.expand(right_half);
|
||||
|
||||
// Expansion should produce 48 bits from 32
|
||||
assert_eq!(expanded >> 48, 0, "Expandsion exceeds 48 bits");
|
||||
|
||||
// Test that expansion duplicates bits correctly
|
||||
// Bit 0 of expanded should match bit 31 of input (EXPANSION[0]=32)
|
||||
assert_eq!(
|
||||
(expanded >> 47) & 1,
|
||||
((right_half as u64) >> 31) & 1,
|
||||
"Expansion bit 0 failed"
|
||||
);
|
||||
// Bit 1 should match bit 0 (EXPANSION[1]=1)
|
||||
assert_eq!(
|
||||
(expanded >> 46) & 1,
|
||||
(right_half as u64) & 1,
|
||||
"Expansion bit 1 failed"
|
||||
);
|
||||
// Test wraparound: bit 47 should match bit 0 again (EXPANSION[47]=1)
|
||||
assert_eq!(
|
||||
expanded & 1,
|
||||
(right_half as u64) & 1,
|
||||
"Expansion wraparound failed"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sbox_subsitution() {
|
||||
let des = des_instance();
|
||||
let sbox_tests = [
|
||||
// (box_idx, 6-bit input, expected 4-bit output)
|
||||
(0, 0b000000, 14), // S1: 00 0000 -> row 0, col 0 -> 14
|
||||
(0, 0b011111, 9), // S1: 01 1111 -> row 1, col 15 -> 9
|
||||
(1, 0b100000, 0), // S2: 10 0000 -> row 2, col 0 -> 0
|
||||
(2, 0b001010, 2), // S3: 00 1010 -> row 0, col 10 -> 2
|
||||
];
|
||||
|
||||
for (box_idx, input, expected) in sbox_tests {
|
||||
let row = (input & 1) | ((input >> 4) & 0x2);
|
||||
let col = (input >> 1) & 0xF;
|
||||
let val = des._s_boxes[box_idx][row as usize][col as usize];
|
||||
|
||||
assert_eq!(
|
||||
val,
|
||||
expected as u8,
|
||||
"S{} failed: input {input:06b} (row {row}, col {col}) expected {expected}, got {val}",
|
||||
box_idx + 1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn permuation_pbox() {
|
||||
let des = des_instance();
|
||||
let input = RIGHT_KEY;
|
||||
let result = des.permutate_output(input);
|
||||
|
||||
// P-box should preserve all bits (32 in, 32 out), just reorder
|
||||
let bit_count = input.count_ones();
|
||||
let result_bit_count = result.count_ones();
|
||||
assert_eq!(bit_count, result_bit_count, "P-box changes bit count");
|
||||
|
||||
// Test specific bit mapping: PERMUTATION[0]=16 means bit 15 (0-based) of output = bit 15 of input
|
||||
let input_bit_15 = (input >> 15) & 1;
|
||||
let output_bit_0 = (result >> 31) & 1; // MSB first
|
||||
assert_eq!(input_bit_15, output_bit_0, "P-box bit mapping failed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feistel_function_properties() {
|
||||
let des = des_instance();
|
||||
let right = RIGHT_KEY;
|
||||
let subkey = 0xFEDCBA9876543210 & 0xFFFF_FFFF_FFFF;
|
||||
|
||||
let feistel_result = des.feistel(right, subkey);
|
||||
|
||||
// Feistel output should always be 32 bits
|
||||
assert_le!(feistel_result, u32::MAX, "Feistel output exceeds 32 bits");
|
||||
|
||||
// Test that zero subkey produces deterministic result
|
||||
let zero_subkey_result = des.feistel(right, 0);
|
||||
let zero_expanded = des.expand(right);
|
||||
let sbox_result = des.apply_sboxes(zero_expanded);
|
||||
let expected = des.permutate_output(sbox_result as u32);
|
||||
assert_eq!(zero_subkey_result, expected, "Feistel with zero key failed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_zero_paintext() {
|
||||
let des = des_instance();
|
||||
|
||||
let plain = 0;
|
||||
let encrypted = des.encrypt(plain);
|
||||
let decrypted = des.decrypt(encrypted);
|
||||
assert_eq!(decrypted, plain, "All-zero plaintext failed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_one_paintext() {
|
||||
let des = des_instance();
|
||||
|
||||
let plain = 1;
|
||||
let encrypted = des.encrypt(plain);
|
||||
let decrypted = des.decrypt(encrypted);
|
||||
assert_eq!(decrypted, plain, "All-one plaintext failed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn different_inputs() {
|
||||
let des = des_instance();
|
||||
|
||||
let plain1 = 0x0000000000000001;
|
||||
let plain2 = 0x0000000000000002;
|
||||
let enc1 = des.encrypt(plain1);
|
||||
let enc2 = des.encrypt(plain2);
|
||||
assert_ne!(
|
||||
enc1, enc2,
|
||||
"Encryption not deterministic for different inputs"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Invalid key size")]
|
||||
fn invalid_key_size() {
|
||||
DES::new(0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn performance() {
|
||||
let des = des_instance();
|
||||
let plaintext = TEST_PLAINTEXT;
|
||||
|
||||
let start = Instant::now();
|
||||
for _ in 0..10000 {
|
||||
des.encrypt(plaintext);
|
||||
}
|
||||
let duration = start.elapsed();
|
||||
|
||||
println!("10k encryption took: {duration:?}");
|
||||
// Reasonable benchmark: should be under 1ms on modern hardware
|
||||
assert!(duration.as_millis() < 100, "Performance degraded");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fuzz_properties() {
|
||||
let des = des_instance();
|
||||
|
||||
for _ in 0..100 {
|
||||
let plaintext = random();
|
||||
let encrypted = des.encrypt(plaintext);
|
||||
let decrypted = des.decrypt(plaintext);
|
||||
|
||||
assert_eq!(decrypted, encrypted, "Fuzz roundtrip failed");
|
||||
assert_ne!(encrypted, plaintext, "Encryption is identity function");
|
||||
|
||||
let key2 = random();
|
||||
if key2 != TEST_KEY {
|
||||
let des2 = DES::new(key2);
|
||||
let encrypted2 = des2.encrypt(plaintext);
|
||||
assert_ne!(
|
||||
encrypted, encrypted2,
|
||||
"Different keys produced same encryption"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
33
tests/des.rs
Normal file
33
tests/des.rs
Normal file
@ -0,0 +1,33 @@
|
||||
use des::DES;
|
||||
|
||||
#[test]
|
||||
fn test_ecb_mode_equivalence() {
|
||||
// If you implement ECB mode, test it matches single block
|
||||
let key = 0x1334_5779_9BBC_DFF1;
|
||||
let des = DES::new(key);
|
||||
let plain = 0x0123_4567_89AB_CDEF;
|
||||
|
||||
let _single_block = des.encrypt(plain);
|
||||
// let ecb_result = encrypt_ecb(&[plain]);
|
||||
// assert_eq!(single_block, ecb_result[0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_real_data() {
|
||||
// Test with actual 8-byte data
|
||||
let key_bytes = b"KGenius\x01";
|
||||
let key = u64::from_le_bytes(*key_bytes);
|
||||
|
||||
let data_bytes = b"HelloDES!";
|
||||
let mut padded = [0u8; 8];
|
||||
padded[..data_bytes.len()].copy_from_slice(data_bytes);
|
||||
let plaintext = u64::from_le_bytes(padded);
|
||||
|
||||
let des = DES::new(key);
|
||||
let encrypted = des.encrypt(plaintext);
|
||||
|
||||
// Verify we can roundtrip
|
||||
let decrypted = des.decrypt(encrypted);
|
||||
let decrypted_bytes = decrypted.to_le_bytes();
|
||||
assert_eq!(decrypted_bytes[..data_bytes.len()], *data_bytes);
|
||||
}
|
||||
76
tests/key_schedule.rs
Normal file
76
tests/key_schedule.rs
Normal file
@ -0,0 +1,76 @@
|
||||
use des::DES;
|
||||
|
||||
// Full expected subkeys for TEST_KEY (48 bits each, from FIPS spec)
|
||||
const EXPECTED_SUBKEYS: [u64; 16] = [
|
||||
0xF3FDFBF373848CF5u64,
|
||||
0xF3738CF548C4F3F5u64,
|
||||
0x848C4F3F5F373848u64,
|
||||
0xC4F3F5F373848CCFu64,
|
||||
0xF3F5F373848CCF39u64,
|
||||
0x5F373848CCF39A7Au64,
|
||||
0x373848CCF39A7A29u64,
|
||||
0x848CCF39A7A29D6Bu64,
|
||||
0xCCF39A7A29D6B3E6u64,
|
||||
0xF39A7A29D6B3E674u64,
|
||||
0x9A7A29D6B3E674F1u64,
|
||||
0x7A29D6B3E674F1D3u64,
|
||||
0x29D6B3E674F1D39Bu64,
|
||||
0xD6B3E674F1D39BFAu64,
|
||||
0xB3E674F1D39BFACFu64,
|
||||
0xE674F1D39BFACF3Fu64,
|
||||
];
|
||||
|
||||
const TEST_KEY: u64 = 0x133457799BBCDFF1;
|
||||
|
||||
#[test]
|
||||
fn test_full_key_schedule() {
|
||||
let des = DES::new(TEST_KEY);
|
||||
|
||||
for (i, &expected) in EXPECTED_SUBKEYS.iter().enumerate() {
|
||||
let masked_gen = des.subkeys[i] & 0xFFFFFFFFFFFFu64;
|
||||
let masked_exp = expected & 0xFFFFFFFFFFFFu64;
|
||||
assert_eq!(
|
||||
masked_gen, masked_exp,
|
||||
"Subkey {} failed: expected {:012X}, got {:012X}",
|
||||
i, masked_exp, masked_gen
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rotation_shifts() {
|
||||
// Test the left rotation logic in key schedule
|
||||
let mut c: u32 = 0x0FFFFFFF; // 28 bits all 1s
|
||||
c = c.rotate_left(1);
|
||||
assert_eq!(c, 0x1FFFFFFF >> 4, "Single bit rotation failed");
|
||||
|
||||
// Test double shift
|
||||
let mut d: u32 = 0xAAAAAAA; // 101010... pattern
|
||||
d = d.rotate_left(2);
|
||||
assert_eq!(d, 0x2AAAAAA, "Double rotation failed"); // Check pattern shift
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_weak_key_detection() {
|
||||
let weak_keys = [
|
||||
0x0101010101010101u64, // All odd parity
|
||||
0xFEFEFEFEFEFEFEFEu64, // All even parity
|
||||
0x1F1F1F1F0E0E0E0Eu64, // Semi-weak
|
||||
];
|
||||
|
||||
for key in weak_keys {
|
||||
let des = DES::new(key);
|
||||
// Weak keys often produce subkeys that don't vary much
|
||||
let subkeys = &des.subkeys;
|
||||
let first = subkeys[0];
|
||||
let last = subkeys[15];
|
||||
// For true weak keys, many subkeys may be identical
|
||||
// This is just a basic check - implement full weak key analysis if desired
|
||||
println!(
|
||||
"Weak key {} subkeys: first={:012X}, last={:012X}",
|
||||
key,
|
||||
first & 0xFFFFFFFFFFFF,
|
||||
last & 0xFFFFFFFFFFFF
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user