diff --git a/src/password_manager/encryption.py b/src/password_manager/encryption.py index 5f6fe1c..c309e52 100644 --- a/src/password_manager/encryption.py +++ b/src/password_manager/encryption.py @@ -4,7 +4,7 @@ Encryption Module This module provides the EncryptionManager class, which handles encryption and decryption -of data and files using a provided Fernet-compatible encryption key. This class ensures +of data and files using a provided AES-GCM encryption key. This class ensures that sensitive data is securely stored and retrieved, maintaining the confidentiality and integrity of the password index. @@ -22,7 +22,9 @@ import os from pathlib import Path from typing import Optional -from cryptography.fernet import Fernet, InvalidToken +import base64 +from cryptography.hazmat.primitives.ciphers.aead import AESGCM +from cryptography.exceptions import InvalidTag from termcolor import colored from utils.file_lock import ( exclusive_lock, @@ -36,7 +38,7 @@ class EncryptionManager: """ EncryptionManager Class - Manages the encryption and decryption of data and files using a Fernet encryption key. + Manages the encryption and decryption of data and files using an AES-GCM encryption key. """ def __init__(self, encryption_key: bytes, fingerprint_dir: Path): @@ -44,19 +46,23 @@ class EncryptionManager: Initializes the EncryptionManager with the provided encryption key and fingerprint directory. Parameters: - encryption_key (bytes): The Fernet encryption key. + encryption_key (bytes): A base64-encoded AES-GCM key. fingerprint_dir (Path): The directory corresponding to the fingerprint. """ self.fingerprint_dir = fingerprint_dir self.parent_seed_file = self.fingerprint_dir / "parent_seed.enc" - self.key = encryption_key try: - self.fernet = Fernet(self.key) - logger.debug(f"EncryptionManager initialized for {self.fingerprint_dir}") + if isinstance(encryption_key, str): + encryption_key = encryption_key.encode() + self.key = base64.urlsafe_b64decode(encryption_key) + self.cipher = AESGCM(self.key) + logger.debug( + f"EncryptionManager initialized for {self.fingerprint_dir} using AES-GCM" + ) except Exception as e: logger.error( - f"Failed to initialize Fernet with provided encryption key: {e}" + f"Failed to initialize AESGCM with provided encryption key: {e}" ) print( colored(f"Error: Failed to initialize encryption manager: {e}", "red") @@ -119,7 +125,7 @@ class EncryptionManager: f"Parent seed decrypted successfully from '{parent_seed_path}'." ) return parent_seed - except InvalidToken: + except InvalidTag: logger.error( "Invalid encryption key or corrupted data while decrypting parent seed." ) @@ -130,15 +136,13 @@ class EncryptionManager: raise def encrypt_data(self, data: bytes) -> bytes: - """ - Encrypts the given data using Fernet. + """Encrypt ``data`` with AES-GCM and prepend the nonce.""" - :param data: Data to encrypt. - :return: Encrypted data. - """ try: - encrypted_data = self.fernet.encrypt(data) - logger.debug("Data encrypted successfully.") + nonce = os.urandom(12) + ciphertext = self.cipher.encrypt(nonce, data, None) + encrypted_data = nonce + ciphertext + logger.debug("Data encrypted successfully with AES-GCM.") return encrypted_data except Exception as e: logger.error(f"Failed to encrypt data: {e}", exc_info=True) @@ -146,17 +150,14 @@ class EncryptionManager: raise def decrypt_data(self, encrypted_data: bytes) -> bytes: - """ - Decrypts the provided encrypted data using the derived key. + """Decrypt AES-GCM data that includes a prepended nonce.""" - :param encrypted_data: The encrypted data to decrypt. - :return: The decrypted data as bytes. - """ try: - decrypted_data = self.fernet.decrypt(encrypted_data) - logger.debug("Data decrypted successfully.") + nonce, ciphertext = encrypted_data[:12], encrypted_data[12:] + decrypted_data = self.cipher.decrypt(nonce, ciphertext, None) + logger.debug("Data decrypted successfully with AES-GCM.") return decrypted_data - except InvalidToken: + except InvalidTag: logger.error( "Invalid encryption key or corrupted data while decrypting data." ) @@ -228,7 +229,7 @@ class EncryptionManager: decrypted_data = self.decrypt_data(encrypted_data) logger.debug(f"Data decrypted successfully from '{file_path}'.") return decrypted_data - except InvalidToken: + except InvalidTag: logger.error( "Invalid encryption key or corrupted data while decrypting file." ) @@ -308,7 +309,7 @@ class EncryptionManager: f"Failed to decode JSON data from '{file_path}': {e}", exc_info=True ) raise - except InvalidToken: + except InvalidTag: logger.error( "Invalid encryption key or corrupted data while decrypting JSON data." ) diff --git a/src/password_manager/portable_backup.py b/src/password_manager/portable_backup.py index 14c7dce..3e27671 100644 --- a/src/password_manager/portable_backup.py +++ b/src/password_manager/portable_backup.py @@ -74,7 +74,7 @@ def export_backup( "created_at": int(time.time()), "fingerprint": vault.fingerprint_dir.name, "encryption_mode": PortableMode.SEED_ONLY.value, - "cipher": "fernet", + "cipher": "aes-gcm", "checksum": checksum, "payload": base64.b64encode(payload_bytes).decode("utf-8"), } diff --git a/src/tests/test_portable_backup.py b/src/tests/test_portable_backup.py index 8c89fae..07e6fe0 100644 --- a/src/tests/test_portable_backup.py +++ b/src/tests/test_portable_backup.py @@ -43,13 +43,15 @@ def test_round_trip(monkeypatch): path = export_backup(vault, backup, parent_seed=SEED) assert path.exists() + wrapper = json.loads(path.read_text()) + assert wrapper.get("cipher") == "aes-gcm" vault.save_index({"pw": 0}) import_backup(vault, backup, path, parent_seed=SEED) assert vault.load_index()["pw"] == data["pw"] -from cryptography.fernet import InvalidToken +from cryptography.exceptions import InvalidTag def test_corruption_detection(monkeypatch): @@ -66,7 +68,7 @@ def test_corruption_detection(monkeypatch): content["payload"] = base64.b64encode(payload).decode() path.write_text(json.dumps(content)) - with pytest.raises(InvalidToken): + with pytest.raises(InvalidTag): import_backup(vault, backup, path, parent_seed=SEED)