Skip to main content

dvb_csa/
csa.rs

1//! CSA combination — block cipher + stream cipher.
2//!
3//! DVB-CSA2 encrypt/decrypt consists of two phases applied in a specific order:
4//!
5//! **Encrypt** (scramble):
6//! 1. Block-cipher CBC: encrypt the last block first, then work backward,
7//!    XORing each plaintext block into the next before encrypting.
8//! 2. Stream-cipher XOR: seed from the (now-encrypted) first block, XOR bytes 8..end.
9//!
10//! **Decrypt** (descramble):
11//! 1. Stream-cipher XOR: seed from the (still-encrypted) first block, XOR bytes 8..end.
12//! 2. Block-cipher CBC undo: decrypt first block, then work forward,
13//!    XORing each decrypted block into the next before decrypting.
14//!
15//! Payloads shorter than 8 bytes are passed through unchanged.
16use crate::block::BlockCipher;
17use crate::key::ControlWord;
18use crate::stream::StreamCipher;
19
20/// Scramble (encrypt) `data` in-place with the given control word.
21///
22/// Payloads shorter than 8 bytes are not scrambled.
23pub fn scramble(cw: &ControlWord, data: &mut [u8]) {
24    let len = data.len();
25    if len < 8 {
26        return;
27    }
28
29    let sch = cw.expand_block();
30    let cws = cw.expand_stream();
31    let bc = BlockCipher::new(sch);
32
33    let nblocks = len / 8;
34
35    // Phase 1: Block cipher, reverse CBC
36    // Encrypt the last block first (ECB)
37    bc.encrypt_block(&mut data[(nblocks - 1) * 8..nblocks * 8]);
38
39    // Work backward: XOR (already-encrypted) block i+1 INTO block i, then encrypt block i
40    for i in (0..nblocks - 1).rev() {
41        for j in 0..8 {
42            data[i * 8 + j] ^= data[(i + 1) * 8 + j];
43        }
44        bc.encrypt_block(&mut data[i * 8..(i + 1) * 8]);
45    }
46
47    // Phase 2: Stream cipher XOR bytes 8..len
48    let iv: [u8; 8] = data[0..8].try_into().unwrap();
49    let mut sc = StreamCipher::new(&cws, &iv);
50    sc.xor_stream(&mut data[8..]);
51}
52
53/// Descramble (decrypt) `data` in-place with the given control word.
54///
55/// Payloads shorter than 8 bytes are not descrambled.
56pub fn descramble(cw: &ControlWord, data: &mut [u8]) {
57    let len = data.len();
58    if len < 8 {
59        return;
60    }
61
62    let sch = cw.expand_block();
63    let cws = cw.expand_stream();
64    let bc = BlockCipher::new(sch);
65
66    // Phase 1: Stream cipher XOR bytes 8..len (using encrypted first block as IV)
67    let iv: [u8; 8] = data[0..8].try_into().unwrap();
68    let mut sc = StreamCipher::new(&cws, &iv);
69    sc.xor_stream(&mut data[8..]);
70
71    // Phase 2: Block cipher, forward CBC undo
72    let nblocks = len / 8;
73
74    // Decrypt first block (ECB)
75    bc.decrypt_block(&mut data[0..8]);
76
77    // Work forward: XOR current (still encrypted) block INTO previous (decrypted), then decrypt current
78    for i in 1..nblocks {
79        for j in 0..8 {
80            data[(i - 1) * 8 + j] ^= data[i * 8 + j];
81        }
82        bc.decrypt_block(&mut data[i * 8..(i + 1) * 8]);
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn short_payload_unchanged() {
92        let cw = ControlWord::from_bytes([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]);
93        let mut data = [0xaa, 0xbb, 0xcc, 0xdd];
94        let orig = data;
95        scramble(&cw, &mut data);
96        assert_eq!(data, orig);
97        descramble(&cw, &mut data);
98        assert_eq!(data, orig);
99    }
100
101    #[test]
102    fn roundtrip() {
103        let cw = ControlWord::from_bytes([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]);
104        // Use first 16 bytes from golden vector 1 plaintext
105        let plaintext: [u8; 16] = [
106            0x00, 0x08, 0x10, 0x18, 0x20, 0x28, 0x30, 0x38, 0x40, 0x48, 0x50, 0x58, 0x60, 0x5b,
107            0x63, 0x6b,
108        ];
109        let mut data = plaintext;
110        scramble(&cw, &mut data);
111        assert_ne!(data, plaintext);
112        descramble(&cw, &mut data);
113        assert_eq!(data, plaintext);
114    }
115
116    #[test]
117    fn vector_13_16byte() {
118        let cw = ControlWord::from_bytes([0xf0, 0x70, 0x94, 0xf2, 0xca, 0x22, 0x74, 0x32]);
119        let plaintext: [u8; 16] = [
120            0x1e, 0x25, 0x49, 0x31, 0x30, 0x96, 0xf4, 0xe4, 0xc2, 0x79, 0x3c, 0x92, 0x54, 0x44,
121            0x66, 0x40,
122        ];
123        let expected: [u8; 16] = [
124            0x3d, 0xf6, 0x9d, 0xf1, 0x4e, 0x97, 0x82, 0x31, 0x1e, 0x31, 0xe6, 0x0f, 0xe0, 0x70,
125            0x17, 0x4e,
126        ];
127
128        let mut data = plaintext;
129        scramble(&cw, &mut data);
130        assert_eq!(data, expected, "Vector 13 scramble mismatch");
131
132        let mut data2 = expected;
133        descramble(&cw, &mut data2);
134        assert_eq!(data2, plaintext, "Vector 13 descramble mismatch");
135    }
136
137    #[test]
138    fn vector_15_64byte() {
139        let cw = ControlWord::from_bytes([0xc6, 0xe6, 0x2a, 0x81, 0xff, 0xd6, 0x18, 0xea]);
140        let plaintext: [u8; 64] = [
141            0x11, 0x80, 0xf1, 0x5b, 0x59, 0xf0, 0x0e, 0x95, 0x78, 0xd5, 0x74, 0x6a, 0x89, 0x10,
142            0x64, 0x2c, 0x1f, 0xfe, 0xc1, 0xd7, 0x01, 0x86, 0x26, 0xfe, 0xa5, 0xe1, 0xc3, 0x76,
143            0x6d, 0x2c, 0xfc, 0xc4, 0xa3, 0xaf, 0xc0, 0x47, 0x36, 0xeb, 0xa9, 0x24, 0x32, 0x66,
144            0xd8, 0xf6, 0x01, 0x35, 0x8e, 0x33, 0x63, 0x4e, 0x8b, 0x5c, 0x2e, 0x8b, 0x27, 0xec,
145            0xc7, 0x7d, 0x31, 0xfe, 0x5c, 0xf6, 0x8b, 0xbc,
146        ];
147        let expected: [u8; 64] = [
148            0x45, 0xd2, 0x49, 0x70, 0x85, 0x52, 0x6f, 0x02, 0x12, 0x94, 0x39, 0x41, 0x07, 0x2e,
149            0x59, 0x3a, 0x28, 0x93, 0x92, 0xf1, 0x99, 0x7e, 0x2c, 0x0e, 0x5d, 0x3d, 0x72, 0xd5,
150            0xe3, 0x9a, 0xc6, 0x70, 0x39, 0x81, 0x31, 0x43, 0x83, 0x2c, 0xd5, 0xc8, 0xf5, 0xd2,
151            0x7a, 0x28, 0x2b, 0xff, 0x5f, 0x4d, 0xb3, 0xc7, 0x2e, 0xce, 0xa8, 0xb1, 0xff, 0xbf,
152            0x7d, 0x12, 0x4d, 0x53, 0xe2, 0xa7, 0x91, 0x4b,
153        ];
154
155        let mut data = plaintext;
156        scramble(&cw, &mut data);
157        assert_eq!(data, expected, "Vector 15 scramble mismatch");
158
159        let mut data2 = expected;
160        descramble(&cw, &mut data2);
161        assert_eq!(data2, plaintext, "Vector 15 descramble mismatch");
162    }
163
164    #[test]
165    fn vector_14_32byte() {
166        // Vector 14: CW=1e279addca1cf532, 32 bytes
167        let cw = ControlWord::from_bytes([0x1e, 0x27, 0x9a, 0xdd, 0xca, 0x1c, 0xf5, 0x32]);
168        let plaintext: [u8; 32] = [
169            0xf1, 0xb3, 0x6d, 0x88, 0x5c, 0x9b, 0x68, 0x16, 0xf7, 0xef, 0x1c, 0x31, 0x94, 0x46,
170            0xa5, 0x32, 0x66, 0x79, 0xe7, 0x26, 0x38, 0x32, 0x29, 0x39, 0x70, 0x39, 0x6e, 0xdf,
171            0xc7, 0x7e, 0x93, 0xc8,
172        ];
173        let expected: [u8; 32] = [
174            0xb9, 0x59, 0x3e, 0xbd, 0xe4, 0xae, 0x0a, 0x30, 0xc7, 0x57, 0xe8, 0x6f, 0xf7, 0x6e,
175            0x7a, 0x42, 0xb1, 0x17, 0x03, 0x16, 0xca, 0x69, 0x5d, 0x8b, 0x0f, 0x73, 0x7e, 0x1b,
176            0x62, 0x4e, 0x55, 0x54,
177        ];
178
179        let mut data = plaintext;
180        scramble(&cw, &mut data);
181        assert_eq!(data, expected, "Vector 14 scramble mismatch");
182
183        let mut data2 = expected;
184        descramble(&cw, &mut data2);
185        assert_eq!(data2, plaintext, "Vector 14 descramble mismatch");
186    }
187}