"""Verschlüsselung für Zugangsdaten, die Certain im Klartext braucht. Ein IMAP-Passwort lässt sich nicht hashen: Certain muss es dem Mailserver vorlegen können. Es liegt deshalb verschlüsselt in der Datenbank – wer die Datei in die Hände bekommt, hat damit noch nichts. Der Schlüssel wird aus ``session_secret`` abgeleitet und nirgends gespeichert. Das hat eine Folge, die im Formular auch so stehen muss: Wird das Server-Geheimnis getauscht, sind die abgelegten Passwörter unlesbar und die Postfächer müssen neu eingerichtet werden. Das ist der gewollte Preis dafür, dass ein Datenbank-Diebstahl allein nicht reicht. """ from functools import lru_cache from cryptography.fernet import Fernet, InvalidToken from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.hkdf import HKDF from ..config import get_settings # Trennt diesen Verwendungszweck von jedem anderen, der dasselbe Geheimnis nutzt. _INFO = b"certain-finance/mail-credentials/v1" @lru_cache def _schluessel() -> Fernet: roh = HKDF( algorithm=hashes.SHA256(), length=32, salt=None, info=_INFO ).derive(get_settings().session_secret.encode("utf-8")) import base64 return Fernet(base64.urlsafe_b64encode(roh)) def verschluesseln(klartext: str) -> str: return _schluessel().encrypt(klartext.encode("utf-8")).decode("ascii") def entschluesseln(geheim: str) -> str | None: """Klartext zurück, oder None wenn der Schlüssel nicht mehr passt.""" try: return _schluessel().decrypt(geheim.encode("ascii")).decode("utf-8") except (InvalidToken, ValueError): return None