56 lines
1.7 KiB
Python
56 lines
1.7 KiB
Python
"""E2E encryption for federation payloads.
|
|
|
|
X25519 key exchange + ChaCha20-Poly1305 for symmetric encryption.
|
|
Uses the `cryptography` library (already a koi-net dependency).
|
|
"""
|
|
|
|
import base64
|
|
import os
|
|
|
|
from cryptography.hazmat.primitives.asymmetric.x25519 import (
|
|
X25519PrivateKey,
|
|
X25519PublicKey,
|
|
)
|
|
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
|
|
|
|
|
|
def generate_keypair() -> tuple[bytes, bytes]:
|
|
"""Generate X25519 keypair. Returns (private_key_bytes, public_key_bytes)."""
|
|
private_key = X25519PrivateKey.generate()
|
|
public_key = private_key.public_key()
|
|
return (
|
|
private_key.private_bytes_raw(),
|
|
public_key.public_bytes_raw(),
|
|
)
|
|
|
|
|
|
def derive_shared_key(our_private: bytes, their_public: bytes) -> bytes:
|
|
"""Derive shared secret from X25519 key exchange."""
|
|
private_key = X25519PrivateKey.from_private_bytes(our_private)
|
|
public_key = X25519PublicKey.from_public_bytes(their_public)
|
|
return private_key.exchange(public_key)
|
|
|
|
|
|
def encrypt(shared_key: bytes, plaintext: bytes) -> bytes:
|
|
"""Encrypt with ChaCha20-Poly1305. Returns nonce + ciphertext."""
|
|
aead = ChaCha20Poly1305(shared_key)
|
|
nonce = os.urandom(12)
|
|
ciphertext = aead.encrypt(nonce, plaintext, None)
|
|
return nonce + ciphertext
|
|
|
|
|
|
def decrypt(shared_key: bytes, data: bytes) -> bytes:
|
|
"""Decrypt ChaCha20-Poly1305. Expects nonce (12 bytes) + ciphertext."""
|
|
aead = ChaCha20Poly1305(shared_key)
|
|
nonce = data[:12]
|
|
ciphertext = data[12:]
|
|
return aead.decrypt(nonce, ciphertext, None)
|
|
|
|
|
|
def public_key_to_b64(key_bytes: bytes) -> str:
|
|
return base64.b64encode(key_bytes).decode()
|
|
|
|
|
|
def b64_to_public_key(b64: str) -> bytes:
|
|
return base64.b64decode(b64)
|