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:
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
bytearraydo nadpisywania danych na miejscu, w przeciwieństwie do niemodyfikowalnychbytes. - 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:
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:
- Plik blokady
.encryption_lock.json: zapisuje statusin_progressoraz 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
Brak komentarzy.