"""enc:v2 envelope encryption (H4): roundtrip, legacy v1 compat, format checks.""" import base64 import hashlib import pytest from cryptography.fernet import Fernet from app.services import crypto @pytest.fixture(autouse=True) def _kek(monkeypatch): monkeypatch.setattr(crypto.settings, "encryption_key", "k" * 64) # Reset module caches so each test derives from the patched KEK. crypto._fernet_v1 = None crypto._fernet_v2_cache.clear() crypto._encrypt_salt = None yield crypto._fernet_v1 = None crypto._fernet_v2_cache.clear() crypto._encrypt_salt = None def test_v2_roundtrip(): blob = crypto.encrypt_api_key("0x" + "ab" * 32) assert blob.startswith(crypto.ENC_PREFIX_V2) assert crypto.decrypt_api_key(blob) == "0x" + "ab" * 32 assert crypto.is_current_format(blob) # Fits the hl_api_key String(256) column. assert len(blob) <= 256 def test_v1_blob_still_decrypts(): # Build a v1 blob exactly the way the legacy code did. digest = hashlib.sha256(("k" * 64).encode()).digest() f = Fernet(base64.urlsafe_b64encode(digest)) blob = crypto.ENC_PREFIX_V1 + f.encrypt(b"secret-key").decode() assert not crypto.is_current_format(blob) assert crypto.decrypt_api_key(blob) == "secret-key" def test_wrong_kek_raises(monkeypatch): blob = crypto.encrypt_api_key("topsecret") monkeypatch.setattr(crypto.settings, "encryption_key", "x" * 64) crypto._fernet_v2_cache.clear() with pytest.raises(RuntimeError): crypto.decrypt_api_key(blob) def test_malformed_v2_raises(): with pytest.raises(RuntimeError): crypto.decrypt_api_key(crypto.ENC_PREFIX_V2 + "no-salt-separator") def test_plaintext_refused_in_production(monkeypatch): monkeypatch.setattr(crypto.settings, "environment", "production") with pytest.raises(RuntimeError): crypto.decrypt_api_key("raw-plaintext-key")