Skip to main content

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