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