Retour à l'accueil

Chiffrement Python pour USB : SecureBytes et chiffrement streaming

L'article décrit un moteur de chiffrement pour USB en Python avec la classe SecureBytes pour le stockage sécurisé des clés, le chiffrement streaming de gros fichiers, et des mécanismes de tolérance aux pannes. Prise en charge d'AES-GCM, ChaCha20. Accent sur l'élimination des vulnérabilités mémoire et des problèmes de panne d'alimentation.

Création d'un moteur de chiffrement pour clés USB : Python sans fuites mémoire
Advertisement 728x90

Chiffrement USB Sécurisé en Python : Gestion de la Mémoire et Traitement par Flux

Pour transférer des fichiers de manière sécurisée via USB, l'essentiel est la simplicité : insérez la clé, saisissez un mot de passe, accédez aux données chiffrées. VeraCrypt utilise des fichiers conteneurs, ce qui complique la manipulation directe des fichiers entre différents systèmes d'exploitation. Le vrai défi ? Protéger contre la corruption des données en cas de coupure de courant soudaine lorsqu'une clé flash est retirée pendant le chiffrement.

Un moteur cryptographique développé en Python exploite AES-GCM et ChaCha20. L'accent est mis sur le stockage sécurisé des clés en mémoire, le chiffrement par flux de fichiers de plusieurs gigaoctets sans épuiser la RAM, ainsi que sur des mécanismes robustes d'intégrité des données.

La classe SecureBytes élimine une vulnérabilité critique dans Python et Java : la copie par le ramasse-miettes (GC) de données sensibles (mots de passe, clés) vers de nouvelles zones mémoire, laissant des traces en RAM. Voici son fonctionnement :

Google AdInline article slot
class SecureBytes:
    def __init__(self, data: Union[bytes, bytearray, int]):
        if isinstance(data, int):
            self._buffer = bytearray(data)
        else:
            self._buffer = bytearray(data)
        self._finalized = False
        # Enregistrer un finaliseur faible
        self._weak_ref = weakref.ref(self, self._cleanup_callback)

    def wipe(self, passes: int = 3):
        if self._finalized or len(self._buffer) == 0:
            return
        # Passage 1 : octets aléatoires
        self._buffer[:] = secrets.token_bytes(len(self._buffer))
        # Passage 2 : zéros
        self._buffer[:] = b'\x00' * len(self._buffer)
        self._finalized = True
        gc.collect()

    def __del__(self):
        if not self._finalized:
            self.wipe()

Fonctionnalités principales :

  • Utilisation de bytearray pour écrasements in-place — contrairement à bytes, immuable.
  • Effacement multi-pass : données aléatoires suivies de zéros (selon les recommandations NIST SP 800-88).
  • Conçu pour être utilisé avec les gestionnaires de contexte (with secure_key(...)) afin d’assurer le nettoyage même en cas d’exception.

Cela réduit au minimum l’exposition des clés en mémoire et empêche les fuites dues au ramasse-miettes.

Chiffrement par Flux : MemorySensitiveReader

Chiffrer un fichier de 10 Go sur une machine avec seulement 4 Go de RAM nécessite une approche par flux. La classe MemorySensitiveReader ajuste automatiquement son mode selon la taille du fichier et la mémoire disponible :

Google AdInline article slot
class MemorySensitiveReader:
    def __init__(self, file_path: str, memory_threshold: int = 100 * 1024 * 1024):
        self.file_size = os.path.getsize(file_path)
        # Seuil pour passer en mode flux
        self.use_streaming = self.file_size > memory_threshold 

    def iter_chunks(self, chunk_size: int = 8192):
        # Lecture et chiffrement par blocs
        ...

Résolution du problème du nonce : Réutiliser le même nonce avec une même clé dans AES-GCM ou ChaCha20 constitue une faille catastrophique. Pour l’éviter, chaque bloc reçoit un nonce unique dérivé d’un nonce de base et de son index :

def _derive_block_nonce_12bit(base_nonce: bytes, block_index: int) -> bytes:
    # Premiers 8 octets = préfixe, derniers 4 = compteur de bloc
    prefix = base_nonce[:8]
    block_counter = block_index.to_bytes(4, byteorder='big')
    return prefix + block_counter

Les blocs sont lus par tranches de 8 Ko, avec des nonces générés à partir de la valeur de base et de l’index — garantissant une résistance cryptographique indépendamment de la taille du fichier.

Résilience aux Défaillances

Le chiffrement classique supprimant le fichier original en cas d’échec entraîne une perte définitive des données. Ce système inclut :

Google AdInline article slot
  • Fichier verrou .encryption_lock.json : suit l’état (in_progress) et liste les fichiers traités.
  • Fichiers temporaires .tmp : chiffre une copie ; supprime l’original uniquement après vérification réussie.
  • Contrôles d’intégrité : déchiffre un bloc, compare les hachages HMAC et SHA-256.
  • Mécanisme de retour arrière : en cas d’interruption, restaure automatiquement les originaux à partir des fichiers chiffrés.

Cela garantit qu’après une coupure de courant, les données sont soit entièrement chiffrées, soit totalement intactes — jamais partiellement modifiées.

Algorithmes Pris en Charge

  • AES-256-GCM : tire parti de l’accélération matérielle via AES-NI.
  • ChaCha20-Poly1305 : meilleur rendement sur les appareils ARM sans AES-NI.
  • XChaCha20-Poly1305 : nonce de 24 octets pour un chiffrement à grande échelle.

La parallélisation via ThreadPoolExecutor améliore les performances I/O pour de nombreux petits fichiers, contournant ainsi la limitation du GIL.

Ce qui compte le plus :

  • SecureBytes empêche les fuites de clés via le GC grâce à un effacement multi-pass.
  • Chiffrement par flux avec nonces uniques pour les fichiers > 100 Mo — risque de collision nul.
  • Résilience complète : fichiers verrous et retour arrière automatique en cas de coupure.
  • Support des algorithmes AES-GCM, ChaCha20, XChaCha20 — sans compromettre la sécurité.
  • Métadonnées stockées dans .usb_crypt_meta.json, mots de passe ≥12 caractères avec validation.

— Editorial Team

Advertisement 728x90

Lire ensuite