Skip to main content

cryptography/ciphers/
cast128.rs

1//! CAST-128 / CAST5 block cipher — RFC 2144.
2//!
3//! 64-bit block cipher with variable key sizes from 40 to 128 bits in 8-bit
4//! increments. The default `Cast128::new` constructor uses the full 128-bit
5//! key size and therefore all 16 rounds. `with_key_bytes` supports the RFC's
6//! shorter key sizes and automatically drops to 12 rounds for keys up to
7//! and including 80 bits.
8//!
9//! The fast path keeps the direct 8-bit S-box tables from RFC 2144 Appendix A.
10//! `Cast128Ct` uses a fixed-scan table selection helper so the round function
11//! and key schedule avoid secret-indexed table reads in portable software.
12
13use crate::ct::{ct_lookup_u32, zeroize_slice};
14use crate::BlockCipher;
15
16include!("cast128_tables.rs");
17
18#[inline]
19fn sbox(table: &[u32; 256], idx: u8, use_ct: bool) -> u32 {
20    if use_ct {
21        ct_lookup_u32(table, idx)
22    } else {
23        table[idx as usize]
24    }
25}
26
27#[inline]
28fn pack(bytes: &[u8; 16], a: usize, b: usize, c: usize, d: usize) -> u32 {
29    u32::from_be_bytes([bytes[a], bytes[b], bytes[c], bytes[d]])
30}
31
32#[inline]
33fn unpack(bytes: &mut [u8; 16], start: usize, value: u32) {
34    bytes[start..start + 4].copy_from_slice(&value.to_be_bytes());
35}
36
37// The RFC key schedule alternates between 16-byte `x` and `z` states; these
38// helpers implement those byte-to-byte recurrences directly so the published
39// K1..K32 formulas stay readable.
40fn x_to_z(x: &[u8; 16], use_ct: bool) -> [u8; 16] {
41    let mut z = [0u8; 16];
42    let w0 = pack(x, 0, 1, 2, 3)
43        ^ sbox(&S5, x[13], use_ct)
44        ^ sbox(&S6, x[15], use_ct)
45        ^ sbox(&S7, x[12], use_ct)
46        ^ sbox(&S8, x[14], use_ct)
47        ^ sbox(&S7, x[8], use_ct);
48    unpack(&mut z, 0, w0);
49
50    let w1 = pack(x, 8, 9, 10, 11)
51        ^ sbox(&S5, z[0], use_ct)
52        ^ sbox(&S6, z[2], use_ct)
53        ^ sbox(&S7, z[1], use_ct)
54        ^ sbox(&S8, z[3], use_ct)
55        ^ sbox(&S8, x[10], use_ct);
56    unpack(&mut z, 4, w1);
57
58    let w2 = pack(x, 12, 13, 14, 15)
59        ^ sbox(&S5, z[7], use_ct)
60        ^ sbox(&S6, z[6], use_ct)
61        ^ sbox(&S7, z[5], use_ct)
62        ^ sbox(&S8, z[4], use_ct)
63        ^ sbox(&S5, x[9], use_ct);
64    unpack(&mut z, 8, w2);
65
66    let w3 = pack(x, 4, 5, 6, 7)
67        ^ sbox(&S5, z[10], use_ct)
68        ^ sbox(&S6, z[9], use_ct)
69        ^ sbox(&S7, z[11], use_ct)
70        ^ sbox(&S8, z[8], use_ct)
71        ^ sbox(&S6, x[11], use_ct);
72    unpack(&mut z, 12, w3);
73
74    z
75}
76
77fn z_to_x(z: &[u8; 16], use_ct: bool) -> [u8; 16] {
78    let mut x = [0u8; 16];
79    let w0 = pack(z, 8, 9, 10, 11)
80        ^ sbox(&S5, z[5], use_ct)
81        ^ sbox(&S6, z[7], use_ct)
82        ^ sbox(&S7, z[4], use_ct)
83        ^ sbox(&S8, z[6], use_ct)
84        ^ sbox(&S7, z[0], use_ct);
85    unpack(&mut x, 0, w0);
86
87    let w1 = pack(z, 0, 1, 2, 3)
88        ^ sbox(&S5, x[0], use_ct)
89        ^ sbox(&S6, x[2], use_ct)
90        ^ sbox(&S7, x[1], use_ct)
91        ^ sbox(&S8, x[3], use_ct)
92        ^ sbox(&S8, z[2], use_ct);
93    unpack(&mut x, 4, w1);
94
95    let w2 = pack(z, 4, 5, 6, 7)
96        ^ sbox(&S5, x[7], use_ct)
97        ^ sbox(&S6, x[6], use_ct)
98        ^ sbox(&S7, x[5], use_ct)
99        ^ sbox(&S8, x[4], use_ct)
100        ^ sbox(&S5, z[1], use_ct);
101    unpack(&mut x, 8, w2);
102
103    let w3 = pack(z, 12, 13, 14, 15)
104        ^ sbox(&S5, x[10], use_ct)
105        ^ sbox(&S6, x[9], use_ct)
106        ^ sbox(&S7, x[11], use_ct)
107        ^ sbox(&S8, x[8], use_ct)
108        ^ sbox(&S6, z[3], use_ct);
109    unpack(&mut x, 12, w3);
110
111    x
112}
113
114fn extract_z_a(z: &[u8; 16], use_ct: bool) -> [u32; 4] {
115    [
116        sbox(&S5, z[8], use_ct)
117            ^ sbox(&S6, z[9], use_ct)
118            ^ sbox(&S7, z[7], use_ct)
119            ^ sbox(&S8, z[6], use_ct)
120            ^ sbox(&S5, z[2], use_ct),
121        sbox(&S5, z[10], use_ct)
122            ^ sbox(&S6, z[11], use_ct)
123            ^ sbox(&S7, z[5], use_ct)
124            ^ sbox(&S8, z[4], use_ct)
125            ^ sbox(&S6, z[6], use_ct),
126        sbox(&S5, z[12], use_ct)
127            ^ sbox(&S6, z[13], use_ct)
128            ^ sbox(&S7, z[3], use_ct)
129            ^ sbox(&S8, z[2], use_ct)
130            ^ sbox(&S7, z[9], use_ct),
131        sbox(&S5, z[14], use_ct)
132            ^ sbox(&S6, z[15], use_ct)
133            ^ sbox(&S7, z[1], use_ct)
134            ^ sbox(&S8, z[0], use_ct)
135            ^ sbox(&S8, z[12], use_ct),
136    ]
137}
138
139fn extract_x_a(x: &[u8; 16], use_ct: bool) -> [u32; 4] {
140    [
141        sbox(&S5, x[3], use_ct)
142            ^ sbox(&S6, x[2], use_ct)
143            ^ sbox(&S7, x[12], use_ct)
144            ^ sbox(&S8, x[13], use_ct)
145            ^ sbox(&S5, x[8], use_ct),
146        sbox(&S5, x[1], use_ct)
147            ^ sbox(&S6, x[0], use_ct)
148            ^ sbox(&S7, x[14], use_ct)
149            ^ sbox(&S8, x[15], use_ct)
150            ^ sbox(&S6, x[13], use_ct),
151        sbox(&S5, x[7], use_ct)
152            ^ sbox(&S6, x[6], use_ct)
153            ^ sbox(&S7, x[8], use_ct)
154            ^ sbox(&S8, x[9], use_ct)
155            ^ sbox(&S7, x[3], use_ct),
156        sbox(&S5, x[5], use_ct)
157            ^ sbox(&S6, x[4], use_ct)
158            ^ sbox(&S7, x[10], use_ct)
159            ^ sbox(&S8, x[11], use_ct)
160            ^ sbox(&S8, x[7], use_ct),
161    ]
162}
163
164fn extract_z_b(z: &[u8; 16], use_ct: bool) -> [u32; 4] {
165    [
166        sbox(&S5, z[3], use_ct)
167            ^ sbox(&S6, z[2], use_ct)
168            ^ sbox(&S7, z[12], use_ct)
169            ^ sbox(&S8, z[13], use_ct)
170            ^ sbox(&S5, z[9], use_ct),
171        sbox(&S5, z[1], use_ct)
172            ^ sbox(&S6, z[0], use_ct)
173            ^ sbox(&S7, z[14], use_ct)
174            ^ sbox(&S8, z[15], use_ct)
175            ^ sbox(&S6, z[12], use_ct),
176        sbox(&S5, z[7], use_ct)
177            ^ sbox(&S6, z[6], use_ct)
178            ^ sbox(&S7, z[8], use_ct)
179            ^ sbox(&S8, z[9], use_ct)
180            ^ sbox(&S7, z[2], use_ct),
181        sbox(&S5, z[5], use_ct)
182            ^ sbox(&S6, z[4], use_ct)
183            ^ sbox(&S7, z[10], use_ct)
184            ^ sbox(&S8, z[11], use_ct)
185            ^ sbox(&S8, z[6], use_ct),
186    ]
187}
188
189fn extract_x_b(x: &[u8; 16], use_ct: bool) -> [u32; 4] {
190    [
191        sbox(&S5, x[8], use_ct)
192            ^ sbox(&S6, x[9], use_ct)
193            ^ sbox(&S7, x[7], use_ct)
194            ^ sbox(&S8, x[6], use_ct)
195            ^ sbox(&S5, x[3], use_ct),
196        sbox(&S5, x[10], use_ct)
197            ^ sbox(&S6, x[11], use_ct)
198            ^ sbox(&S7, x[5], use_ct)
199            ^ sbox(&S8, x[4], use_ct)
200            ^ sbox(&S6, x[7], use_ct),
201        sbox(&S5, x[12], use_ct)
202            ^ sbox(&S6, x[13], use_ct)
203            ^ sbox(&S7, x[3], use_ct)
204            ^ sbox(&S8, x[2], use_ct)
205            ^ sbox(&S7, x[8], use_ct),
206        sbox(&S5, x[14], use_ct)
207            ^ sbox(&S6, x[15], use_ct)
208            ^ sbox(&S7, x[1], use_ct)
209            ^ sbox(&S8, x[0], use_ct)
210            ^ sbox(&S8, x[13], use_ct),
211    ]
212}
213
214fn round_f(data: u32, km: u32, kr: u8, round: usize, use_ct: bool) -> u32 {
215    // CAST cycles through three different mixing formulas. The modulo on the
216    // round index is the exact RFC rule for selecting F1 / F2 / F3.
217    let i = match round % 3 {
218        0 => km.wrapping_add(data).rotate_left(u32::from(kr)),
219        1 => (km ^ data).rotate_left(u32::from(kr)),
220        _ => km.wrapping_sub(data).rotate_left(u32::from(kr)),
221    };
222    let [ia, ib, ic, id] = i.to_be_bytes();
223    match round % 3 {
224        0 => (sbox(&S1, ia, use_ct) ^ sbox(&S2, ib, use_ct))
225            .wrapping_sub(sbox(&S3, ic, use_ct))
226            .wrapping_add(sbox(&S4, id, use_ct)),
227        1 => {
228            (sbox(&S1, ia, use_ct).wrapping_sub(sbox(&S2, ib, use_ct)))
229                .wrapping_add(sbox(&S3, ic, use_ct))
230                ^ sbox(&S4, id, use_ct)
231        }
232        _ => ((sbox(&S1, ia, use_ct).wrapping_add(sbox(&S2, ib, use_ct))) ^ sbox(&S3, ic, use_ct))
233            .wrapping_sub(sbox(&S4, id, use_ct)),
234    }
235}
236
237#[derive(Clone, Copy)]
238struct Subkeys {
239    km: [u32; 16],
240    kr: [u8; 16],
241    rounds: usize,
242}
243
244fn expand_subkeys(key: &[u8], use_ct: bool) -> Subkeys {
245    assert!(
246        (5..=16).contains(&key.len()),
247        "CAST-128 key length must be 5..=16 bytes, got {}",
248        key.len()
249    );
250
251    let mut x = [0u8; 16];
252    x[..key.len()].copy_from_slice(key);
253    // Short keys are right-padded with zeros and switch to 12 rounds at 80
254    // bits and below, exactly as RFC 2144 specifies for CAST5-nn variants.
255    let rounds = if key.len() * 8 <= 80 { 12 } else { 16 };
256
257    let mut k = [0u32; 32];
258    let mut offset = 0usize;
259
260    for _ in 0..2 {
261        // Each x/z cycle emits 16 of the RFC's intermediate K words; the
262        // first half becomes masking subkeys, the second half rotation subkeys.
263        let z = x_to_z(&x, use_ct);
264        k[offset..offset + 4].copy_from_slice(&extract_z_a(&z, use_ct));
265        x = z_to_x(&z, use_ct);
266        k[offset + 4..offset + 8].copy_from_slice(&extract_x_a(&x, use_ct));
267        let z = x_to_z(&x, use_ct);
268        k[offset + 8..offset + 12].copy_from_slice(&extract_z_b(&z, use_ct));
269        x = z_to_x(&z, use_ct);
270        k[offset + 12..offset + 16].copy_from_slice(&extract_x_b(&x, use_ct));
271        offset += 16;
272    }
273
274    let mut km = [0u32; 16];
275    let mut kr = [0u8; 16];
276    let mut i = 0usize;
277    while i < 16 {
278        km[i] = k[i];
279        kr[i] = (k[16 + i] & 0x1f) as u8;
280        i += 1;
281    }
282
283    Subkeys { km, kr, rounds }
284}
285
286fn cast_encrypt(block: [u8; 8], subkeys: &Subkeys, use_ct: bool) -> [u8; 8] {
287    let mut l = u32::from_be_bytes(block[0..4].try_into().unwrap());
288    let mut r = u32::from_be_bytes(block[4..8].try_into().unwrap());
289
290    let mut i = 0usize;
291    while i < subkeys.rounds {
292        // Standard Feistel step: the right half becomes the next left half,
293        // and the previous left half is mixed with F(right, subkey).
294        let new_l = r;
295        let new_r = l ^ round_f(r, subkeys.km[i], subkeys.kr[i], i, use_ct);
296        l = new_l;
297        r = new_r;
298        i += 1;
299    }
300
301    let mut out = [0u8; 8];
302    out[0..4].copy_from_slice(&r.to_be_bytes());
303    out[4..8].copy_from_slice(&l.to_be_bytes());
304    out
305}
306
307fn cast_decrypt(block: [u8; 8], subkeys: &Subkeys, use_ct: bool) -> [u8; 8] {
308    let mut l = u32::from_be_bytes(block[0..4].try_into().unwrap());
309    let mut r = u32::from_be_bytes(block[4..8].try_into().unwrap());
310
311    let mut idx = subkeys.rounds;
312    while idx > 0 {
313        idx -= 1;
314        // Decryption is the same Feistel structure with the round keys applied
315        // in reverse order.
316        let new_l = r;
317        let new_r = l ^ round_f(r, subkeys.km[idx], subkeys.kr[idx], idx, use_ct);
318        l = new_l;
319        r = new_r;
320    }
321
322    let mut out = [0u8; 8];
323    out[0..4].copy_from_slice(&r.to_be_bytes());
324    out[4..8].copy_from_slice(&l.to_be_bytes());
325    out
326}
327
328pub struct Cast128 {
329    subkeys: Subkeys,
330}
331
332impl Cast128 {
333    #[must_use]
334    pub fn new(key: &[u8; 16]) -> Self {
335        Self {
336            subkeys: expand_subkeys(key, false),
337        }
338    }
339
340    #[must_use]
341    pub fn with_key_bytes(key: &[u8]) -> Self {
342        Self {
343            subkeys: expand_subkeys(key, false),
344        }
345    }
346
347    pub fn with_key_bytes_wiping(key: &mut [u8]) -> Self {
348        let out = Self::with_key_bytes(key);
349        zeroize_slice(key);
350        out
351    }
352
353    pub fn new_wiping(key: &mut [u8; 16]) -> Self {
354        let out = Self::new(key);
355        zeroize_slice(key);
356        out
357    }
358
359    #[must_use]
360    pub fn encrypt_block(&self, block: &[u8; 8]) -> [u8; 8] {
361        cast_encrypt(*block, &self.subkeys, false)
362    }
363
364    #[must_use]
365    pub fn decrypt_block(&self, block: &[u8; 8]) -> [u8; 8] {
366        cast_decrypt(*block, &self.subkeys, false)
367    }
368}
369
370impl BlockCipher for Cast128 {
371    const BLOCK_LEN: usize = 8;
372
373    fn encrypt(&self, block: &mut [u8]) {
374        let arr: &[u8; 8] = (&*block).try_into().expect("wrong block length");
375        let ct = self.encrypt_block(arr);
376        block.copy_from_slice(&ct);
377    }
378
379    fn decrypt(&self, block: &mut [u8]) {
380        let arr: &[u8; 8] = (&*block).try_into().expect("wrong block length");
381        let pt = self.decrypt_block(arr);
382        block.copy_from_slice(&pt);
383    }
384}
385
386impl Drop for Cast128 {
387    fn drop(&mut self) {
388        zeroize_slice(&mut self.subkeys.km);
389        zeroize_slice(&mut self.subkeys.kr);
390    }
391}
392
393pub struct Cast128Ct {
394    subkeys: Subkeys,
395}
396
397impl Cast128Ct {
398    #[must_use]
399    pub fn new(key: &[u8; 16]) -> Self {
400        Self {
401            subkeys: expand_subkeys(key, true),
402        }
403    }
404
405    #[must_use]
406    pub fn with_key_bytes(key: &[u8]) -> Self {
407        Self {
408            subkeys: expand_subkeys(key, true),
409        }
410    }
411
412    pub fn with_key_bytes_wiping(key: &mut [u8]) -> Self {
413        let out = Self::with_key_bytes(key);
414        zeroize_slice(key);
415        out
416    }
417
418    pub fn new_wiping(key: &mut [u8; 16]) -> Self {
419        let out = Self::new(key);
420        zeroize_slice(key);
421        out
422    }
423
424    #[must_use]
425    pub fn encrypt_block(&self, block: &[u8; 8]) -> [u8; 8] {
426        cast_encrypt(*block, &self.subkeys, true)
427    }
428
429    #[must_use]
430    pub fn decrypt_block(&self, block: &[u8; 8]) -> [u8; 8] {
431        cast_decrypt(*block, &self.subkeys, true)
432    }
433}
434
435impl BlockCipher for Cast128Ct {
436    const BLOCK_LEN: usize = 8;
437
438    fn encrypt(&self, block: &mut [u8]) {
439        let arr: &[u8; 8] = (&*block).try_into().expect("wrong block length");
440        let ct = self.encrypt_block(arr);
441        block.copy_from_slice(&ct);
442    }
443
444    fn decrypt(&self, block: &mut [u8]) {
445        let arr: &[u8; 8] = (&*block).try_into().expect("wrong block length");
446        let pt = self.decrypt_block(arr);
447        block.copy_from_slice(&pt);
448    }
449}
450
451impl Drop for Cast128Ct {
452    fn drop(&mut self) {
453        zeroize_slice(&mut self.subkeys.km);
454        zeroize_slice(&mut self.subkeys.kr);
455    }
456}
457
458pub type Cast5 = Cast128;
459pub type Cast5Ct = Cast128Ct;
460
461#[cfg(test)]
462mod tests {
463    use super::*;
464
465    fn decode_hex(s: &str) -> Vec<u8> {
466        assert_eq!(s.len() % 2, 0);
467        let mut out = Vec::with_capacity(s.len() / 2);
468        let bytes = s.as_bytes();
469        let mut i = 0usize;
470        while i < bytes.len() {
471            let hi = (bytes[i] as char).to_digit(16).unwrap();
472            let lo = (bytes[i + 1] as char).to_digit(16).unwrap();
473            out.push(u8::try_from((hi << 4) | lo).expect("decoded hex byte fits in u8"));
474            i += 2;
475        }
476        out
477    }
478
479    #[test]
480    fn cast128_128bit_kat() {
481        let key: [u8; 16] = decode_hex("0123456712345678234567893456789A")
482            .try_into()
483            .unwrap();
484        let pt: [u8; 8] = decode_hex("0123456789ABCDEF").try_into().unwrap();
485        let ct: [u8; 8] = decode_hex("238B4FE5847E44B2").try_into().unwrap();
486        let cipher = Cast128::new(&key);
487        assert_eq!(cipher.encrypt_block(&pt), ct);
488        assert_eq!(cipher.decrypt_block(&ct), pt);
489        let cipher_ct = Cast128Ct::new(&key);
490        assert_eq!(cipher_ct.encrypt_block(&pt), ct);
491        assert_eq!(cipher_ct.decrypt_block(&ct), pt);
492    }
493
494    #[test]
495    fn cast128_80bit_kat() {
496        let key = decode_hex("01234567123456782345");
497        let pt: [u8; 8] = decode_hex("0123456789ABCDEF").try_into().unwrap();
498        let ct: [u8; 8] = decode_hex("EB6A711A2C02271B").try_into().unwrap();
499        let cipher = Cast128::with_key_bytes(&key);
500        assert_eq!(cipher.encrypt_block(&pt), ct);
501        assert_eq!(cipher.decrypt_block(&ct), pt);
502        let cipher_ct = Cast128Ct::with_key_bytes(&key);
503        assert_eq!(cipher_ct.encrypt_block(&pt), ct);
504        assert_eq!(cipher_ct.decrypt_block(&ct), pt);
505    }
506
507    #[test]
508    fn cast128_40bit_kat() {
509        let key = decode_hex("0123456712");
510        let pt: [u8; 8] = decode_hex("0123456789ABCDEF").try_into().unwrap();
511        let ct: [u8; 8] = decode_hex("7AC816D16E9B302E").try_into().unwrap();
512        let cipher = Cast128::with_key_bytes(&key);
513        assert_eq!(cipher.encrypt_block(&pt), ct);
514        assert_eq!(cipher.decrypt_block(&ct), pt);
515        let cipher_ct = Cast128Ct::with_key_bytes(&key);
516        assert_eq!(cipher_ct.encrypt_block(&pt), ct);
517        assert_eq!(cipher_ct.decrypt_block(&ct), pt);
518    }
519
520    #[test]
521    fn cast128_matches_openssl_ecb() {
522        let key_hex = "0123456712345678234567893456789a";
523        let pt_hex = "0123456789abcdef";
524        let pt: [u8; 8] = decode_hex(pt_hex).try_into().unwrap();
525        let Some(expected) = crate::test_utils::run_openssl_enc("-cast5-ecb", key_hex, None, &pt) else {
526            return;
527        };
528
529        let key: [u8; 16] = decode_hex(key_hex).try_into().unwrap();
530        let cipher = Cast128::new(&key);
531        assert_eq!(cipher.encrypt_block(&pt).as_slice(), expected.as_slice());
532    }
533}