42 lines
1.2 KiB
Python
42 lines
1.2 KiB
Python
"""
|
|
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()
|