Test legacy password fallback in decrypt_data

This commit is contained in:
thePR0M3TH3AN
2025-08-03 17:32:25 -04:00
parent f03a890776
commit 6a31ec7e99
2 changed files with 80 additions and 24 deletions

View File

@@ -94,35 +94,59 @@ class EncryptionManager:
(3) The core migration logic. Tries the new format first, then falls back
to the old one. This is the ONLY place decryption logic should live.
"""
# Try the new V2 format first
if encrypted_data.startswith(b"V2:"):
try:
nonce = encrypted_data[3:15]
ciphertext = encrypted_data[15:]
if len(ciphertext) < 16:
logger.error("AES-GCM payload too short")
raise InvalidToken("AES-GCM payload too short")
return self.cipher.decrypt(nonce, ciphertext, None)
except InvalidTag as e:
logger.error("AES-GCM decryption failed: Invalid authentication tag.")
try:
# Try the new V2 format first
if encrypted_data.startswith(b"V2:"):
try:
result = self.fernet.decrypt(encrypted_data[3:])
logger.warning(
"Legacy-format file had incorrect 'V2:' header; decrypted with Fernet"
nonce = encrypted_data[3:15]
ciphertext = encrypted_data[15:]
if len(ciphertext) < 16:
logger.error("AES-GCM payload too short")
raise InvalidToken("AES-GCM payload too short")
return self.cipher.decrypt(nonce, ciphertext, None)
except InvalidTag as e:
logger.error(
"AES-GCM decryption failed: Invalid authentication tag."
)
return result
except InvalidToken:
raise InvalidToken("AES-GCM decryption failed.") from e
try:
result = self.fernet.decrypt(encrypted_data[3:])
logger.warning(
"Legacy-format file had incorrect 'V2:' header; decrypted with Fernet"
)
return result
except InvalidToken:
raise InvalidToken("AES-GCM decryption failed.") from e
# If it's not V2, it must be the legacy Fernet format
else:
logger.warning("Data is in legacy Fernet format. Attempting migration.")
# If it's not V2, it must be the legacy Fernet format
else:
logger.warning("Data is in legacy Fernet format. Attempting migration.")
try:
return self.fernet.decrypt(encrypted_data)
except InvalidToken as e:
logger.error(
"Legacy Fernet decryption failed. Vault may be corrupt or key is incorrect."
)
raise InvalidToken(
"Could not decrypt data with any available method."
) from e
except (InvalidToken, InvalidTag) as e:
if isinstance(e, InvalidToken) and str(e) == "AES-GCM payload too short":
raise
logger.error(f"FATAL: Could not decrypt data: {e}", exc_info=True)
try:
return self.fernet.decrypt(encrypted_data)
except InvalidToken as e:
logger.error(
"Legacy Fernet decryption failed. Vault may be corrupt or key is incorrect."
password = prompt_existing_password(
"Enter your master password for legacy decryption: "
)
legacy_key = _derive_legacy_key_from_password(password)
legacy_mgr = EncryptionManager(legacy_key, self.fingerprint_dir)
result = legacy_mgr.decrypt_data(encrypted_data)
logger.warning(
"Data decrypted using legacy password-only key derivation."
)
return result
except Exception as e2: # pragma: no cover - exceptional path
logger.error(f"Failed legacy decryption attempt: {e2}", exc_info=True)
raise InvalidToken(
"Could not decrypt data with any available method."
) from e

View File

@@ -0,0 +1,32 @@
import base64
import hashlib
import unicodedata
from helpers import TEST_PASSWORD
import seedpass.core.encryption as enc_module
from seedpass.core.encryption import EncryptionManager
from utils.key_derivation import derive_key_from_password
def _fast_legacy_key(password: str, iterations: int = 100_000) -> bytes:
normalized = unicodedata.normalize("NFKD", password).strip().encode("utf-8")
key = hashlib.pbkdf2_hmac("sha256", normalized, b"", 1, dklen=32)
return base64.urlsafe_b64encode(key)
def test_decrypt_data_password_fallback(tmp_path, monkeypatch):
monkeypatch.setattr(
enc_module, "_derive_legacy_key_from_password", _fast_legacy_key
)
monkeypatch.setattr(
enc_module, "prompt_existing_password", lambda *_a, **_k: TEST_PASSWORD
)
legacy_key = _fast_legacy_key(TEST_PASSWORD)
legacy_mgr = EncryptionManager(legacy_key, tmp_path)
payload = legacy_mgr.encrypt_data(b"secret")
new_key = derive_key_from_password(TEST_PASSWORD, "fp")
new_mgr = EncryptionManager(new_key, tmp_path)
assert new_mgr.decrypt_data(payload) == b"secret"