from __future__ import annotations import base64 import hashlib import hmac import secrets from pathlib import Path from app.core.config import SERVER_DIR SECRET_KEY_FILE = SERVER_DIR / ".secrets" / "settings.key" SECRET_BOX_VERSION = "v1" KEY_BYTES = 32 NONCE_BYTES = 16 MAC_BYTES = 32 BLOCK_BYTES = 32 def _ensure_secret_dir(path: Path) -> None: path.parent.mkdir(parents=True, exist_ok=True) def get_or_create_secret_key() -> bytes: _ensure_secret_dir(SECRET_KEY_FILE) if SECRET_KEY_FILE.exists(): encoded = SECRET_KEY_FILE.read_text(encoding="utf-8").strip() if encoded: return base64.urlsafe_b64decode(encoded.encode("ascii")) secret_key = secrets.token_bytes(KEY_BYTES) encoded = base64.urlsafe_b64encode(secret_key).decode("ascii") SECRET_KEY_FILE.write_text(encoded, encoding="utf-8") return secret_key def _keystream(secret_key: bytes, nonce: bytes, length: int) -> bytes: chunks: list[bytes] = [] counter = 0 while sum(len(chunk) for chunk in chunks) < length: block = hmac.new( secret_key, b"stream:" + nonce + counter.to_bytes(4, "big"), hashlib.sha256, ).digest() chunks.append(block) counter += 1 return b"".join(chunks)[:length] def encrypt_secret(value: str) -> str: if not value: return "" secret_key = get_or_create_secret_key() nonce = secrets.token_bytes(NONCE_BYTES) plaintext = value.encode("utf-8") ciphertext = bytes(a ^ b for a, b in zip(plaintext, _keystream(secret_key, nonce, len(plaintext)), strict=False)) mac = hmac.new(secret_key, b"mac:" + nonce + ciphertext, hashlib.sha256).digest() encoded_nonce = base64.urlsafe_b64encode(nonce).decode("ascii") encoded_ciphertext = base64.urlsafe_b64encode(ciphertext).decode("ascii") encoded_mac = base64.urlsafe_b64encode(mac).decode("ascii") return f"{SECRET_BOX_VERSION}${encoded_nonce}${encoded_ciphertext}${encoded_mac}" def decrypt_secret(value: str) -> str: if not value: return "" try: version, encoded_nonce, encoded_ciphertext, encoded_mac = value.split("$", 3) except ValueError as exc: raise ValueError("Invalid secret payload format") from exc if version != SECRET_BOX_VERSION: raise ValueError("Unsupported secret payload version") secret_key = get_or_create_secret_key() nonce = base64.urlsafe_b64decode(encoded_nonce.encode("ascii")) ciphertext = base64.urlsafe_b64decode(encoded_ciphertext.encode("ascii")) expected_mac = base64.urlsafe_b64decode(encoded_mac.encode("ascii")) actual_mac = hmac.new(secret_key, b"mac:" + nonce + ciphertext, hashlib.sha256).digest() if not hmac.compare_digest(actual_mac, expected_mac): raise ValueError("Secret payload integrity check failed") plaintext = bytes(a ^ b for a, b in zip(ciphertext, _keystream(secret_key, nonce, len(ciphertext)), strict=False)) return plaintext.decode("utf-8")