"""ChaCha20-Poly1305 cipher backend.""" import os import base64 from typing import Tuple from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305 from .base import CipherBackend class ChaCha20Cipher(CipherBackend): """ChaCha20-Poly1305 authenticated encryption.""" def encrypt(self, plaintext: bytes) -> Tuple[bytes, str]: """ Encrypt using ChaCha20-Poly1305. Returns: Tuple of (nonce + ciphertext, base64url-encoded key) """ # Generate random 256-bit key key = ChaCha20Poly1305.generate_key() chacha = ChaCha20Poly1305(key) # Generate random 96-bit nonce nonce = os.urandom(12) # Encrypt and authenticate ciphertext = chacha.encrypt(nonce, plaintext, None) # Prepend nonce to ciphertext encrypted_data = nonce + ciphertext # Encode key for URL fragment key_encoded = base64.urlsafe_b64encode(key).decode('ascii').rstrip('=') return encrypted_data, key_encoded def decrypt(self, ciphertext: bytes, key: str) -> bytes: """ Decrypt using ChaCha20-Poly1305. Args: ciphertext: Nonce (12 bytes) + encrypted data key: Base64url-encoded 256-bit key """ # Decode key (add padding if needed) padding = '=' * (4 - len(key) % 4) if len(key) % 4 else '' key_bytes = base64.urlsafe_b64decode(key + padding) if len(key_bytes) != 32: raise ValueError("Invalid key length. Expected 32 bytes for ChaCha20.") # Extract nonce and ciphertext nonce = ciphertext[:12] encrypted = ciphertext[12:] # Decrypt chacha = ChaCha20Poly1305(key_bytes) plaintext = chacha.decrypt(nonce, encrypted, None) return plaintext def get_name(self) -> str: """Return cipher name.""" return "chacha20-poly1305"