from __future__ import annotations from pathlib import Path from cryptography.fernet import Fernet, InvalidToken from paths import DATA_DIR KEY_FILE = DATA_DIR / ".secret_key" ENCRYPTED_PREFIX = "enc:" def _ensure_key() -> bytes: KEY_FILE.parent.mkdir(parents=True, exist_ok=True) if KEY_FILE.exists(): raw = KEY_FILE.read_bytes().strip() if raw: return raw key = Fernet.generate_key() KEY_FILE.write_bytes(key) try: KEY_FILE.chmod(0o600) except OSError: pass return key def _fernet() -> Fernet: return Fernet(_ensure_key()) def encrypt_secret(plain: str) -> str: if not plain: return "" if plain.startswith(ENCRYPTED_PREFIX): return plain token = _fernet().encrypt(plain.encode("utf-8")).decode("ascii") return f"{ENCRYPTED_PREFIX}{token}" def decrypt_secret(value: str) -> str: if not value: return "" if not value.startswith(ENCRYPTED_PREFIX): return value token = value[len(ENCRYPTED_PREFIX) :].encode("ascii") try: return _fernet().decrypt(token).decode("utf-8") except InvalidToken as exc: raise ValueError( "Could not decrypt stored password. Secret key may have changed; re-enter password." ) from exc def is_encrypted(value: str) -> bool: return bool(value) and value.startswith(ENCRYPTED_PREFIX)