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