Comfoa's picture
feat: Vercel-ready OpenCouncil with 3 cities connected (Paris, Sulphur Springs, Frisco)
6ce841a
Raw History Blame Contribute Delete
3.9 kB
"""
API key encryption utilities for OpenCouncil.
Provides Fernet-based symmetric encryption for the Groq API key
so it can be stored encrypted in the database or config files.
For Vercel/GitHub deployment:
1. Generate an encryption key once: python -c "from crypto_utils import generate_key; print(generate_key())"
2. Set ENCRYPTION_KEY as a Vercel environment variable
3. Set GROK_API_KEY (encrypted) as a Vercel environment variable
4. The app decrypts at runtime using the ENCRYPTION_KEY
This means even if someone gets access to your DB or .env,
the API key is still encrypted with a separate key.
"""
import base64
import os
from typing import Optional
try:
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
HAS_CRYPTO = True
except ImportError:
HAS_CRYPTO = False
def generate_key() -> str:
"""Generate a new Fernet encryption key.
Run this once and save the output as ENCRYPTION_KEY env var.
Usage:
python -c "from crypto_utils import generate_key; print(generate_key())"
"""
if not HAS_CRYPTO:
raise ImportError("Install cryptography: pip install cryptography")
return Fernet.generate_key().decode()
def _get_fernet(encryption_key: Optional[str] = None) -> Fernet:
"""Get a Fernet instance using the provided key or ENCRYPTION_KEY env var."""
if not HAS_CRYPTO:
raise ImportError(
"cryptography package is required for API key encryption. "
"Install it with: pip install cryptography"
)
key = encryption_key or os.getenv("ENCRYPTION_KEY")
if not key:
raise ValueError(
"ENCRYPTION_KEY is required. Generate one with: "
"python -c \"from crypto_utils import generate_key; print(generate_key())\""
)
# Ensure key is bytes and properly formatted
if isinstance(key, str):
key = key.encode()
return Fernet(key)
def encrypt_api_key(api_key: str, encryption_key: Optional[str] = None) -> str:
"""Encrypt an API key (e.g., DeepSeek key) for safe storage.
Args:
api_key: The plaintext API key to encrypt
encryption_key: Optional custom encryption key (defaults to ENCRYPTION_KEY env var)
Returns:
Base64-encoded encrypted string
"""
f = _get_fernet(encryption_key)
encrypted = f.encrypt(api_key.encode())
return encrypted.decode()
def decrypt_api_key(encrypted_key: str, encryption_key: Optional[str] = None) -> str:
"""Decrypt an API key that was encrypted with encrypt_api_key().
Args:
encrypted_key: The encrypted API key string
encryption_key: Optional custom encryption key (defaults to ENCRYPTION_KEY env var)
Returns:
Plaintext API key
"""
f = _get_fernet(encryption_key)
decrypted = f.decrypt(encrypted_key.encode())
return decrypted.decode()
def get_api_key_from_env() -> Optional[str]:
"""Get the Groq API key, supporting both plaintext and encrypted modes.
Resolution order:
1. GROK_API_KEY (plaintext) — for local dev
2. ENCRYPTED_GROK_KEY + ENCRYPTION_KEY — for production/GitHub-safe deploy
Returns:
The decrypted API key, or None if not configured.
"""
# First try plaintext (local dev)
plain_key = os.getenv("GROK_API_KEY")
if plain_key:
return plain_key
# Then try encrypted (production/GitHub-safe)
encrypted_key = os.getenv("ENCRYPTED_GROK_KEY")
encryption_key = os.getenv("ENCRYPTION_KEY")
if encrypted_key and encryption_key:
try:
return decrypt_api_key(encrypted_key, encryption_key)
except Exception as e:
print(f"[WARN] Failed to decrypt API key: {e}")
return None
return None