A Python port of the Invisible Internet Project (I2P)
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()