mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2025-09-09 07:48:57 +00:00
Merge pull request #743 from PR0M3TH3AN/codex/fix-aes-gcm-decryption-error-due-to-key-mismatch
Add legacy decryption fallback and tests
This commit is contained in:
@@ -94,6 +94,7 @@ class EncryptionManager:
|
|||||||
(3) The core migration logic. Tries the new format first, then falls back
|
(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.
|
to the old one. This is the ONLY place decryption logic should live.
|
||||||
"""
|
"""
|
||||||
|
try:
|
||||||
# Try the new V2 format first
|
# Try the new V2 format first
|
||||||
if encrypted_data.startswith(b"V2:"):
|
if encrypted_data.startswith(b"V2:"):
|
||||||
try:
|
try:
|
||||||
@@ -104,7 +105,9 @@ class EncryptionManager:
|
|||||||
raise InvalidToken("AES-GCM payload too short")
|
raise InvalidToken("AES-GCM payload too short")
|
||||||
return self.cipher.decrypt(nonce, ciphertext, None)
|
return self.cipher.decrypt(nonce, ciphertext, None)
|
||||||
except InvalidTag as e:
|
except InvalidTag as e:
|
||||||
logger.error("AES-GCM decryption failed: Invalid authentication tag.")
|
logger.error(
|
||||||
|
"AES-GCM decryption failed: Invalid authentication tag."
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
result = self.fernet.decrypt(encrypted_data[3:])
|
result = self.fernet.decrypt(encrypted_data[3:])
|
||||||
logger.warning(
|
logger.warning(
|
||||||
@@ -127,6 +130,27 @@ class EncryptionManager:
|
|||||||
"Could not decrypt data with any available method."
|
"Could not decrypt data with any available method."
|
||||||
) from e
|
) 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:
|
||||||
|
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
|
||||||
|
|
||||||
# --- All functions below this point now use the smart `decrypt_data` method ---
|
# --- All functions below this point now use the smart `decrypt_data` method ---
|
||||||
|
|
||||||
def resolve_relative_path(self, relative_path: Path) -> Path:
|
def resolve_relative_path(self, relative_path: Path) -> Path:
|
||||||
|
32
src/tests/test_decrypt_data_legacy_fallback.py
Normal file
32
src/tests/test_decrypt_data_legacy_fallback.py
Normal 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"
|
Reference in New Issue
Block a user