ipcrypt_rs/
ndx.rs

1use aes::cipher::{BlockDecrypt, BlockEncrypt, KeyInit};
2use aes::{Aes128, Block};
3use std::net::IpAddr;
4
5use crate::common::{bytes_to_ip, ip_to_bytes};
6
7/// A structure representing the IPCrypt context for non-deterministic XTS mode encryption.
8///
9/// This struct provides methods for encrypting and decrypting IP addresses using AES-XTS mode
10/// with a 16-byte tweak. The key is 32 bytes (two AES-128 keys).
11pub struct IpcryptNdx {
12    cipher1: Aes128, // For data encryption
13    cipher2: Aes128, // For tweak encryption
14}
15
16impl IpcryptNdx {
17    /// The number of bytes required for the encryption key (two AES-128 keys).
18    pub const KEY_BYTES: usize = 32;
19    /// The number of bytes required for the tweak.
20    pub const TWEAK_BYTES: usize = 16;
21    /// The number of bytes of the encrypted IP address.
22    pub const NDIP_BYTES: usize = 32;
23
24    /// Generates a new random key for encryption.
25    #[cfg(feature = "random")]
26    pub fn generate_key() -> [u8; Self::KEY_BYTES] {
27        rand::random()
28    }
29
30    /// Creates a new IpcryptNdx instance with the given key.
31    ///
32    /// # Arguments
33    ///
34    /// * `key` - A 32-byte array containing two AES-128 keys.
35    pub fn new(key: [u8; Self::KEY_BYTES]) -> Self {
36        let (key1, key2) = key.split_at(Self::KEY_BYTES / 2);
37        let cipher1 = Aes128::new_from_slice(key1).expect("key1 length is correct");
38        let cipher2 = Aes128::new_from_slice(key2).expect("key2 length is correct");
39        Self { cipher1, cipher2 }
40    }
41
42    /// Creates a new IpcryptNdx instance with a random key.
43    #[cfg(feature = "random")]
44    pub fn new_random() -> Self {
45        Self::new(Self::generate_key())
46    }
47
48    /// Generates a random tweak.
49    #[cfg(feature = "random")]
50    pub fn generate_tweak() -> [u8; Self::TWEAK_BYTES] {
51        rand::random()
52    }
53
54    /// Encrypts a 16-byte IP address using XTS mode.
55    ///
56    /// # Arguments
57    ///
58    /// * `ip` - The IP address bytes to encrypt
59    /// * `tweak` - The tweak to use for encryption
60    fn encrypt_ip16(&self, ip: &mut [u8; 16], tweak: &[u8; Self::TWEAK_BYTES]) {
61        // First encrypt the tweak with the second key
62        let mut encrypted_tweak = Block::from(*tweak);
63        self.cipher2.encrypt_block(&mut encrypted_tweak);
64
65        // XOR the input with the encrypted tweak
66        let mut block = Block::from(*ip);
67        for (b, t) in block.iter_mut().zip(encrypted_tweak.iter()) {
68            *b ^= t;
69        }
70
71        // Encrypt with the first key
72        self.cipher1.encrypt_block(&mut block);
73
74        // XOR with the encrypted tweak again
75        for (b, t) in block.iter_mut().zip(encrypted_tweak.iter()) {
76            *b ^= t;
77        }
78
79        *ip = block.into();
80    }
81
82    /// Decrypts a 16-byte IP address using XTS mode.
83    ///
84    /// # Arguments
85    ///
86    /// * `ip` - The encrypted IP address bytes to decrypt
87    /// * `tweak` - The tweak used for encryption
88    fn decrypt_ip16(&self, ip: &mut [u8; 16], tweak: &[u8; Self::TWEAK_BYTES]) {
89        // First encrypt the tweak with the second key
90        let mut encrypted_tweak = Block::from(*tweak);
91        self.cipher2.encrypt_block(&mut encrypted_tweak);
92
93        // XOR the input with the encrypted tweak
94        let mut block = Block::from(*ip);
95        for (b, t) in block.iter_mut().zip(encrypted_tweak.iter()) {
96            *b ^= t;
97        }
98
99        // Decrypt with the first key
100        self.cipher1.decrypt_block(&mut block);
101
102        // XOR with the encrypted tweak again
103        for (b, t) in block.iter_mut().zip(encrypted_tweak.iter()) {
104            *b ^= t;
105        }
106
107        *ip = block.into();
108    }
109
110    /// Encrypts an IP address using XTS mode.
111    ///
112    /// # Arguments
113    ///
114    /// * `ip` - The IP address to encrypt
115    /// * `tweak` - Optional tweak to use. If None, a random tweak will be generated.
116    ///
117    /// # Returns
118    /// The encrypted IP address, as a byte array of length 32.
119    pub fn encrypt_ipaddr(
120        &self,
121        ip: IpAddr,
122        tweak: Option<[u8; Self::TWEAK_BYTES]>,
123    ) -> [u8; Self::NDIP_BYTES] {
124        let mut out: [u8; Self::NDIP_BYTES] = [0; Self::NDIP_BYTES];
125        #[cfg(feature = "random")]
126        let tweak = tweak.unwrap_or_else(Self::generate_tweak);
127        #[cfg(not(feature = "random"))]
128        let tweak = tweak.expect("tweak must be provided when random feature is disabled");
129        let mut bytes = ip_to_bytes(ip);
130        self.encrypt_ip16(&mut bytes, &tweak);
131        out[0..16].copy_from_slice(&tweak);
132        out[16..].copy_from_slice(&bytes);
133        out
134    }
135
136    /// Decrypts an IP address using XTS mode.
137    ///
138    /// # Arguments
139    ///
140    /// * `encrypted` - The encrypted IP address as a byte array of length 32.
141    /// * `tweak` - The tweak used for encryption
142    ///
143    /// # Returns
144    /// The decrypted IP address
145    pub fn decrypt_ipaddr(&self, encrypted: &[u8; Self::NDIP_BYTES]) -> IpAddr {
146        let mut tweak = [0u8; Self::TWEAK_BYTES];
147        tweak.copy_from_slice(&encrypted[0..16]);
148        let mut bytes = [0u8; 16];
149        bytes.copy_from_slice(&encrypted[16..]);
150        self.decrypt_ip16(&mut bytes, &tweak);
151        bytes_to_ip(bytes)
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158    use ct_codecs::{Decoder as _, Encoder as _, Hex};
159    use std::str::FromStr;
160
161    #[test]
162    fn test_ndx_vectors() {
163        let test_vectors = vec![
164            (
165                // Test vector 1
166                "0123456789abcdeffedcba98765432101032547698badcfeefcdab8967452301",
167                "0.0.0.0",
168                "21bd1834bc088cd2b4ecbe30b70898d7",
169                "21bd1834bc088cd2b4ecbe30b70898d782db0d4125fdace61db35b8339f20ee5",
170            ),
171            (
172                // Test vector 2
173                "1032547698badcfeefcdab89674523010123456789abcdeffedcba9876543210",
174                "192.0.2.1",
175                "08e0c289bff23b7cb4ecbe30b70898d7",
176                "08e0c289bff23b7cb4ecbe30b70898d7766a533392a69edf1ad0d3ce362ba98a",
177            ),
178            (
179                // Test vector 3
180                "2b7e151628aed2a6abf7158809cf4f3c3c4fcf098815f7aba6d2ae2816157e2b",
181                "2001:db8::1",
182                "21bd1834bc088cd2b4ecbe30b70898d7",
183                "21bd1834bc088cd2b4ecbe30b70898d76089c7e05ae30c2d10ca149870a263e4",
184            ),
185        ];
186
187        for (key_hex, input_ip, tweak_hex, expected_output) in test_vectors {
188            // Parse key using constant-time hex decoder
189            let key_vec = Hex::decode_to_vec(key_hex.as_bytes(), None).unwrap();
190            let mut key = [0u8; IpcryptNdx::KEY_BYTES];
191            key.copy_from_slice(&key_vec);
192
193            // Parse tweak
194            let tweak_vec = Hex::decode_to_vec(tweak_hex.as_bytes(), None).unwrap();
195            let mut tweak = [0u8; IpcryptNdx::TWEAK_BYTES];
196            tweak.copy_from_slice(&tweak_vec);
197
198            // Create IpcryptNdx instance
199            let ipcrypt = IpcryptNdx::new(key);
200
201            // Parse input IP
202            let ip = IpAddr::from_str(input_ip).unwrap();
203
204            // Encrypt with provided tweak
205            let encrypted = ipcrypt.encrypt_ipaddr(ip, Some(tweak));
206
207            // Convert to hex string for comparison
208            let encrypted_hex = Hex::encode_to_string(encrypted).unwrap();
209            assert_eq!(encrypted_hex, expected_output);
210
211            // Test decryption
212            let decrypted = ipcrypt.decrypt_ipaddr(&encrypted);
213            assert_eq!(decrypted, ip);
214        }
215    }
216}