A Python port of the Invisible Internet Project (I2P)
at main 98 lines 2.9 kB view raw
1"""AES-256-CBC encrypt/decrypt for NTCP2 handshake obfuscation. 2 3Ported from: 4 net.i2p.router.transport.ntcp.NTCP2Payload (obfuscation of ephemeral key X) 5 net.i2p.crypto.CryptixAESEngine (underlying AES-CBC) 6 7In NTCP2 handshake message 1, the 32-byte ephemeral key X is encrypted 8with AES-256-CBC: 9 - Key = SHA-256(responder's RouterIdentity bytes) — the identity hash itself 10 - IV = responder's "i" parameter (16 bytes from RouterAddress options) 11 - Plaintext = 32 bytes (exactly 2 AES blocks, no padding needed) 12 - After encryption, bytes 16-31 of ciphertext become the IV for message 2 13 14These functions use the ``cryptography`` library with no padding, since 15NTCP2 messages are always block-aligned. 16""" 17 18from __future__ import annotations 19 20from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes 21 22_BLOCK_SIZE = 16 23_KEY_SIZE = 32 24 25 26def _validate(key: bytes, iv: bytes, data: bytes) -> None: 27 """Validate key, IV, and data sizes.""" 28 if len(key) != _KEY_SIZE: 29 raise ValueError( 30 f"Key must be exactly {_KEY_SIZE} bytes, got {len(key)}" 31 ) 32 if len(iv) != _BLOCK_SIZE: 33 raise ValueError( 34 f"IV must be exactly {_BLOCK_SIZE} bytes, got {len(iv)}" 35 ) 36 if len(data) == 0: 37 raise ValueError("Data must not be empty") 38 if len(data) % _BLOCK_SIZE != 0: 39 raise ValueError( 40 f"Data length must be a multiple of {_BLOCK_SIZE}, " 41 f"got {len(data)}" 42 ) 43 44 45def aes_cbc_encrypt(key: bytes, iv: bytes, plaintext: bytes) -> bytes: 46 """Encrypt *plaintext* with AES-256-CBC. 47 48 Parameters 49 ---------- 50 key: 51 AES-256 key, exactly 32 bytes. 52 iv: 53 Initialisation vector, exactly 16 bytes. 54 plaintext: 55 Data to encrypt. Length must be a positive multiple of 16. 56 57 Returns 58 ------- 59 bytes 60 Ciphertext of the same length as *plaintext*. 61 62 Raises 63 ------ 64 ValueError 65 If any size constraint is violated. 66 """ 67 _validate(key, iv, plaintext) 68 cipher = Cipher(algorithms.AES256(key), modes.CBC(iv)) 69 encryptor = cipher.encryptor() 70 return encryptor.update(plaintext) + encryptor.finalize() 71 72 73def aes_cbc_decrypt(key: bytes, iv: bytes, ciphertext: bytes) -> bytes: 74 """Decrypt *ciphertext* with AES-256-CBC. 75 76 Parameters 77 ---------- 78 key: 79 AES-256 key, exactly 32 bytes. 80 iv: 81 Initialisation vector, exactly 16 bytes. 82 ciphertext: 83 Data to decrypt. Length must be a positive multiple of 16. 84 85 Returns 86 ------- 87 bytes 88 Plaintext of the same length as *ciphertext*. 89 90 Raises 91 ------ 92 ValueError 93 If any size constraint is violated. 94 """ 95 _validate(key, iv, ciphertext) 96 cipher = Cipher(algorithms.AES256(key), modes.CBC(iv)) 97 decryptor = cipher.decryptor() 98 return decryptor.update(ciphertext) + decryptor.finalize()