44 lines
1.4 KiB
Python
44 lines
1.4 KiB
Python
"""Symmetric encryption helper for operator secrets stored in the DB.
|
|
|
|
``DECNET_SECRET_KEY`` must be a URL-safe base64-encoded 32-byte Fernet key
|
|
(generate once with ``python -m decnet.web.db.secrets``). The env var is
|
|
read lazily — at the call site of ``encrypt_secret``/``decrypt_secret`` —
|
|
so processes that never touch encrypted columns start up without it.
|
|
|
|
Fail-closed: a missing or malformed key raises ``RuntimeError`` before any
|
|
plaintext is encrypted or any ciphertext is decrypted.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
|
|
from cryptography.fernet import Fernet, InvalidToken
|
|
|
|
|
|
def _load_key() -> bytes:
|
|
key = os.environ.get("DECNET_SECRET_KEY", "")
|
|
if not key:
|
|
raise RuntimeError(
|
|
"DECNET_SECRET_KEY is not set — cannot encrypt/decrypt secrets. "
|
|
"Generate a key with: python -c \"from cryptography.fernet import Fernet; "
|
|
"print(Fernet.generate_key().decode())\""
|
|
)
|
|
return key.encode()
|
|
|
|
|
|
def encrypt_secret(plaintext: str) -> str:
|
|
"""Return a Fernet ciphertext token for *plaintext*."""
|
|
return Fernet(_load_key()).encrypt(plaintext.encode()).decode()
|
|
|
|
|
|
def decrypt_secret(ciphertext: str) -> str:
|
|
"""Decrypt a Fernet ciphertext token; raises ``InvalidToken`` if tampered."""
|
|
try:
|
|
return Fernet(_load_key()).decrypt(ciphertext.encode()).decode()
|
|
except InvalidToken:
|
|
raise
|
|
|
|
|
|
if __name__ == "__main__":
|
|
print(Fernet.generate_key().decode())
|