import os import sys from pathlib import Path from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from cryptography.hazmat.backends import default_backend sys.path.append(str(Path(__file__).resolve().parent.parent.parent)) from .config import ( SALT_SIZE, KEY_SIZE, AES_NONCE_SIZE, AES_TAG_SIZE, ITERATIONS, ) def derive_key(password: str, salt: bytes) -> bytes: """Derives a key from a password and salt using PBKDF2-HMAC-SHA256.""" if not password: raise ValueError("Password cannot be empty.") kdf = PBKDF2HMAC( algorithm=hashes.SHA256(), length=KEY_SIZE, salt=salt, iterations=ITERATIONS, backend=default_backend(), ) return kdf.derive(password.encode("utf-8")) def encrypt_data(data: bytes, password: str) -> bytes: """Encrypts data using AES-256 GCM.""" salt = os.urandom(SALT_SIZE) nonce = os.urandom(AES_NONCE_SIZE) key = derive_key(password, salt) cipher = Cipher(algorithms.AES(key), modes.GCM(nonce), backend=default_backend()) encryptor = cipher.encryptor() ciphertext = encryptor.update(data) + encryptor.finalize() return salt + nonce + encryptor.tag + ciphertext def decrypt_data(encrypted_data: bytes, password: str) -> bytes: """Decrypts data using AES-256 GCM.""" salt = encrypted_data[:SALT_SIZE] nonce = encrypted_data[SALT_SIZE : SALT_SIZE + AES_NONCE_SIZE] tag = encrypted_data[ SALT_SIZE + AES_NONCE_SIZE : SALT_SIZE + AES_NONCE_SIZE + AES_TAG_SIZE ] ciphertext = encrypted_data[SALT_SIZE + AES_NONCE_SIZE + AES_TAG_SIZE :] key = derive_key(password, salt) cipher = Cipher( algorithms.AES(key), modes.GCM(nonce, tag), backend=default_backend() ) decryptor = cipher.decryptor() return decryptor.update(ciphertext) + decryptor.finalize()