"""AES-256-CBC encrypt/decrypt for NTCP2 handshake obfuscation. Ported from: net.i2p.router.transport.ntcp.NTCP2Payload (obfuscation of ephemeral key X) net.i2p.crypto.CryptixAESEngine (underlying AES-CBC) In NTCP2 handshake message 1, the 32-byte ephemeral key X is encrypted with AES-256-CBC: - Key = SHA-256(responder's RouterIdentity bytes) — the identity hash itself - IV = responder's "i" parameter (16 bytes from RouterAddress options) - Plaintext = 32 bytes (exactly 2 AES blocks, no padding needed) - After encryption, bytes 16-31 of ciphertext become the IV for message 2 These functions use the ``cryptography`` library with no padding, since NTCP2 messages are always block-aligned. """ from __future__ import annotations from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes _BLOCK_SIZE = 16 _KEY_SIZE = 32 def _validate(key: bytes, iv: bytes, data: bytes) -> None: """Validate key, IV, and data sizes.""" if len(key) != _KEY_SIZE: raise ValueError( f"Key must be exactly {_KEY_SIZE} bytes, got {len(key)}" ) if len(iv) != _BLOCK_SIZE: raise ValueError( f"IV must be exactly {_BLOCK_SIZE} bytes, got {len(iv)}" ) if len(data) == 0: raise ValueError("Data must not be empty") if len(data) % _BLOCK_SIZE != 0: raise ValueError( f"Data length must be a multiple of {_BLOCK_SIZE}, " f"got {len(data)}" ) def aes_cbc_encrypt(key: bytes, iv: bytes, plaintext: bytes) -> bytes: """Encrypt *plaintext* with AES-256-CBC. Parameters ---------- key: AES-256 key, exactly 32 bytes. iv: Initialisation vector, exactly 16 bytes. plaintext: Data to encrypt. Length must be a positive multiple of 16. Returns ------- bytes Ciphertext of the same length as *plaintext*. Raises ------ ValueError If any size constraint is violated. """ _validate(key, iv, plaintext) cipher = Cipher(algorithms.AES256(key), modes.CBC(iv)) encryptor = cipher.encryptor() return encryptor.update(plaintext) + encryptor.finalize() def aes_cbc_decrypt(key: bytes, iv: bytes, ciphertext: bytes) -> bytes: """Decrypt *ciphertext* with AES-256-CBC. Parameters ---------- key: AES-256 key, exactly 32 bytes. iv: Initialisation vector, exactly 16 bytes. ciphertext: Data to decrypt. Length must be a positive multiple of 16. Returns ------- bytes Plaintext of the same length as *ciphertext*. Raises ------ ValueError If any size constraint is violated. """ _validate(key, iv, ciphertext) cipher = Cipher(algorithms.AES256(key), modes.CBC(iv)) decryptor = cipher.decryptor() return decryptor.update(ciphertext) + decryptor.finalize()