Powrót do strony głównej

Python crypto dla USB: SecureBytes i szyfrowanie strumieniowe

Artykuł opisuje crypto-silnik dla USB w Pythonie z klasą SecureBytes do bezpiecznego przechowywania kluczy, strumieniowym szyfrowaniem dużych plików i mechanizmami odporności na awarie. Obsługa AES-GCM, ChaCha20. Nacisk na eliminację luk pamięciowych i awarie zasilania.

Tworzymy crypto-silnik dla pendrive'ów: Python bez wycieków pamięci
Advertisement 728x90

Bezpieczne szyfrowanie nośników USB w Pythonie: zarządzanie pamięcią i przetwarzanie strumieniowe

Aby bezpiecznie przesyłać pliki przez USB, potrzebna jest prosta obsługa: włożenie nośnika, wpisanie hasła, dostęp do zaszyfrowanych danych. VeraCrypt tworzy kontenery, co utrudnia pracę z pojedynczymi plikami na różnych systemach operacyjnych. Kluczowym wyzwaniem jest ochrona przed uszkodzeniem danych przy nagłym odłączeniu zasilania, gdy pendrive zostaje wyjęty podczas szyfrowania.

Rozwinięto silnik kryptograficzny w Pythonie z wykorzystaniem AES-GCM i ChaCha20. Głównym celem było bezpieczne przechowywanie kluczy w pamięci, szyfrowanie plików o rozmiarze gigabajtów bez przepełniania RAM oraz mechanizmy zapewnienia integralności danych.

Bezpieczne przechowywanie kluczy: klasa SecureBytes

W Pythonie i Java, kolektor śmieci (GC) kopiuje wrażliwe dane (hasła, klucze) do nowych obszarów pamięci, pozostawiając kopie w RAM. Klasa SecureBytes eliminuje tę lukę bezpieczeństwa:

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
        # Rejestruj słaby finalizator
        self._weak_ref = weakref.ref(self, self._cleanup_callback)

    def wipe(self, passes: int = 3):
        if self._finalized or len(self._buffer) == 0:
            return
        # Przejście 1: losowe dane
        self._buffer[:] = secrets.token_bytes(len(self._buffer))
        # Przejście 2: zera
        self._buffer[:] = b'\x00' * len(self._buffer)
        self._finalized = True
        gc.collect()

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

Kluczowe cechy:

  • Użycie bytearray do nadpisywania danych na miejscu, w przeciwieństwie do niemodyfikowalnych bytes.
  • Wielokrotne wyczyszczenie: najpierw dane losowe, potem zera (zgodnie z NIST SP 800-88).
  • Wykorzystanie menadżera kontekstu with secure_key(...) zapewniające automatyczne wyczyszczenie nawet przy wyjątkach.

To minimalizuje czas przechowywania kluczy w pamięci i zapobiega ujawnieniu danych przez GC.

Szyfrowanie strumieniowe: MemorySensitiveReader

Szyfrowanie pliku 10 GB na maszynie z 4 GB RAM wymaga podejścia strumieniowego. Klasa MemorySensitiveReader wybiera tryb w zależności od rozmiaru pliku i dostępnej pamięci:

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)
        # Próg przełączania na tryb strumieniowy
        self.use_streaming = self.file_size > memory_threshold 

    def iter_chunks(self, chunk_size: int = 8192):
        # Odczyt i szyfrowanie blokami
        ...

Rozwiązanie problemu nonce: W AES-GCM i ChaCha20 powtarzanie tego samego nonce z tym samym kluczem to krytyczna luka. Derywacja unikalnego nonce dla każdego bloku:

def _derive_block_nonce_12bit(base_nonce: bytes, block_index: int) -> bytes:
    # Pierwsze 8 bajtów — prefiks, ostatnie 4 — licznik bloku
    prefix = base_nonce[:8]
    block_counter = block_index.to_bytes(4, byteorder='big')
    return prefix + block_counter

Bloków o rozmiarze 8 KB czyta się kolejno, a nonce generuje się z podstawowego i indeksu, zapewniając odporność kryptograficzną na pliki dowolnego rozmiaru.

Odporność na awarie

Przy awarii zasilania standardowe szyfrowanie z usuwaniem oryginału prowadzi do utraty danych. Zaimplementowane zostały mechanizmy:

Google AdInline article slot
  • Plik blokady .encryption_lock.json: zapisuje status in_progress oraz listę przetworzonych plików.
  • Pliki tymczasowe .tmp: szyfrowanie w kopii, oryginał usuwa się dopiero po weryfikacji.
  • Sprawdzenie integralności: deszyfrowanie bloku, porównanie HMAC i skrótów SHA-256.
  • Cofnięcie zmian: przy przerwaniu — automatyczne przywrócenie oryginałów z zaszyfrowanych plików.

Gwarantuje to, że po awarii dane albo są całkowicie zaszyfrowane, albo nie zostały zmienione.

Obsługiwane algorytmy

  • AES-256-GCM: sprzętowe przyspieszenie AES-NI.
  • ChaCha20-Poly1305: optymalne bez AES-NI (ARM).
  • XChaCha20-Poly1305: 24-bajtowy nonce dla dużych objętości.

Paralelizacja poprzez ThreadPoolExecutor przyspiesza I/O dla wielu małych plików, mimo GIL.

Co ważne:

  • Klasa SecureBytes zapobiega wyciekom kluczy przez GC dzięki wielokrotnemu czyszczeniu.
  • Szyfrowanie strumieniowe z unikalnymi nonce dla plików >100 MB bez ryzyka kolizji.
  • Pełna odporność na awarie: pliki blokad i cofnięcie przy awarii zasilania.
  • Obsługa AES-GCM, ChaCha20, XChaCha20 bez kompromisów w bezpieczeństwie.
  • Metadane w .usb_crypt_meta.json, hasła min. 12 znaków z walidacją.

Ograniczenia i model zagrożeń

Narzędzie chroni przed utratą pendrive’a, ale nie ukrywa nazw plików ani struktury katalogów. Nie chroni przed klawiaturami logującymi na hostowej maszynie. Przydatne dla programistów średnio- i zaawansowanych, którzy potrzebują niestandardowego szyfrowania USB z naciskiem na zarządzanie pamięcią i odporność na awarie.

— Editorial Team

Advertisement 728x90

Czytaj dalej