#!/usr/bin/env python3 """ Module de déchiffrement pour le serveur API. Copie des fonctions de déchiffrement depuis agent_v0/storage_encrypted.py sans les dépendances sur config et raw_session. """ import os import logging from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC logger = logging.getLogger("server.storage_encrypted") def decrypt_session_file( encrypted_path: str, password: str, output_path: str | None = None ) -> str: """ Déchiffre un fichier .enc et restaure le ZIP original. Args: encrypted_path: Chemin du fichier .enc password: Mot de passe de déchiffrement output_path: Chemin de sortie (défaut: même nom avec .zip) Returns: Chemin du ZIP déchiffré Note: Version serveur sans dépendances agent_v0 """ if output_path is None: output_path = encrypted_path.replace('.enc', '.zip') try: # Lire le fichier chiffré with open(encrypted_path, 'rb') as f: salt = f.read(16) iv = f.read(16) ciphertext = f.read() if len(salt) != 16 or len(iv) != 16: raise ValueError("Fichier chiffré corrompu: salt ou IV manquant") # Dériver la clé depuis le password kdf = PBKDF2HMAC( algorithm=hashes.SHA256(), length=32, salt=salt, iterations=100000, backend=default_backend() ) key = kdf.derive(password.encode('utf-8')) # Créer cipher pour déchiffrement cipher = Cipher( algorithms.AES(key), modes.CBC(iv), backend=default_backend() ) decryptor = cipher.decryptor() # Déchiffrer plaintext = decryptor.update(ciphertext) + decryptor.finalize() # Retirer padding PKCS7 if len(plaintext) == 0: raise ValueError("Données déchiffrées vides") padding_length = plaintext[-1] if padding_length > 16 or padding_length == 0: raise ValueError(f"Padding invalide: {padding_length}") # Vérifier que tous les bytes de padding sont identiques for i in range(padding_length): if plaintext[-(i+1)] != padding_length: raise ValueError("Padding PKCS7 invalide") plaintext = plaintext[:-padding_length] # Écrire le ZIP déchiffré with open(output_path, 'wb') as f: f.write(plaintext) logger.info(f"Fichier déchiffré: {output_path}") return os.path.abspath(output_path) except Exception as e: logger.exception(f"Erreur lors du déchiffrement: {e}") raise def test_decryption_password(password: str) -> bool: """ Test rapide pour vérifier qu'un mot de passe de déchiffrement fonctionne. Args: password: Le mot de passe à tester Returns: True si le mot de passe semble valide """ if not password: return False # Test basique: le mot de passe doit être une chaîne non vide if len(password.strip()) == 0: return False # En production, on pourrait faire un test plus sophistiqué # avec un fichier de test connu return True if __name__ == "__main__": # Test rapide print("Module de déchiffrement serveur chargé") print(f"Test password 'test': {test_decryption_password('test')}") print(f"Test password '': {test_decryption_password('')}")