crypto_secrets.py
Rama main · réplica de lectura del repositorio original.
#!/usr/bin/env python3
"""crypto_secrets.py - at-rest encryption for user-supplied credentials
(CloudLog API keys, QRZ.com/HamQTH.com passwords) stored in users.json.
Key derivation: SHA-256 of this installation's JWT_SECRET (config.SECRET,
a random value already generated per-install into .env by config.ensure_env),
base64-urlsafe encoded for Fernet. Reuses the existing per-installation
secret instead of introducing a second key file to manage/lose/back up.
"""
import base64
import hashlib
try:
from cryptography.fernet import Fernet, InvalidToken
HAS_CRYPTO = True
except ImportError:
HAS_CRYPTO = False
PREFIX = "enc1:"
_fernet = None
def _get_fernet():
global _fernet
if _fernet is None:
from config import SECRET
key = base64.urlsafe_b64encode(hashlib.sha256(SECRET.encode()).digest())
_fernet = Fernet(key)
return _fernet
def encrypt_secret(value: str) -> str:
"""Encrypt a credential for storage. Empty values, and values already
starting with PREFIX, pass through unchanged. If the cryptography
package is unavailable, degrades to plaintext (same behavior as before
this module existed) rather than failing the save."""
if not value or value.startswith(PREFIX) or not HAS_CRYPTO:
return value
return PREFIX + _get_fernet().encrypt(value.encode()).decode()
def decrypt_secret(value: str) -> str:
"""Decrypt a stored credential. Values without the PREFIX are treated
as legacy plaintext (pre-encryption installs, or crypto unavailable)
and returned as-is - they get encrypted the next time they're saved."""
if not value or not value.startswith(PREFIX) or not HAS_CRYPTO:
return value
try:
return _get_fernet().decrypt(value[len(PREFIX):].encode()).decode()
except InvalidToken:
return value