ipcrypt_rs/
nd.rs

1use aes::hazmat;
2use aes::Block;
3use std::net::IpAddr;
4
5use crate::aes::*;
6use crate::common::{bytes_to_ip, ip_to_bytes};
7
8/// A structure representing the IPCrypt context for non-deterministic mode.
9///
10/// This struct provides methods for encrypting and decrypting IP addresses using KIASU-BC mode
11/// with an 8-byte tweak. The key is 16 bytes (one AES-128 key).
12pub struct IpcryptNd {
13    round_keys: Vec<Block>,
14}
15
16impl Drop for IpcryptNd {
17    fn drop(&mut self) {
18        self.round_keys.clear();
19    }
20}
21
22impl IpcryptNd {
23    /// The number of bytes required for the encryption key.
24    pub const KEY_BYTES: usize = 16;
25    /// The number of bytes required for the tweak.
26    pub const TWEAK_BYTES: usize = 8;
27    /// The number of bytes in the non-deterministic mode output (8-byte tweak + 16-byte ciphertext).
28    pub const NDIP_BYTES: usize = Self::TWEAK_BYTES + 16;
29
30    /// Generates a new random key for encryption.
31    #[cfg(feature = "random")]
32    pub fn generate_key() -> [u8; Self::KEY_BYTES] {
33        rand::random()
34    }
35
36    /// Creates a new IpcryptNd instance with the given key.
37    ///
38    /// # Arguments
39    ///
40    /// * `key` - A 16-byte array containing the encryption key.
41    pub fn new(key: [u8; Self::KEY_BYTES]) -> Self {
42        let round_keys = Self::expand_key(&key);
43        Self { round_keys }
44    }
45
46    /// Creates a new IpcryptNd instance with a random key.
47    #[cfg(feature = "random")]
48    pub fn new_random() -> Self {
49        Self::new(Self::generate_key())
50    }
51
52    /// Generates a random tweak.
53    #[cfg(feature = "random")]
54    pub fn generate_tweak() -> [u8; Self::TWEAK_BYTES] {
55        rand::random()
56    }
57
58    /// Pads an 8-byte tweak to 16 bytes according to KIASU-BC specification.
59    /// The tweak is padded by placing each 2-byte pair at the start of a 4-byte group.
60    fn pad_tweak(tweak: &[u8; Self::TWEAK_BYTES]) -> Block {
61        let mut padded = Block::default();
62        for i in (0..8).step_by(2) {
63            padded[i * 2] = tweak[i];
64            padded[i * 2 + 1] = tweak[i + 1];
65        }
66        padded
67    }
68
69    /// Encrypts a 16-byte IP address using KIASU-BC mode.
70    ///
71    /// This is an internal function that performs the core KIASU-BC encryption.
72    /// For public use, prefer `encrypt_ipaddr`.
73    fn encrypt_ip16(&self, ip: &mut [u8; 16], tweak: &[u8; Self::TWEAK_BYTES]) {
74        let padded_tweak = Self::pad_tweak(tweak);
75        let mut block = Block::from(*ip);
76
77        // Initial round
78        for i in 0..16 {
79            block[i] ^= self.round_keys[0][i] ^ padded_tweak[i];
80        }
81
82        // Main rounds
83        for round in 1..10 {
84            // Create tweaked round key by XORing round key with tweak
85            let mut tweaked_key = self.round_keys[round];
86            for i in 0..16 {
87                tweaked_key[i] ^= padded_tweak[i];
88            }
89            hazmat::cipher_round(&mut block, &tweaked_key);
90        }
91
92        // Final round
93        // SubBytes
94        for i in 0..16 {
95            block[i] = SBOX[block[i] as usize];
96        }
97
98        // ShiftRows
99        shift_rows(&mut block);
100
101        // AddRoundKey with tweak
102        for i in 0..16 {
103            block[i] ^= self.round_keys[10][i] ^ padded_tweak[i];
104        }
105
106        *ip = block.into();
107    }
108
109    /// Decrypts a 16-byte IP address using KIASU-BC mode.
110    ///
111    /// This is an internal function that performs the core KIASU-BC decryption.
112    /// For public use, prefer `decrypt_ipaddr`.
113    fn decrypt_ip16(&self, ip: &mut [u8; 16], tweak: &[u8; Self::TWEAK_BYTES]) {
114        let padded_tweak = Self::pad_tweak(tweak);
115        let mut block = Block::from(*ip);
116
117        // Initial round
118        for i in 0..16 {
119            block[i] ^= self.round_keys[10][i] ^ padded_tweak[i];
120        }
121
122        // Inverse ShiftRows
123        inv_shift_rows(&mut block);
124
125        // Inverse SubBytes
126        for i in 0..16 {
127            block[i] = INV_SBOX[block[i] as usize];
128        }
129
130        // Main rounds
131        for round in (1..10).rev() {
132            // AddRoundKey with tweak
133            for i in 0..16 {
134                block[i] ^= self.round_keys[round][i] ^ padded_tweak[i];
135            }
136
137            // Inverse MixColumns
138            inv_mix_columns(&mut block);
139
140            // Inverse ShiftRows
141            inv_shift_rows(&mut block);
142
143            // Inverse SubBytes
144            for i in 0..16 {
145                block[i] = INV_SBOX[block[i] as usize];
146            }
147        }
148
149        // Final round
150        for i in 0..16 {
151            block[i] ^= self.round_keys[0][i] ^ padded_tweak[i];
152        }
153
154        *ip = block.into();
155    }
156
157    /// Encrypts an IP address using non-deterministic mode with KIASU-BC.
158    ///
159    /// # Arguments
160    ///
161    /// * `ip` - The IP address to encrypt
162    /// * `tweak` - Optional tweak to use. If None, a random tweak will be generated.
163    ///
164    /// # Returns
165    /// A 24-byte array containing the concatenation of the 8-byte tweak and the 16-byte encrypted IP address.
166    pub fn encrypt_ipaddr(
167        &self,
168        ip: IpAddr,
169        tweak: Option<[u8; Self::TWEAK_BYTES]>,
170    ) -> [u8; Self::NDIP_BYTES] {
171        let mut out = [0u8; Self::NDIP_BYTES];
172        #[cfg(feature = "random")]
173        let tweak = tweak.unwrap_or_else(Self::generate_tweak);
174        #[cfg(not(feature = "random"))]
175        let tweak = tweak.expect("tweak must be provided when random feature is disabled");
176        let mut bytes = ip_to_bytes(ip);
177        self.encrypt_ip16(&mut bytes, &tweak);
178        out[0..Self::TWEAK_BYTES].copy_from_slice(&tweak);
179        out[Self::TWEAK_BYTES..].copy_from_slice(&bytes);
180        out
181    }
182
183    /// Decrypts an IP address that was encrypted using non-deterministic mode with KIASU-BC.
184    ///
185    /// # Arguments
186    ///
187    /// * `encrypted` - A 24-byte array containing the concatenation of the 8-byte tweak and 16-byte ciphertext
188    ///
189    /// # Returns
190    /// The decrypted IP address
191    pub fn decrypt_ipaddr(&self, encrypted: &[u8; Self::NDIP_BYTES]) -> IpAddr {
192        let mut tweak = [0u8; Self::TWEAK_BYTES];
193        tweak.copy_from_slice(&encrypted[0..Self::TWEAK_BYTES]);
194        let mut bytes = [0u8; 16];
195        bytes.copy_from_slice(&encrypted[Self::TWEAK_BYTES..]);
196        self.decrypt_ip16(&mut bytes, &tweak);
197        bytes_to_ip(bytes)
198    }
199
200    /// Expands a 16-byte key into 11 round keys using the AES key schedule.
201    ///
202    /// This is an internal function used during initialization to generate the round keys
203    /// needed for encryption and decryption operations.
204    fn expand_key(key: &[u8; Self::KEY_BYTES]) -> Vec<Block> {
205        let mut round_keys = Vec::with_capacity(11);
206
207        // First round key is the original key
208        let current_key = Block::from(*key);
209        round_keys.push(current_key);
210
211        // Generate remaining round keys
212        for i in 1..11 {
213            let prev_key = round_keys[i - 1];
214            let mut next_key = Block::default();
215
216            // First word
217            // RotWord and SubWord
218            let t0 = prev_key[13];
219            let t1 = prev_key[14];
220            let t2 = prev_key[15];
221            let t3 = prev_key[12];
222            let s0 = SBOX[t0 as usize];
223            let s1 = SBOX[t1 as usize];
224            let s2 = SBOX[t2 as usize];
225            let s3 = SBOX[t3 as usize];
226
227            // XOR with Rcon and previous key
228            next_key[0] = prev_key[0] ^ s0 ^ RCON[i - 1];
229            next_key[1] = prev_key[1] ^ s1;
230            next_key[2] = prev_key[2] ^ s2;
231            next_key[3] = prev_key[3] ^ s3;
232
233            // Remaining words
234            next_key[4] = next_key[0] ^ prev_key[4];
235            next_key[5] = next_key[1] ^ prev_key[5];
236            next_key[6] = next_key[2] ^ prev_key[6];
237            next_key[7] = next_key[3] ^ prev_key[7];
238
239            next_key[8] = next_key[4] ^ prev_key[8];
240            next_key[9] = next_key[5] ^ prev_key[9];
241            next_key[10] = next_key[6] ^ prev_key[10];
242            next_key[11] = next_key[7] ^ prev_key[11];
243
244            next_key[12] = next_key[8] ^ prev_key[12];
245            next_key[13] = next_key[9] ^ prev_key[13];
246            next_key[14] = next_key[10] ^ prev_key[14];
247            next_key[15] = next_key[11] ^ prev_key[15];
248
249            round_keys.push(next_key);
250        }
251
252        round_keys
253    }
254}
255
256#[cfg(test)]
257mod tests {
258    use super::*;
259    use ct_codecs::{Decoder as _, Encoder as _, Hex};
260    use std::str::FromStr;
261
262    #[test]
263    fn test_nd_vectors() {
264        let test_vectors = vec![
265            (
266                // Test vector 1
267                "0123456789abcdeffedcba9876543210",
268                "0.0.0.0",
269                "08e0c289bff23b7c",
270                "08e0c289bff23b7cb349aadfe3bcef56221c384c7c217b16",
271            ),
272            (
273                // Test vector 2
274                "1032547698badcfeefcdab8967452301",
275                "192.0.2.1",
276                "21bd1834bc088cd2",
277                "21bd1834bc088cd2e5e1fe55f95876e639faae2594a0caad",
278            ),
279            (
280                // Test vector 3
281                "2b7e151628aed2a6abf7158809cf4f3c",
282                "2001:db8::1",
283                "b4ecbe30b70898d7",
284                "b4ecbe30b70898d7553ac8974d1b4250eafc4b0aa1f80c96",
285            ),
286        ];
287
288        for (key_hex, input_ip, tweak_hex, 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; IpcryptNd::KEY_BYTES];
292            key.copy_from_slice(&key_vec);
293
294            // Parse tweak
295            let tweak_vec = Hex::decode_to_vec(tweak_hex.as_bytes(), None).unwrap();
296            let mut tweak = [0u8; IpcryptNd::TWEAK_BYTES];
297            tweak.copy_from_slice(&tweak_vec);
298
299            // Create IpcryptNd instance
300            let ipcrypt = IpcryptNd::new(key);
301
302            // Parse input IP
303            let ip = IpAddr::from_str(input_ip).unwrap();
304
305            // Encrypt with provided tweak
306            let encrypted = ipcrypt.encrypt_ipaddr(ip, Some(tweak));
307
308            // Convert to hex string for comparison
309            let encrypted_hex = Hex::encode_to_string(encrypted).unwrap();
310            assert_eq!(encrypted_hex, expected_output);
311
312            // Test decryption
313            let decrypted = ipcrypt.decrypt_ipaddr(&encrypted);
314            assert_eq!(decrypted, ip);
315        }
316    }
317}