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