Switch encryption to AES-GCM

This commit is contained in:
thePR0M3TH3AN
2025-07-12 14:05:06 -04:00
parent 7b09bb0fdb
commit d27e3708c5
3 changed files with 32 additions and 29 deletions

View File

@@ -4,7 +4,7 @@
Encryption Module Encryption Module
This module provides the EncryptionManager class, which handles encryption and decryption 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 that sensitive data is securely stored and retrieved, maintaining the confidentiality and integrity
of the password index. of the password index.
@@ -22,7 +22,9 @@ import os
from pathlib import Path from pathlib import Path
from typing import Optional 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 termcolor import colored
from utils.file_lock import ( from utils.file_lock import (
exclusive_lock, exclusive_lock,
@@ -36,7 +38,7 @@ class EncryptionManager:
""" """
EncryptionManager Class 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): 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. Initializes the EncryptionManager with the provided encryption key and fingerprint directory.
Parameters: 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. fingerprint_dir (Path): The directory corresponding to the fingerprint.
""" """
self.fingerprint_dir = fingerprint_dir self.fingerprint_dir = fingerprint_dir
self.parent_seed_file = self.fingerprint_dir / "parent_seed.enc" self.parent_seed_file = self.fingerprint_dir / "parent_seed.enc"
self.key = encryption_key
try: try:
self.fernet = Fernet(self.key) if isinstance(encryption_key, str):
logger.debug(f"EncryptionManager initialized for {self.fingerprint_dir}") 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: except Exception as e:
logger.error( logger.error(
f"Failed to initialize Fernet with provided encryption key: {e}" f"Failed to initialize AESGCM with provided encryption key: {e}"
) )
print( print(
colored(f"Error: Failed to initialize encryption manager: {e}", "red") 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}'." f"Parent seed decrypted successfully from '{parent_seed_path}'."
) )
return parent_seed return parent_seed
except InvalidToken: except InvalidTag:
logger.error( logger.error(
"Invalid encryption key or corrupted data while decrypting parent seed." "Invalid encryption key or corrupted data while decrypting parent seed."
) )
@@ -130,15 +136,13 @@ class EncryptionManager:
raise raise
def encrypt_data(self, data: bytes) -> bytes: def encrypt_data(self, data: bytes) -> bytes:
""" """Encrypt ``data`` with AES-GCM and prepend the nonce."""
Encrypts the given data using Fernet.
:param data: Data to encrypt.
:return: Encrypted data.
"""
try: try:
encrypted_data = self.fernet.encrypt(data) nonce = os.urandom(12)
logger.debug("Data encrypted successfully.") ciphertext = self.cipher.encrypt(nonce, data, None)
encrypted_data = nonce + ciphertext
logger.debug("Data encrypted successfully with AES-GCM.")
return encrypted_data return encrypted_data
except Exception as e: except Exception as e:
logger.error(f"Failed to encrypt data: {e}", exc_info=True) logger.error(f"Failed to encrypt data: {e}", exc_info=True)
@@ -146,17 +150,14 @@ class EncryptionManager:
raise raise
def decrypt_data(self, encrypted_data: bytes) -> bytes: def decrypt_data(self, encrypted_data: bytes) -> bytes:
""" """Decrypt AES-GCM data that includes a prepended nonce."""
Decrypts the provided encrypted data using the derived key.
:param encrypted_data: The encrypted data to decrypt.
:return: The decrypted data as bytes.
"""
try: try:
decrypted_data = self.fernet.decrypt(encrypted_data) nonce, ciphertext = encrypted_data[:12], encrypted_data[12:]
logger.debug("Data decrypted successfully.") decrypted_data = self.cipher.decrypt(nonce, ciphertext, None)
logger.debug("Data decrypted successfully with AES-GCM.")
return decrypted_data return decrypted_data
except InvalidToken: except InvalidTag:
logger.error( logger.error(
"Invalid encryption key or corrupted data while decrypting data." "Invalid encryption key or corrupted data while decrypting data."
) )
@@ -228,7 +229,7 @@ class EncryptionManager:
decrypted_data = self.decrypt_data(encrypted_data) decrypted_data = self.decrypt_data(encrypted_data)
logger.debug(f"Data decrypted successfully from '{file_path}'.") logger.debug(f"Data decrypted successfully from '{file_path}'.")
return decrypted_data return decrypted_data
except InvalidToken: except InvalidTag:
logger.error( logger.error(
"Invalid encryption key or corrupted data while decrypting file." "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 f"Failed to decode JSON data from '{file_path}': {e}", exc_info=True
) )
raise raise
except InvalidToken: except InvalidTag:
logger.error( logger.error(
"Invalid encryption key or corrupted data while decrypting JSON data." "Invalid encryption key or corrupted data while decrypting JSON data."
) )

View File

@@ -74,7 +74,7 @@ def export_backup(
"created_at": int(time.time()), "created_at": int(time.time()),
"fingerprint": vault.fingerprint_dir.name, "fingerprint": vault.fingerprint_dir.name,
"encryption_mode": PortableMode.SEED_ONLY.value, "encryption_mode": PortableMode.SEED_ONLY.value,
"cipher": "fernet", "cipher": "aes-gcm",
"checksum": checksum, "checksum": checksum,
"payload": base64.b64encode(payload_bytes).decode("utf-8"), "payload": base64.b64encode(payload_bytes).decode("utf-8"),
} }

View File

@@ -43,13 +43,15 @@ def test_round_trip(monkeypatch):
path = export_backup(vault, backup, parent_seed=SEED) path = export_backup(vault, backup, parent_seed=SEED)
assert path.exists() assert path.exists()
wrapper = json.loads(path.read_text())
assert wrapper.get("cipher") == "aes-gcm"
vault.save_index({"pw": 0}) vault.save_index({"pw": 0})
import_backup(vault, backup, path, parent_seed=SEED) import_backup(vault, backup, path, parent_seed=SEED)
assert vault.load_index()["pw"] == data["pw"] assert vault.load_index()["pw"] == data["pw"]
from cryptography.fernet import InvalidToken from cryptography.exceptions import InvalidTag
def test_corruption_detection(monkeypatch): def test_corruption_detection(monkeypatch):
@@ -66,7 +68,7 @@ def test_corruption_detection(monkeypatch):
content["payload"] = base64.b64encode(payload).decode() content["payload"] = base64.b64encode(payload).decode()
path.write_text(json.dumps(content)) path.write_text(json.dumps(content))
with pytest.raises(InvalidToken): with pytest.raises(InvalidTag):
import_backup(vault, backup, path, parent_seed=SEED) import_backup(vault, backup, path, parent_seed=SEED)