""" Encryption module — Fernet symmetric encryption for servers.json. Used by both GUI (ServerStore) and CLI (ssh.py). """ import os from cryptography.fernet import Fernet, InvalidToken # Strong hardcoded key (generated from os.urandom(32), base64-encoded) # This is the application-level encryption key — by design it's embedded. ENCRYPTION_KEY = b"xK9mQ2vL7pR4wZ8nB3jF6hT1yD5sA0cE-gU_iO9lMWk=" # Migration: old key from v1.1.0-1.2.0 _OLD_KEY = b"b8iPQzO8_18Y68NluKLQPUTfXVyRsz_BIzTeqfm0aZk=" _fernet = Fernet(ENCRYPTION_KEY) _fernet_old = Fernet(_OLD_KEY) def encrypt(plaintext: str) -> bytes: """Encrypt a plaintext string, return Fernet token bytes.""" return _fernet.encrypt(plaintext.encode("utf-8")) def decrypt(data: bytes) -> str: """Decrypt Fernet token bytes, return plaintext string. Tries new key first, falls back to old key for migration.""" try: return _fernet.decrypt(data).decode("utf-8") except InvalidToken: # Try old key for backward compatibility return _fernet_old.decrypt(data).decode("utf-8") def is_encrypted(data: bytes) -> bool: """Check if data is a Fernet token (starts with 'gAAAAA') vs plain JSON (starts with '{').""" try: return data.decode("utf-8").strip().startswith("gAAAAA") except UnicodeDecodeError: return True def needs_reencrypt(data: bytes) -> bool: """Check if data was encrypted with old key and needs re-encryption.""" try: _fernet.decrypt(data) return False except InvalidToken: pass try: _fernet_old.decrypt(data) return True except InvalidToken: return False