ipcrypt_rs/
pfx.rs

1use aes::cipher::{BlockEncrypt, KeyInit};
2use aes::Aes128;
3use aes::Block;
4use std::net::IpAddr;
5
6use crate::common::{bytes_to_ip, ip_to_bytes};
7
8/// A structure representing the IPCrypt context for prefix-preserving mode.
9pub struct IpcryptPfx {
10    cipher1: Aes128,
11    cipher2: Aes128,
12}
13
14impl IpcryptPfx {
15    /// The number of bytes required for the encryption key.
16    pub const KEY_BYTES: usize = 32;
17
18    /// Generates a new random key for encryption.
19    pub fn generate_key() -> [u8; Self::KEY_BYTES] {
20        rand::random()
21    }
22
23    /// Creates a new IpcryptPfx instance with the given key.
24    ///
25    /// # Arguments
26    ///
27    /// * `key` - A 32-byte array containing the encryption key.
28    ///
29    /// # Panics
30    ///
31    /// Panics if the two halves of the key are identical, as this would
32    /// compromise the security of the encryption.
33    pub fn new(key: [u8; Self::KEY_BYTES]) -> Self {
34        // Split the key into two 16-byte halves
35        let (k1, k2) = key.split_at(16);
36
37        // Ensure the two halves are different
38        assert_ne!(k1, k2, "The two halves of the key must be different");
39
40        let cipher1 = Aes128::new_from_slice(k1).expect("key length is guaranteed to be correct");
41        let cipher2 = Aes128::new_from_slice(k2).expect("key length is guaranteed to be correct");
42
43        Self { cipher1, cipher2 }
44    }
45
46    /// Creates a new IpcryptPfx instance with a random key.
47    pub fn new_random() -> Self {
48        loop {
49            let key = Self::generate_key();
50            let (k1, k2) = key.split_at(16);
51            if k1 != k2 {
52                return Self::new(key);
53            }
54        }
55    }
56
57    /// Encrypts an IP address using prefix-preserving encryption.
58    ///
59    /// # Arguments
60    ///
61    /// * `ip` - The IP address to encrypt
62    ///
63    /// # Returns
64    ///
65    /// The encrypted IP address
66    pub fn encrypt_ipaddr(&self, ip: IpAddr) -> IpAddr {
67        let bytes = ip_to_bytes(ip);
68        let encrypted = self.encrypt_bytes(&bytes, ip);
69        bytes_to_ip(encrypted)
70    }
71
72    /// Decrypts an IP address using prefix-preserving encryption.
73    ///
74    /// # Arguments
75    ///
76    /// * `encrypted` - The encrypted IP address
77    ///
78    /// # Returns
79    ///
80    /// The decrypted IP address
81    pub fn decrypt_ipaddr(&self, encrypted: IpAddr) -> IpAddr {
82        let encrypted_bytes = ip_to_bytes(encrypted);
83        let decrypted = self.decrypt_bytes(&encrypted_bytes, encrypted);
84        bytes_to_ip(decrypted)
85    }
86
87    /// Internal method to encrypt bytes
88    fn encrypt_bytes(&self, bytes: &[u8; 16], ip: IpAddr) -> [u8; 16] {
89        let mut encrypted = [0u8; 16];
90
91        // Determine starting point
92        let prefix_start = if ip.is_ipv4() { 96 } else { 0 };
93
94        // If IPv4, copy the IPv4-mapped prefix
95        if ip.is_ipv4() {
96            encrypted[..12].copy_from_slice(&bytes[..12]);
97        }
98
99        // Initialize padded_prefix for the starting prefix length
100        let mut padded_prefix = if prefix_start == 0 {
101            Self::pad_prefix_0()
102        } else {
103            Self::pad_prefix_96()
104        };
105
106        // Process each bit position
107        for prefix_len_bits in prefix_start..128 {
108            // Compute pseudorandom function with dual AES encryption
109            let mut block1 = Block::from(padded_prefix);
110            let mut block2 = Block::from(padded_prefix);
111
112            self.cipher1.encrypt_block(&mut block1);
113            self.cipher2.encrypt_block(&mut block2);
114
115            // XOR the two encryptions
116            let e1: [u8; 16] = block1.into();
117            let e2: [u8; 16] = block2.into();
118            let mut e = [0u8; 16];
119            for i in 0..16 {
120                e[i] = e1[i] ^ e2[i];
121            }
122
123            // Extract the least significant bit
124            let cipher_bit = e[15] & 1;
125
126            // Get the current bit position
127            let bit_pos = 127 - prefix_len_bits;
128            let original_bit = Self::get_bit(bytes, bit_pos);
129
130            // Set the bit in the encrypted result
131            Self::set_bit(&mut encrypted, bit_pos, cipher_bit ^ original_bit);
132
133            // Prepare padded_prefix for next iteration
134            padded_prefix = Self::shift_left_one_bit(&padded_prefix);
135            Self::set_bit(&mut padded_prefix, 0, original_bit);
136        }
137
138        encrypted
139    }
140
141    /// Internal method to decrypt bytes
142    fn decrypt_bytes(&self, encrypted_bytes: &[u8; 16], encrypted_ip: IpAddr) -> [u8; 16] {
143        let mut decrypted = [0u8; 16];
144
145        // For decryption, determine if this was originally IPv4
146        let prefix_start = if encrypted_ip.is_ipv4() { 96 } else { 0 };
147
148        // If this was originally IPv4, set up the IPv4-mapped IPv6 prefix
149        if prefix_start == 96 {
150            decrypted[10..12].copy_from_slice(&[0xFF; 2]);
151        }
152
153        // Initialize padded_prefix for the starting prefix length
154        let mut padded_prefix = if prefix_start == 0 {
155            Self::pad_prefix_0()
156        } else {
157            Self::pad_prefix_96()
158        };
159
160        // Process each bit position
161        for prefix_len_bits in prefix_start..128 {
162            // Compute pseudorandom function with dual AES encryption
163            let mut block1 = Block::from(padded_prefix);
164            let mut block2 = Block::from(padded_prefix);
165
166            self.cipher1.encrypt_block(&mut block1);
167            self.cipher2.encrypt_block(&mut block2);
168
169            // XOR the two encryptions
170            let e1: [u8; 16] = block1.into();
171            let e2: [u8; 16] = block2.into();
172            let mut e = [0u8; 16];
173            for i in 0..16 {
174                e[i] = e1[i] ^ e2[i];
175            }
176
177            // Extract the least significant bit
178            let cipher_bit = e[15] & 1;
179
180            // Get the current bit position
181            let bit_pos = 127 - prefix_len_bits;
182            let encrypted_bit = Self::get_bit(encrypted_bytes, bit_pos);
183            let original_bit = cipher_bit ^ encrypted_bit;
184
185            // Set the bit in the decrypted result
186            Self::set_bit(&mut decrypted, bit_pos, original_bit);
187
188            // Prepare padded_prefix for next iteration
189            padded_prefix = Self::shift_left_one_bit(&padded_prefix);
190            Self::set_bit(&mut padded_prefix, 0, original_bit);
191        }
192
193        decrypted
194    }
195
196    /// Extract bit at position from 16-byte array.
197    /// position: 0 = LSB of byte 15, 127 = MSB of byte 0
198    fn get_bit(data: &[u8; 16], position: usize) -> u8 {
199        let byte_index = 15 - (position / 8);
200        let bit_index = position % 8;
201        (data[byte_index] >> bit_index) & 1
202    }
203
204    /// Set bit at position in 16-byte array.
205    /// position: 0 = LSB of byte 15, 127 = MSB of byte 0
206    fn set_bit(data: &mut [u8; 16], position: usize, value: u8) {
207        let byte_index = 15 - (position / 8);
208        let bit_index = position % 8;
209        if value != 0 {
210            data[byte_index] |= 1 << bit_index;
211        } else {
212            data[byte_index] &= !(1 << bit_index);
213        }
214    }
215
216    /// Shift a 16-byte array one bit to the left.
217    /// The most significant bit is lost, and a zero bit is shifted in from the right.
218    fn shift_left_one_bit(data: &[u8; 16]) -> [u8; 16] {
219        let mut result = [0u8; 16];
220        let mut carry = 0;
221
222        // Process from least significant byte (byte 15) to most significant (byte 0)
223        for i in (0..16).rev() {
224            // Current byte shifted left by 1, with carry from previous byte
225            result[i] = (data[i] << 1) | carry;
226            // Extract the bit that will be carried to the next byte
227            carry = (data[i] >> 7) & 1;
228        }
229
230        result
231    }
232
233    /// Pad prefix for prefix_len_bits=0 (IPv6).
234    /// Sets separator bit at position 0 (LSB of byte 15).
235    fn pad_prefix_0() -> [u8; 16] {
236        let mut padded = [0u8; 16];
237        padded[15] = 0x01; // Set bit at position 0 (LSB of byte 15)
238        padded
239    }
240
241    /// Pad prefix for prefix_len_bits=96 (IPv4).
242    /// For IPv4, the data always has format: 00...00 ffff xxxx (IPv4-mapped)
243    /// Result: 00000001 00...00 0000ffff (separator at pos 96, then 96 bits)
244    fn pad_prefix_96() -> [u8; 16] {
245        let mut padded = [0u8; 16];
246        padded[3] = 0x01; // Set bit at position 96 (bit 0 of byte 3)
247        padded[14] = 0xFF;
248        padded[15] = 0xFF;
249        padded
250    }
251}
252
253#[cfg(test)]
254mod tests {
255    use super::*;
256    use ct_codecs::{Decoder as _, Hex};
257    use std::str::FromStr;
258
259    #[test]
260    fn test_pfx_basic_vectors() {
261        let test_vectors = vec![
262            // Test vector 1 (IPv4)
263            (
264                "0123456789abcdeffedcba98765432101032547698badcfeefcdab8967452301",
265                "0.0.0.0",
266                "151.82.155.134",
267            ),
268            // Test vector 2 (IPv4)
269            (
270                "0123456789abcdeffedcba98765432101032547698badcfeefcdab8967452301",
271                "255.255.255.255",
272                "94.185.169.89",
273            ),
274            // Test vector 3 (IPv4)
275            (
276                "0123456789abcdeffedcba98765432101032547698badcfeefcdab8967452301",
277                "192.0.2.1",
278                "100.115.72.131",
279            ),
280            // Test vector 4 (IPv6)
281            (
282                "0123456789abcdeffedcba98765432101032547698badcfeefcdab8967452301",
283                "2001:db8::1",
284                "c180:5dd4:2587:3524:30ab:fa65:6ab6:f88",
285            ),
286        ];
287
288        for (key_hex, input_ip, expected_output) in test_vectors {
289            // Parse key using constant-time hex decoder
290            let key_vec = Hex::decode_to_vec(key_hex.as_bytes(), None).unwrap();
291            let mut key = [0u8; IpcryptPfx::KEY_BYTES];
292            key.copy_from_slice(&key_vec);
293
294            // Create IpcryptPfx instance
295            let ipcrypt = IpcryptPfx::new(key);
296
297            // Parse input IP
298            let ip = IpAddr::from_str(input_ip).unwrap();
299
300            // Encrypt
301            let encrypted = ipcrypt.encrypt_ipaddr(ip);
302            assert_eq!(encrypted.to_string(), expected_output);
303
304            // Decrypt
305            let decrypted = ipcrypt.decrypt_ipaddr(encrypted);
306            assert_eq!(decrypted, ip);
307        }
308    }
309
310    #[test]
311    fn test_pfx_prefix_preserving_ipv4_24() {
312        let key_hex = "2b7e151628aed2a6abf7158809cf4f3ca9f5ba40db214c3798f2e1c23456789a";
313        let key_vec = Hex::decode_to_vec(key_hex.as_bytes(), None).unwrap();
314        let mut key = [0u8; IpcryptPfx::KEY_BYTES];
315        key.copy_from_slice(&key_vec);
316        let ipcrypt = IpcryptPfx::new(key);
317
318        // Test IPv4 addresses from same /24 network
319        let test_cases = vec![
320            ("10.0.0.47", "19.214.210.244"),
321            ("10.0.0.129", "19.214.210.80"),
322            ("10.0.0.234", "19.214.210.30"),
323        ];
324
325        for (input_ip, expected_output) in test_cases {
326            let ip = IpAddr::from_str(input_ip).unwrap();
327            let encrypted = ipcrypt.encrypt_ipaddr(ip);
328            assert_eq!(encrypted.to_string(), expected_output);
329
330            let decrypted = ipcrypt.decrypt_ipaddr(encrypted);
331            assert_eq!(decrypted, ip);
332        }
333
334        // Verify prefix preservation: first 24 bits should be the same
335        let ip1 = IpAddr::from_str("10.0.0.47").unwrap();
336        let ip2 = IpAddr::from_str("10.0.0.129").unwrap();
337        let ip3 = IpAddr::from_str("10.0.0.234").unwrap();
338
339        let enc1 = ipcrypt.encrypt_ipaddr(ip1);
340        let enc2 = ipcrypt.encrypt_ipaddr(ip2);
341        let enc3 = ipcrypt.encrypt_ipaddr(ip3);
342
343        // All encrypted addresses should be IPv4
344        assert!(enc1.is_ipv4());
345        assert!(enc2.is_ipv4());
346        assert!(enc3.is_ipv4());
347
348        // Extract first 24 bits of each encrypted address
349        let enc1_bytes = match enc1 {
350            IpAddr::V4(ip) => ip.octets(),
351            _ => panic!("Expected IPv4"),
352        };
353        let enc2_bytes = match enc2 {
354            IpAddr::V4(ip) => ip.octets(),
355            _ => panic!("Expected IPv4"),
356        };
357        let enc3_bytes = match enc3 {
358            IpAddr::V4(ip) => ip.octets(),
359            _ => panic!("Expected IPv4"),
360        };
361
362        // First 3 bytes should be identical
363        assert_eq!(enc1_bytes[0], enc2_bytes[0]);
364        assert_eq!(enc1_bytes[0], enc3_bytes[0]);
365        assert_eq!(enc1_bytes[1], enc2_bytes[1]);
366        assert_eq!(enc1_bytes[1], enc3_bytes[1]);
367        assert_eq!(enc1_bytes[2], enc2_bytes[2]);
368        assert_eq!(enc1_bytes[2], enc3_bytes[2]);
369    }
370
371    #[test]
372    fn test_pfx_prefix_preserving_ipv4_16() {
373        let key_hex = "2b7e151628aed2a6abf7158809cf4f3ca9f5ba40db214c3798f2e1c23456789a";
374        let key_vec = Hex::decode_to_vec(key_hex.as_bytes(), None).unwrap();
375        let mut key = [0u8; IpcryptPfx::KEY_BYTES];
376        key.copy_from_slice(&key_vec);
377        let ipcrypt = IpcryptPfx::new(key);
378
379        // Test IPv4 addresses from same /16 but different /24 networks
380        let test_cases = vec![
381            ("172.16.5.193", "210.78.229.136"),
382            ("172.16.97.42", "210.78.179.241"),
383            ("172.16.248.177", "210.78.121.215"),
384        ];
385
386        for (input_ip, expected_output) in test_cases {
387            let ip = IpAddr::from_str(input_ip).unwrap();
388            let encrypted = ipcrypt.encrypt_ipaddr(ip);
389            assert_eq!(encrypted.to_string(), expected_output);
390
391            let decrypted = ipcrypt.decrypt_ipaddr(encrypted);
392            assert_eq!(decrypted, ip);
393        }
394    }
395
396    #[test]
397    fn test_pfx_prefix_preserving_ipv6_64() {
398        let key_hex = "2b7e151628aed2a6abf7158809cf4f3ca9f5ba40db214c3798f2e1c23456789a";
399        let key_vec = Hex::decode_to_vec(key_hex.as_bytes(), None).unwrap();
400        let mut key = [0u8; IpcryptPfx::KEY_BYTES];
401        key.copy_from_slice(&key_vec);
402        let ipcrypt = IpcryptPfx::new(key);
403
404        // Test IPv6 addresses from same /64 network
405        let test_cases = vec![
406            (
407                "2001:db8::a5c9:4e2f:bb91:5a7d",
408                "7cec:702c:1243:f70:1956:125:b9bd:1aba",
409            ),
410            (
411                "2001:db8::7234:d8f1:3c6e:9a52",
412                "7cec:702c:1243:f70:a3ef:c8e:95c1:cd0d",
413            ),
414            (
415                "2001:db8::f1e0:937b:26d4:8c1a",
416                "7cec:702c:1243:f70:443c:c8e:6a62:b64d",
417            ),
418        ];
419
420        for (input_ip, expected_output) in test_cases {
421            let ip = IpAddr::from_str(input_ip).unwrap();
422            let encrypted = ipcrypt.encrypt_ipaddr(ip);
423            assert_eq!(encrypted.to_string(), expected_output);
424
425            let decrypted = ipcrypt.decrypt_ipaddr(encrypted);
426            assert_eq!(decrypted, ip);
427        }
428    }
429
430    #[test]
431    fn test_pfx_prefix_preserving_ipv6_32() {
432        let key_hex = "2b7e151628aed2a6abf7158809cf4f3ca9f5ba40db214c3798f2e1c23456789a";
433        let key_vec = Hex::decode_to_vec(key_hex.as_bytes(), None).unwrap();
434        let mut key = [0u8; IpcryptPfx::KEY_BYTES];
435        key.copy_from_slice(&key_vec);
436        let ipcrypt = IpcryptPfx::new(key);
437
438        // Test IPv6 addresses from same /32 but different /48 networks
439        let test_cases = vec![
440            (
441                "2001:db8:3a5c::e7d1:4b9f:2c8a:f673",
442                "7cec:702c:3503:bef:e616:96bd:be33:a9b9",
443            ),
444            (
445                "2001:db8:9f27::b4e2:7a3d:5f91:c8e6",
446                "7cec:702c:a504:b74e:194a:3d90:b047:2d1a",
447            ),
448            (
449                "2001:db8:d8b4::193c:a5e7:8b2f:46d1",
450                "7cec:702c:f840:aa67:1b8:e84f:ac9d:77fb",
451            ),
452        ];
453
454        for (input_ip, expected_output) in test_cases {
455            let ip = IpAddr::from_str(input_ip).unwrap();
456            let encrypted = ipcrypt.encrypt_ipaddr(ip);
457            assert_eq!(encrypted.to_string(), expected_output);
458
459            let decrypted = ipcrypt.decrypt_ipaddr(encrypted);
460            assert_eq!(decrypted, ip);
461        }
462    }
463
464    #[test]
465    fn test_random_key() {
466        let ipcrypt = IpcryptPfx::new_random();
467        let ip = IpAddr::from_str("192.0.2.1").unwrap();
468        let encrypted = ipcrypt.encrypt_ipaddr(ip);
469        let decrypted = ipcrypt.decrypt_ipaddr(encrypted);
470        assert_eq!(ip, decrypted);
471    }
472
473    #[test]
474    #[should_panic(expected = "The two halves of the key must be different")]
475    fn test_identical_key_halves() {
476        let mut key = [0u8; 32];
477        // Make both halves identical
478        key[0..16].copy_from_slice(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
479        key[16..32].copy_from_slice(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
480        IpcryptPfx::new(key);
481    }
482
483    #[test]
484    fn test_bit_operations() {
485        let mut data = [0u8; 16];
486
487        // Test setting and getting bits
488        for pos in 0..128 {
489            IpcryptPfx::set_bit(&mut data, pos, 1);
490            assert_eq!(IpcryptPfx::get_bit(&data, pos), 1);
491            IpcryptPfx::set_bit(&mut data, pos, 0);
492            assert_eq!(IpcryptPfx::get_bit(&data, pos), 0);
493        }
494    }
495
496    #[test]
497    fn test_shift_left() {
498        let data = [
499            0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
500            0x00, 0x01,
501        ];
502        let result = IpcryptPfx::shift_left_one_bit(&data);
503
504        // After shift left, the MSB should be lost and a 0 shifted in at LSB
505        assert_eq!(result[0], 0x00); // MSB was 1, now lost
506        assert_eq!(result[15], 0x02); // LSB was 1, shifted left
507    }
508
509    #[test]
510    fn test_pad_prefix() {
511        let padded0 = IpcryptPfx::pad_prefix_0();
512        assert_eq!(padded0[15], 0x01);
513        assert_eq!(padded0[0..15], [0u8; 15]);
514
515        let padded96 = IpcryptPfx::pad_prefix_96();
516        assert_eq!(padded96[3], 0x01);
517        assert_eq!(padded96[14], 0xFF);
518        assert_eq!(padded96[15], 0xFF);
519        assert_eq!(padded96[0..3], [0u8; 3]);
520        assert_eq!(padded96[4..14], [0u8; 10]);
521    }
522}