Skip to main content

arcana/cipher/
des.rs

1//! DES (FIPS 46-3) and Triple-DES (3DES / TDEA) block cipher
2//! implementations.
3//!
4//! DES operates on 64-bit blocks with a 56-bit effective key
5//! (stored as 64 bits with parity). Triple-DES uses three DES keys
6//! in EDE (Encrypt-Decrypt-Encrypt) mode.
7//!
8//! # ⚠ Side-channel posture (legacy, no hardening planned)
9//!
10//! Both DES and Triple-DES use **table-based S-boxes** with the
11//! same cache-timing leakage surface as the AES table-based
12//! implementation (cf. [`super::aes`]). There is **no hardening
13//! planned** — these primitives ship for legacy interop only and
14//! should be avoided on SCA-sensitive targets. Prefer AES (and
15//! once roadmap item `T1-A` lands, a fixsliced AES) instead.
16//!
17//! # Security note
18//!
19//! DES is considered insecure due to its 56-bit key length and
20//! should not be used for new applications. 3DES is deprecated by
21//! NIST as of 2023. Use AES instead.
22
23use crate::BlockCipher;
24
25// ============================================================
26// DES permutation and S-box tables
27// ============================================================
28
29/// Initial Permutation (IP), 1-indexed.
30const IP: [u8; 64] = [
31    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,
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,
33    39, 31, 23, 15, 7,
34];
35
36/// Final Permutation (IP^-1), 1-indexed.
37const FP: [u8; 64] = [
38    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,
39    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,
40    49, 17, 57, 25,
41];
42
43/// Expansion permutation E (32 -> 48 bits), 1-indexed.
44const E_PERM: [u8; 48] = [
45    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,
46    22, 23, 24, 25, 24, 25, 26, 27, 28, 29, 28, 29, 30, 31, 32, 1,
47];
48
49/// Permutation P (after S-box output), 1-indexed.
50const P_PERM: [u8; 32] = [
51    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,
52    25,
53];
54
55/// Permuted Choice 1 (PC-1): select 56 bits from 64-bit key, 1-indexed.
56const PC1: [u8; 56] = [
57    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,
58    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,
59];
60
61/// Permuted Choice 2 (PC-2): select 48 bits from 56-bit key state, 1-indexed.
62const PC2: [u8; 48] = [
63    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,
64    40, 51, 45, 33, 48, 44, 49, 39, 56, 34, 53, 46, 42, 50, 36, 29, 32,
65];
66
67/// Number of left shifts per round in key schedule.
68const KEY_SHIFTS: [u8; 16] = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1];
69
70/// DES S-boxes (8 boxes, each 4x16 = 64 entries, 6 bits in -> 4 bits out).
71const SBOXES: [[u8; 64]; 8] = [
72    // S1
73    [
74        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,
75        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,
76    ],
77    // S2
78    [
79        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,
80        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,
81    ],
82    // S3
83    [
84        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,
85        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,
86    ],
87    // S4
88    [
89        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,
90        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,
91    ],
92    // S5
93    [
94        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,
95        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,
96    ],
97    // S6
98    [
99        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,
100        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,
101    ],
102    // S7
103    [
104        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,
105        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,
106    ],
107    // S8
108    [
109        13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7, 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 2, 0, 14, 9, 11, 7,
110        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,
111    ],
112];
113
114// ============================================================
115// u64-based helpers for DES
116// ============================================================
117
118/// Convert 8 bytes (big-endian) to u64.
119#[inline]
120fn bytes_to_u64(b: &[u8]) -> u64 {
121    ((b[0] as u64) << 56)
122        | ((b[1] as u64) << 48)
123        | ((b[2] as u64) << 40)
124        | ((b[3] as u64) << 32)
125        | ((b[4] as u64) << 24)
126        | ((b[5] as u64) << 16)
127        | ((b[6] as u64) << 8)
128        | (b[7] as u64)
129}
130
131/// Convert u64 to 8 bytes (big-endian).
132#[inline]
133fn u64_to_bytes(v: u64) -> [u8; 8] {
134    v.to_be_bytes()
135}
136
137/// Get bit `pos` (1-indexed, MSB=1) from a u64.
138#[inline]
139fn get_bit64(val: u64, pos: u8) -> u64 {
140    (val >> (64 - pos as u32)) & 1
141}
142
143/// Apply a permutation table on a u64.
144/// `in_width` is the number of significant bits in the input (counted from MSB).
145/// The table entries are 1-indexed positions in the input.
146/// Output bits are placed starting from the MSB of the returned u64.
147fn permute64(input: u64, table: &[u8]) -> u64 {
148    let mut output: u64 = 0;
149    for (i, &pos) in table.iter().enumerate() {
150        let bit = get_bit64(input, pos);
151        output |= bit << (63 - i as u32);
152    }
153    output
154}
155
156// ============================================================
157// DES core
158// ============================================================
159
160/// DES block cipher (64-bit block, 56-bit effective key).
161pub struct Des {
162    /// 16 round subkeys, each 48 bits (stored right-aligned in u64, but
163    /// we keep them in the high 48 bits for consistency).
164    subkeys: [u64; 16],
165}
166
167impl Des {
168    /// Generate the 16 round subkeys from a 64-bit key.
169    fn generate_subkeys(key: u64) -> [u64; 16] {
170        // Apply PC-1 to get 56 bits in the high 56 bits of a u64
171        let pc1 = permute64(key, &PC1);
172
173        // Split into C (high 28 bits) and D (next 28 bits)
174        let mut c: u32 = (pc1 >> 36) as u32; // top 28 bits
175        let mut d: u32 = ((pc1 >> 8) as u32) & 0x0FFFFFFF; // next 28 bits
176
177        let mut subkeys = [0u64; 16];
178
179        for round in 0..16 {
180            let shift = KEY_SHIFTS[round] as u32;
181            // Left-rotate C and D by `shift` positions within 28 bits
182            c = ((c << shift) | (c >> (28 - shift))) & 0x0FFFFFFF;
183            d = ((d << shift) | (d >> (28 - shift))) & 0x0FFFFFFF;
184
185            // Concatenate C and D into 56 bits in the high part of a u64
186            let cd: u64 = ((c as u64) << 36) | ((d as u64) << 8);
187
188            // Apply PC-2 to get 48-bit subkey in high 48 bits
189            subkeys[round] = permute64(cd, &PC2);
190        }
191
192        subkeys
193    }
194
195    /// The DES Feistel function f(R, K).
196    /// R is 32 bits in the high 32 bits of a u64.
197    /// K is 48 bits in the high 48 bits of a u64.
198    /// Returns 32 bits in the high 32 bits of a u64.
199    fn feistel(r: u32, subkey: u64) -> u32 {
200        // Expand R from 32 to 48 bits using E permutation
201        // R is in the high 32 bits... actually let's put R into a u64 in high bits
202        let r64 = (r as u64) << 32;
203        let expanded = permute64(r64, &E_PERM); // 48 bits in high 48 bits
204
205        // XOR with subkey
206        let xored = expanded ^ subkey;
207
208        // Apply 8 S-boxes: extract 6 bits at a time from the high 48 bits
209        let mut sbox_result: u32 = 0;
210        for i in 0..8 {
211            // Extract 6 bits starting at bit position (16 + i*6) from the right
212            // or equivalently, bits [63 - i*6 .. 63 - i*6 - 5] from MSB numbering
213            let shift = 58 - (i * 6); // 58, 52, 46, 40, 34, 28, 22, 16
214            let six_bits = ((xored >> shift) & 0x3F) as u8;
215
216            // Row = outer two bits (bit5, bit0), column = inner four bits (bit4..bit1)
217            let row = ((six_bits & 0x20) >> 4) | (six_bits & 0x01); // bit5 << 1 | bit0
218            let col = (six_bits >> 1) & 0x0F;
219            let val = SBOXES[i as usize][(row as usize) * 16 + (col as usize)];
220
221            // Pack into sbox_result (high 32 bits pattern: 4 bits per S-box)
222            sbox_result |= (val as u32) << (28 - i * 4);
223        }
224
225        // Apply P permutation on the 32-bit S-box output
226        let sbox64 = (sbox_result as u64) << 32;
227        let p_out = permute64(sbox64, &P_PERM);
228        (p_out >> 32) as u32
229    }
230
231    /// Core DES cipher (encrypt or decrypt based on subkey order).
232    fn des_cipher(&self, input: u64, decrypt: bool) -> u64 {
233        // Initial permutation
234        let permuted = permute64(input, &IP);
235
236        let mut l: u32 = (permuted >> 32) as u32;
237        let mut r: u32 = permuted as u32;
238
239        // 16 Feistel rounds
240        for round in 0..16 {
241            let subkey_idx = if decrypt { 15 - round } else { round };
242            let f_out = Self::feistel(r, self.subkeys[subkey_idx]);
243
244            let new_r = l ^ f_out;
245            l = r;
246            r = new_r;
247        }
248
249        // Combine R || L (note the 32-bit swap) and apply final permutation
250        let pre_fp: u64 = ((r as u64) << 32) | (l as u64);
251        permute64(pre_fp, &FP)
252    }
253}
254
255impl BlockCipher for Des {
256    const BLOCK_LEN: usize = 8;
257    const KEY_LENS: &'static [usize] = &[8]; // 64 bits (56 effective + 8 parity)
258
259    fn new(key: &[u8]) -> Self {
260        assert_eq!(key.len(), 8, "DES requires an 8-byte key");
261        Des {
262            subkeys: Self::generate_subkeys(bytes_to_u64(key)),
263        }
264    }
265
266    fn encrypt_block(&self, block: &mut [u8]) {
267        assert!(block.len() >= 8, "DES: block must be at least 8 bytes");
268        let input = bytes_to_u64(&block[..8]);
269        let output = self.des_cipher(input, false);
270        block[..8].copy_from_slice(&u64_to_bytes(output));
271    }
272
273    fn decrypt_block(&self, block: &mut [u8]) {
274        assert!(block.len() >= 8, "DES: block must be at least 8 bytes");
275        let input = bytes_to_u64(&block[..8]);
276        let output = self.des_cipher(input, true);
277        block[..8].copy_from_slice(&u64_to_bytes(output));
278    }
279}
280
281// ============================================================
282// Triple DES (3DES / TDEA) -- EDE mode
283// ============================================================
284
285/// Triple DES in EDE (Encrypt-Decrypt-Encrypt) mode.
286///
287/// Uses three independent DES keys (K1, K2, K3). The 24-byte key is split
288/// into three 8-byte DES keys.
289pub struct TripleDes {
290    k1: Des,
291    k2: Des,
292    k3: Des,
293}
294
295impl BlockCipher for TripleDes {
296    const BLOCK_LEN: usize = 8;
297    const KEY_LENS: &'static [usize] = &[24]; // 3 x 8 bytes
298
299    fn new(key: &[u8]) -> Self {
300        assert_eq!(key.len(), 24, "3DES requires a 24-byte key (3 x 8)");
301        TripleDes {
302            k1: Des::new(&key[0..8]),
303            k2: Des::new(&key[8..16]),
304            k3: Des::new(&key[16..24]),
305        }
306    }
307
308    fn encrypt_block(&self, block: &mut [u8]) {
309        self.k1.encrypt_block(block);
310        self.k2.decrypt_block(block);
311        self.k3.encrypt_block(block);
312    }
313
314    fn decrypt_block(&self, block: &mut [u8]) {
315        self.k3.decrypt_block(block);
316        self.k2.encrypt_block(block);
317        self.k1.decrypt_block(block);
318    }
319}
320
321// ============================================================
322// Tests
323// ============================================================
324
325#[cfg(test)]
326mod tests {
327    use super::*;
328
329    fn hex_to_bytes(s: &str) -> Vec<u8> {
330        (0..s.len())
331            .step_by(2)
332            .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
333            .collect()
334    }
335
336    /// DES ECB known-answer tests from authoritative sources.
337    #[test]
338    fn des_known_answer_tests() {
339        // J. Orlin Grabbe DES tutorial vector
340        let k1 = Des::new(&hex_to_bytes("133457799BBCDFF1"));
341        let mut b1 = hex_to_bytes("0123456789ABCDEF");
342        k1.encrypt_block(&mut b1);
343        assert_eq!(b1, hex_to_bytes("85E813540F0AB405").as_slice());
344        k1.decrypt_block(&mut b1);
345        assert_eq!(b1, hex_to_bytes("0123456789ABCDEF").as_slice());
346
347        // Key=FEDCBA9876543210, PT=0123456789ABCDEF -> ED39D950FA74BCC4
348        let k2 = Des::new(&hex_to_bytes("FEDCBA9876543210"));
349        let mut b2 = hex_to_bytes("0123456789ABCDEF");
350        k2.encrypt_block(&mut b2);
351        assert_eq!(b2, hex_to_bytes("ED39D950FA74BCC4").as_slice());
352        k2.decrypt_block(&mut b2);
353        assert_eq!(b2, hex_to_bytes("0123456789ABCDEF").as_slice());
354
355        // Key=0123456789ABCDEF, PT=0000000000000000 -> D5D44FF720683D0D
356        let k3 = Des::new(&hex_to_bytes("0123456789ABCDEF"));
357        let mut b3 = [0u8; 8];
358        k3.encrypt_block(&mut b3);
359        assert_eq!(b3.to_vec(), hex_to_bytes("D5D44FF720683D0D"));
360
361        // Key=0123456789ABCDEF, PT=0123456789ABCDEF -> 56CC09E7CFDC4CEF
362        let mut b4 = hex_to_bytes("0123456789ABCDEF");
363        k3.encrypt_block(&mut b4);
364        assert_eq!(b4, hex_to_bytes("56CC09E7CFDC4CEF").as_slice());
365    }
366
367    /// DES round-trip with various inputs.
368    #[test]
369    fn des_round_trip() {
370        let cipher = Des::new(&hex_to_bytes("0123456789ABCDEF"));
371        for pt_hex in &[
372            "0000000000000000",
373            "FFFFFFFFFFFFFFFF",
374            "4E6F772069732074",
375            "0123456789ABCDEF",
376        ] {
377            let pt = hex_to_bytes(pt_hex);
378            let mut block = pt.clone();
379            cipher.encrypt_block(&mut block);
380            assert_ne!(block, pt.as_slice(), "CT should differ from PT for {}", pt_hex);
381            cipher.decrypt_block(&mut block);
382            assert_eq!(block, pt.as_slice(), "round-trip failed for PT={}", pt_hex);
383        }
384    }
385
386    /// 3DES round-trip test.
387    #[test]
388    fn triple_des_round_trip() {
389        let key = hex_to_bytes("0123456789ABCDEF23456789ABCDEF01456789ABCDEF0123");
390        let plaintext = hex_to_bytes("4E6F772069732074");
391
392        let cipher = TripleDes::new(&key);
393
394        let mut block = plaintext.clone();
395        cipher.encrypt_block(&mut block);
396        assert_ne!(block, plaintext.as_slice());
397
398        cipher.decrypt_block(&mut block);
399        assert_eq!(block, plaintext.as_slice());
400    }
401
402    /// 3DES EDE consistency: TripleDes matches manual E-D-E.
403    #[test]
404    fn triple_des_ede_consistency() {
405        let key = hex_to_bytes("0123456789ABCDEF23456789ABCDEF01456789ABCDEF0123");
406        let plaintext = hex_to_bytes("4E6F772069732074");
407        let cipher = TripleDes::new(&key);
408
409        let mut block = plaintext.clone();
410        cipher.encrypt_block(&mut block);
411
412        // Verify by manual EDE
413        let k1 = Des::new(&hex_to_bytes("0123456789ABCDEF"));
414        let k2 = Des::new(&hex_to_bytes("23456789ABCDEF01"));
415        let k3 = Des::new(&hex_to_bytes("456789ABCDEF0123"));
416
417        let mut manual = plaintext.clone();
418        k1.encrypt_block(&mut manual);
419        k2.decrypt_block(&mut manual);
420        k3.encrypt_block(&mut manual);
421
422        assert_eq!(block, manual.as_slice(), "3DES EDE mismatch");
423    }
424}