""" control/crypto.py ----------------- Symmetric encryption for sensitive control-plane fields (tenant DB passwords). Fernet (AES-128-CBC + HMAC) keyed by the CONTROL_FERNET_KEY env var. Generate a key once: python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" Store it in the server environment / .env (never in git). Key rotation requires re-encrypting existing tenant rows. """ import os from cryptography.fernet import Fernet def _fernet() -> Fernet: key = os.environ.get('CONTROL_FERNET_KEY') if not key: raise RuntimeError( "CONTROL_FERNET_KEY is not set. Generate one with: " "python -c \"from cryptography.fernet import Fernet; " "print(Fernet.generate_key().decode())\"" ) return Fernet(key.encode() if isinstance(key, str) else key) def encrypt(plaintext): """Encrypt a string to a Fernet token (str). Passes None through.""" if plaintext is None: return None return _fernet().encrypt(plaintext.encode()).decode() def decrypt(token): """Decrypt a Fernet token back to the original string. Passes None through.""" if token is None: return None return _fernet().decrypt(token.encode()).decode()