Coverage for app/utils/crypto.py: 100.00%
22 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-25 16:05 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-25 16:05 +0000
1"""Symmetric encryption mirror for the worker.
3Identical surface to `backend/app/utils/crypto.py` so behaviour cannot drift.
4The shared `CREDENTIAL_ENCRYPTION_KEY` lets the worker decrypt envelopes the
5backend pushed onto the Celery queue without ever touching the database.
6"""
8from __future__ import annotations
10import base64
12from cryptography.fernet import Fernet, InvalidToken
14from app.config import settings
17def _build_cipher() -> Fernet:
18 key = settings.CREDENTIAL_ENCRYPTION_KEY
19 if not key:
20 raise RuntimeError(
21 "CREDENTIAL_ENCRYPTION_KEY is not set. The worker requires the same "
22 "Fernet key as the backend to decrypt credential envelopes."
23 )
24 try:
25 return Fernet(key.encode() if isinstance(key, str) else key)
26 except (ValueError, TypeError) as e:
27 raise RuntimeError(f"CREDENTIAL_ENCRYPTION_KEY is malformed: {e}") from e
30_cipher = _build_cipher()
33def encrypt(plaintext: str) -> bytes:
34 return _cipher.encrypt(plaintext.encode("utf-8"))
37def decrypt(token: bytes) -> str:
38 return _cipher.decrypt(token).decode("utf-8")
41def encrypt_b64(plaintext: str) -> str:
42 return base64.b64encode(encrypt(plaintext)).decode("ascii")
45def decrypt_b64(token_b64: str) -> str:
46 return decrypt(base64.b64decode(token_b64.encode("ascii")))
49__all__ = ["encrypt", "decrypt", "encrypt_b64", "decrypt_b64", "InvalidToken"]