A Python port of the Invisible Internet Project (I2P)
at main 103 lines 3.1 kB view raw
1""" 2X25519 Elliptic Curve Diffie-Hellman key exchange. 3 4Port of net.i2p.crypto.x25519.X25519DH from I2P Java. 5Wraps Python's ``cryptography`` library (X25519PrivateKey / X25519PublicKey). 6 7All keys and shared secrets are raw 32-byte little-endian byte strings, 8matching the wire format used by I2P's ECIES_X25519. 9""" 10 11from __future__ import annotations 12 13from cryptography.hazmat.primitives.asymmetric.x25519 import ( 14 X25519PrivateKey, 15 X25519PublicKey, 16) 17from cryptography.hazmat.primitives.serialization import ( 18 Encoding, 19 PublicFormat, 20 PrivateFormat, 21 NoEncryption, 22) 23 24 25class X25519DH: 26 """ 27 Static-method helper for X25519 ECDH, mirroring the Java 28 ``X25519DH`` utility class. 29 30 All byte strings are 32 bytes in length. 31 """ 32 33 # Key length in bytes (matches EncType.ECIES_X25519) 34 KEY_LENGTH = 32 35 36 @staticmethod 37 def generate_keypair() -> tuple[bytes, bytes]: 38 """ 39 Generate a new X25519 key pair. 40 41 Returns: 42 (private_key_bytes, public_key_bytes) -- both 32 bytes. 43 """ 44 private_key = X25519PrivateKey.generate() 45 priv_bytes = private_key.private_bytes( 46 Encoding.Raw, PrivateFormat.Raw, NoEncryption() 47 ) 48 pub_bytes = private_key.public_key().public_bytes( 49 Encoding.Raw, PublicFormat.Raw 50 ) 51 return priv_bytes, pub_bytes 52 53 @staticmethod 54 def dh(private_key_bytes: bytes, public_key_bytes: bytes) -> bytes: 55 """ 56 Perform an X25519 Diffie-Hellman exchange. 57 58 Args: 59 private_key_bytes: 32-byte private (scalar) key. 60 public_key_bytes: 32-byte public (u-coordinate) key. 61 62 Returns: 63 32-byte shared secret. 64 65 Raises: 66 ValueError: if either key is not exactly 32 bytes. 67 """ 68 if len(private_key_bytes) != 32: 69 raise ValueError( 70 f"Private key must be 32 bytes, got {len(private_key_bytes)}" 71 ) 72 if len(public_key_bytes) != 32: 73 raise ValueError( 74 f"Public key must be 32 bytes, got {len(public_key_bytes)}" 75 ) 76 77 private_key = X25519PrivateKey.from_private_bytes(private_key_bytes) 78 public_key = X25519PublicKey.from_public_bytes(public_key_bytes) 79 return private_key.exchange(public_key) 80 81 @staticmethod 82 def public_from_private(private_key_bytes: bytes) -> bytes: 83 """ 84 Derive the public key from a private key. 85 86 Args: 87 private_key_bytes: 32-byte private key. 88 89 Returns: 90 32-byte public key. 91 92 Raises: 93 ValueError: if the private key is not exactly 32 bytes. 94 """ 95 if len(private_key_bytes) != 32: 96 raise ValueError( 97 f"Private key must be 32 bytes, got {len(private_key_bytes)}" 98 ) 99 100 private_key = X25519PrivateKey.from_private_bytes(private_key_bytes) 101 return private_key.public_key().public_bytes( 102 Encoding.Raw, PublicFormat.Raw 103 )