Skip to main content

kcptun_rust/
qpp.rs

1//! Quantum Permutation Pad (QPP) - compatible with github.com/xtaci/qpp
2//! Based on Kuang's Quantum Permutation Pad for quantum-resistant encryption.
3
4use aes::Aes256;
5use cipher::{BlockEncryptMut, KeyInit};
6use cipher::generic_array::GenericArray;
7use hmac::{Hmac, Mac};
8use num_bigint::BigUint;
9use pbkdf2::pbkdf2_hmac;
10use sha1::Sha1;
11use sha2::Sha256;
12use std::convert::TryInto;
13
14type HmacSha256 = Hmac<Sha256>;
15
16const PAD_IDENTIFIER: &str = "QPP_";
17const PM_SELECTOR_IDENTIFIER: &[u8] = b"PERMUTATION_MATRIX_SELECTOR";
18const SHUFFLE_SALT: &[u8] = b"___QUANTUM_PERMUTATION_PAD_SHUFFLE_SALT___";
19const PRNG_SALT: &[u8] = b"___QUANTUM_PERMUTATION_PAD_PRNG_SALT___";
20const CHUNK_DERIVE_SALT: &[u8] = b"___QUANTUM_PERMUTATION_PAD_SEED_DERIVE___";
21const PBKDF2_LOOPS: u32 = 128;
22const CHUNK_DERIVE_LOOPS: u32 = 1024;
23const PAD_SWITCH: u8 = 8;
24const QUBITS: u8 = 8;
25const MATRIX_BYTES: usize = 1 << QUBITS; // 256
26
27/// PRNG state - xoshiro256** compatible with Go
28#[derive(Clone)]
29pub struct Rand {
30    pub xoshiro: [u64; 4],
31    pub seed64: u64,
32    pub count: u8,
33}
34
35fn rol64(x: u64, k: i32) -> u64 {
36    (x << k) | (x >> (64 - k))
37}
38
39/// xoshiro256** - must match Go's prng.go
40fn xoshiro256ss(s: &mut [u64; 4]) -> u64 {
41    let result = rol64(s[1].wrapping_mul(5), 7).wrapping_mul(9);
42    let t = s[1] << 17;
43    s[2] ^= s[0];
44    s[3] ^= s[1];
45    s[1] ^= s[2];
46    s[0] ^= s[3];
47    s[2] ^= t;
48    s[3] = rol64(s[3], 45);
49    result
50}
51
52/// Create PRNG from seed - matches Go CreatePRNG
53pub fn create_prng(seed: &[u8]) -> Rand {
54    let mut mac = <HmacSha256 as Mac>::new_from_slice(seed).expect("HMAC key");
55    Mac::update(&mut mac, PM_SELECTOR_IDENTIFIER);
56    let sum = mac.finalize().into_bytes();
57
58    let mut xoshiro = [0u8; 32];
59    pbkdf2_hmac::<Sha1>(&sum, PRNG_SALT, PBKDF2_LOOPS, &mut xoshiro);
60
61    let mut state = [
62        u64::from_le_bytes(xoshiro[0..8].try_into().unwrap()),
63        u64::from_le_bytes(xoshiro[8..16].try_into().unwrap()),
64        u64::from_le_bytes(xoshiro[16..24].try_into().unwrap()),
65        u64::from_le_bytes(xoshiro[24..32].try_into().unwrap()),
66    ];
67    let seed64 = xoshiro256ss(&mut state);
68
69    Rand {
70        xoshiro: state,
71        seed64,
72        count: 0,
73    }
74}
75
76/// Quantum Permutation Pad - encryption/decryption pads
77pub struct QuantumPermutationPad {
78    pads: Vec<u8>,
79    rpads: Vec<u8>,
80    num_pads: u16,
81}
82
83fn qpp_minimum_seed_length(qubits: u8) -> usize {
84    // 2^qubits factorial in bits
85    let n: usize = 1 << qubits;
86    let mut log2_fact = 0f64;
87    for i in 2..=n {
88        log2_fact += (i as f64).log2();
89    }
90    let bit_len = log2_fact.ceil() as usize;
91    let byte_len = (bit_len + 7) / 8;
92    byte_len.max(1)
93}
94
95fn fill(pad: &mut [u8]) {
96    for (i, b) in pad.iter_mut().enumerate() {
97        *b = i as u8;
98    }
99}
100
101fn reverse(pad: &[u8], rpad: &mut [u8]) {
102    for (i, &p) in pad.iter().enumerate() {
103        rpad[p as usize] = i as u8;
104    }
105}
106
107fn seed_to_chunks(seed: &[u8], qubits: u8) -> Vec<Vec<u8>> {
108    let min_len = qpp_minimum_seed_length(qubits);
109    let seed = if seed.len() < 32 {
110        let mut derived = vec![0u8; 32];
111        pbkdf2_hmac::<Sha1>(seed, CHUNK_DERIVE_SALT, PBKDF2_LOOPS, &mut derived);
112        derived
113    } else {
114        seed.to_vec()
115    };
116
117    let chunk_count = ((min_len + 31) / 32).max(1);
118    let mut chunks = Vec::with_capacity(chunk_count);
119    let mut seed_idx = 0;
120    for _ in 0..chunk_count {
121        let mut chunk = vec![0u8; 32];
122        for j in 0..32 {
123            chunk[j] = seed[seed_idx % seed.len()];
124            seed_idx += 1;
125        }
126        let mut derived = vec![0u8; chunk.len()];
127        pbkdf2_hmac::<Sha1>(&chunk, CHUNK_DERIVE_SALT, CHUNK_DERIVE_LOOPS, &mut derived);
128        chunks.push(derived);
129    }
130    chunks
131}
132
133fn shuffle(chunk: &[u8], pad: &mut [u8], pad_id: u16, blocks: &mut [Aes256]) {
134    let mut mac = <HmacSha256 as Mac>::new_from_slice(chunk).expect("HMAC key");
135    let msg = format!("{}{:b}", PAD_IDENTIFIER, pad_id);
136    Mac::update(&mut mac, msg.as_bytes());
137    let sum = mac.finalize().into_bytes();
138    let mut sum = sum.to_vec();
139
140    for i in (1..pad.len()).rev() {
141        for block in blocks.iter_mut() {
142            for off in (0..sum.len()).step_by(16) {
143                if off + 16 <= sum.len() {
144                    let mut block_data: GenericArray<u8, _> =
145                        GenericArray::clone_from_slice(&sum[off..off + 16]);
146                    block.encrypt_block_mut(&mut block_data);
147                    sum[off..off + 16].copy_from_slice(block_data.as_slice());
148                }
149            }
150        }
151        let bigrand = BigUint::from_bytes_be(&sum);
152        let modulus = BigUint::from(i + 1);
153        let rem = &bigrand % &modulus;
154        let digits = rem.to_u64_digits();
155        let j = digits.first().copied().unwrap_or(0) as usize;
156        pad.swap(i, j);
157    }
158}
159
160impl QuantumPermutationPad {
161    /// Create new QPP with seed and number of pads (must match Go peer)
162    pub fn new(seed: &[u8], num_pads: u16) -> Self {
163        let chunks = seed_to_chunks(seed, QUBITS);
164        let mut blocks: Vec<Aes256> = Vec::with_capacity(chunks.len());
165        for chunk in &chunks {
166            let mut aeskey = [0u8; 32];
167            pbkdf2_hmac::<Sha1>(chunk, SHUFFLE_SALT, PBKDF2_LOOPS, &mut aeskey);
168            blocks.push(Aes256::new_from_slice(&aeskey).expect("AES256"));
169        }
170
171        let mut pads = vec![0u8; num_pads as usize * MATRIX_BYTES];
172        let mut rpads = vec![0u8; num_pads as usize * MATRIX_BYTES];
173
174        for i in 0..num_pads {
175            let pad_start = (i as usize) * MATRIX_BYTES;
176            let pad_end = pad_start + MATRIX_BYTES;
177            let (pad, rpad) = (
178                &mut pads[pad_start..pad_end],
179                &mut rpads[pad_start..pad_end],
180            );
181            fill(pad);
182            shuffle(&chunks[i as usize % chunks.len()], pad, i, &mut blocks);
183            reverse(pad, rpad);
184        }
185
186        QuantumPermutationPad {
187            pads,
188            rpads,
189            num_pads,
190        }
191    }
192
193    /// Encrypt data in place using the given PRNG (stream-compatible)
194    pub fn encrypt_with_prng(&self, data: &mut [u8], rand: &mut Rand) {
195        if data.is_empty() {
196            return;
197        }
198        let size = data.len();
199        let num_pads = self.num_pads;
200        let mut r = rand.seed64;
201        let mut count = rand.count;
202        let [mut s0, mut s1, mut s2, mut s3] = rand.xoshiro;
203
204        let mut offset = 0;
205
206        // handle unaligned bytes (count != 0)
207        if count != 0 {
208            while offset < data.len() {
209                let rr = (r >> (count << 3)) as u8;
210                let base = (r as u16 % num_pads) as usize * MATRIX_BYTES;
211                data[offset] = self.pads[base + (data[offset] ^ rr) as usize];
212                count += 1;
213                if count == PAD_SWITCH {
214                    r = rol64(s1.wrapping_mul(5), 7).wrapping_mul(9);
215                    let t = s1 << 17;
216                    s2 ^= s0;
217                    s3 ^= s1;
218                    s1 ^= s2;
219                    s0 ^= s3;
220                    s2 ^= t;
221                    s3 = rol64(s3, 45);
222                    count = 0;
223                    offset += 1;
224                    break;
225                }
226                offset += 1;
227            }
228        }
229
230        // 8-byte aligned blocks, 16 bytes at a time
231        let mut idx = offset;
232        let repeat = (data.len() - idx) >> 4;
233        for _ in 0..repeat {
234            {
235                let d = &mut data[idx..idx + 16];
236                let base = (r as u16 % num_pads) as usize * MATRIX_BYTES;
237                d[0] = self.pads[base + (d[0] ^ r as u8) as usize];
238                d[1] = self.pads[base + (d[1] ^ (r >> 8) as u8) as usize];
239                d[2] = self.pads[base + (d[2] ^ (r >> 16) as u8) as usize];
240                d[3] = self.pads[base + (d[3] ^ (r >> 24) as u8) as usize];
241                d[4] = self.pads[base + (d[4] ^ (r >> 32) as u8) as usize];
242                d[5] = self.pads[base + (d[5] ^ (r >> 40) as u8) as usize];
243                d[6] = self.pads[base + (d[6] ^ (r >> 48) as u8) as usize];
244                d[7] = self.pads[base + (d[7] ^ (r >> 56) as u8) as usize];
245            }
246            r = rol64(s1.wrapping_mul(5), 7).wrapping_mul(9);
247            let t = s1 << 17;
248            s2 ^= s0;
249            s3 ^= s1;
250            s1 ^= s2;
251            s0 ^= s3;
252            s2 ^= t;
253            s3 = rol64(s3, 45);
254            let base = (r as u16 % num_pads) as usize * MATRIX_BYTES;
255            {
256                let d = &mut data[idx + 8..idx + 16];
257                d[0] = self.pads[base + (d[0] ^ r as u8) as usize];
258                d[1] = self.pads[base + (d[1] ^ (r >> 8) as u8) as usize];
259                d[2] = self.pads[base + (d[2] ^ (r >> 16) as u8) as usize];
260                d[3] = self.pads[base + (d[3] ^ (r >> 24) as u8) as usize];
261                d[4] = self.pads[base + (d[4] ^ (r >> 32) as u8) as usize];
262                d[5] = self.pads[base + (d[5] ^ (r >> 40) as u8) as usize];
263                d[6] = self.pads[base + (d[6] ^ (r >> 48) as u8) as usize];
264                d[7] = self.pads[base + (d[7] ^ (r >> 56) as u8) as usize];
265            }
266            r = rol64(s1.wrapping_mul(5), 7).wrapping_mul(9);
267            let t = s1 << 17;
268            s2 ^= s0;
269            s3 ^= s1;
270            s1 ^= s2;
271            s0 ^= s3;
272            s2 ^= t;
273            s3 = rol64(s3, 45);
274            idx += 16;
275        }
276
277        // remaining 8-byte block
278        if idx + 8 <= data.len() {
279            let d = &mut data[idx..idx + 8];
280            let base = (r as u16 % num_pads) as usize * MATRIX_BYTES;
281            d[0] = self.pads[base + (d[0] ^ r as u8) as usize];
282            d[1] = self.pads[base + (d[1] ^ (r >> 8) as u8) as usize];
283            d[2] = self.pads[base + (d[2] ^ (r >> 16) as u8) as usize];
284            d[3] = self.pads[base + (d[3] ^ (r >> 24) as u8) as usize];
285            d[4] = self.pads[base + (d[4] ^ (r >> 32) as u8) as usize];
286            d[5] = self.pads[base + (d[5] ^ (r >> 40) as u8) as usize];
287            d[6] = self.pads[base + (d[6] ^ (r >> 48) as u8) as usize];
288            d[7] = self.pads[base + (d[7] ^ (r >> 56) as u8) as usize];
289            r = rol64(s1.wrapping_mul(5), 7).wrapping_mul(9);
290            let t = s1 << 17;
291            s2 ^= s0;
292            s3 ^= s1;
293            s1 ^= s2;
294            s0 ^= s3;
295            s2 ^= t;
296            s3 = rol64(s3, 45);
297            idx += 8;
298        }
299
300        // tail bytes
301        for i in idx..data.len() {
302            let rr = (r >> (count << 3)) as u8;
303            let base = (r as u16 % num_pads) as usize * MATRIX_BYTES;
304            data[i] = self.pads[base + (data[i] ^ rr) as usize];
305            count += 1;
306        }
307
308        rand.xoshiro = [s0, s1, s2, s3];
309        rand.seed64 = r;
310        rand.count = ((rand.count as usize + size) & (PAD_SWITCH as usize - 1)) as u8;
311    }
312
313    /// Decrypt data in place using the given PRNG
314    pub fn decrypt_with_prng(&self, data: &mut [u8], rand: &mut Rand) {
315        if data.is_empty() {
316            return;
317        }
318        let size = data.len();
319        let num_pads = self.num_pads;
320        let mut r = rand.seed64;
321        let mut count = rand.count;
322        let [mut s0, mut s1, mut s2, mut s3] = rand.xoshiro;
323
324        let mut offset = 0;
325
326        if count != 0 {
327            while offset < data.len() {
328                let rr = (r >> (count << 3)) as u8;
329                let base = (r as u16 % num_pads) as usize * MATRIX_BYTES;
330                data[offset] = self.rpads[base + data[offset] as usize] ^ rr;
331                count += 1;
332                if count == PAD_SWITCH {
333                    r = rol64(s1.wrapping_mul(5), 7).wrapping_mul(9);
334                    let t = s1 << 17;
335                    s2 ^= s0;
336                    s3 ^= s1;
337                    s1 ^= s2;
338                    s0 ^= s3;
339                    s2 ^= t;
340                    s3 = rol64(s3, 45);
341                    count = 0;
342                    offset += 1;
343                    break;
344                }
345                offset += 1;
346            }
347        }
348
349        let mut idx = offset;
350        let repeat = (data.len() - idx) >> 4;
351        for _ in 0..repeat {
352            {
353                let d = &mut data[idx..idx + 8];
354                let base = (r as u16 % num_pads) as usize * MATRIX_BYTES;
355                let (rr0, rr1, rr2, rr3) = (r as u8, (r >> 8) as u8, (r >> 16) as u8, (r >> 24) as u8);
356                let (rr4, rr5, rr6, rr7) =
357                    ((r >> 32) as u8, (r >> 40) as u8, (r >> 48) as u8, (r >> 56) as u8);
358                d[0] = self.rpads[base + d[0] as usize] ^ rr0;
359                d[1] = self.rpads[base + d[1] as usize] ^ rr1;
360                d[2] = self.rpads[base + d[2] as usize] ^ rr2;
361                d[3] = self.rpads[base + d[3] as usize] ^ rr3;
362                d[4] = self.rpads[base + d[4] as usize] ^ rr4;
363                d[5] = self.rpads[base + d[5] as usize] ^ rr5;
364                d[6] = self.rpads[base + d[6] as usize] ^ rr6;
365                d[7] = self.rpads[base + d[7] as usize] ^ rr7;
366            }
367            r = rol64(s1.wrapping_mul(5), 7).wrapping_mul(9);
368            let t = s1 << 17;
369            s2 ^= s0;
370            s3 ^= s1;
371            s1 ^= s2;
372            s0 ^= s3;
373            s2 ^= t;
374            s3 = rol64(s3, 45);
375            let base = (r as u16 % num_pads) as usize * MATRIX_BYTES;
376            {
377                let d = &mut data[idx + 8..idx + 16];
378                let (rr0, rr1, rr2, rr3) = (r as u8, (r >> 8) as u8, (r >> 16) as u8, (r >> 24) as u8);
379                let (rr4, rr5, rr6, rr7) =
380                    ((r >> 32) as u8, (r >> 40) as u8, (r >> 48) as u8, (r >> 56) as u8);
381                d[0] = self.rpads[base + d[0] as usize] ^ rr0;
382                d[1] = self.rpads[base + d[1] as usize] ^ rr1;
383                d[2] = self.rpads[base + d[2] as usize] ^ rr2;
384                d[3] = self.rpads[base + d[3] as usize] ^ rr3;
385                d[4] = self.rpads[base + d[4] as usize] ^ rr4;
386                d[5] = self.rpads[base + d[5] as usize] ^ rr5;
387                d[6] = self.rpads[base + d[6] as usize] ^ rr6;
388                d[7] = self.rpads[base + d[7] as usize] ^ rr7;
389            }
390            r = rol64(s1.wrapping_mul(5), 7).wrapping_mul(9);
391            let t = s1 << 17;
392            s2 ^= s0;
393            s3 ^= s1;
394            s1 ^= s2;
395            s0 ^= s3;
396            s2 ^= t;
397            s3 = rol64(s3, 45);
398            idx += 16;
399        }
400
401        if idx + 8 <= data.len() {
402            let d = &mut data[idx..idx + 8];
403            let base = (r as u16 % num_pads) as usize * MATRIX_BYTES;
404            let (rr0, rr1, rr2, rr3) = (r as u8, (r >> 8) as u8, (r >> 16) as u8, (r >> 24) as u8);
405            let (rr4, rr5, rr6, rr7) =
406                ((r >> 32) as u8, (r >> 40) as u8, (r >> 48) as u8, (r >> 56) as u8);
407            d[0] = self.rpads[base + d[0] as usize] ^ rr0;
408            d[1] = self.rpads[base + d[1] as usize] ^ rr1;
409            d[2] = self.rpads[base + d[2] as usize] ^ rr2;
410            d[3] = self.rpads[base + d[3] as usize] ^ rr3;
411            d[4] = self.rpads[base + d[4] as usize] ^ rr4;
412            d[5] = self.rpads[base + d[5] as usize] ^ rr5;
413            d[6] = self.rpads[base + d[6] as usize] ^ rr6;
414            d[7] = self.rpads[base + d[7] as usize] ^ rr7;
415            r = rol64(s1.wrapping_mul(5), 7).wrapping_mul(9);
416            let t = s1 << 17;
417            s2 ^= s0;
418            s3 ^= s1;
419            s1 ^= s2;
420            s0 ^= s3;
421            s2 ^= t;
422            s3 = rol64(s3, 45);
423            idx += 8;
424        }
425
426        for i in idx..data.len() {
427            let rr = (r >> (count << 3)) as u8;
428            let base = (r as u16 % num_pads) as usize * MATRIX_BYTES;
429            data[i] = self.rpads[base + data[i] as usize] ^ rr;
430            count += 1;
431        }
432
433        rand.xoshiro = [s0, s1, s2, s3];
434        rand.seed64 = r;
435        rand.count = ((rand.count as usize + size) & (PAD_SWITCH as usize - 1)) as u8;
436    }
437}