Введение: Почему не VeraCrypt?
Всё началось с простой задачи: нужно было безопасно передавать файлы на обычных USB-флешках. Существующие решения либо создавали контейнеры (VeraCrypt), что неудобно для быстрого доступа к отдельным файлам на разных ОС, либо работали слишком сложно для конечного пользователя.
Мне нужно было решение уровня «вставил флешку -> ввел пароль -> файлы зашифрованы». Но главное требование — безопасность данных даже при сбое питания. Если выдернуть флешку посередине шифрования, данные не должны превратиться в кашу.
Так появился crypto_engine. Это не попытка изобрести свою криптографию (мы используем стандартные AES-GCM и ChaCha20), а инженерная работа над тем, как безопасно управлять ключами в памяти, обрабатывать гигабайтные файлы без переполнения RAM и гарантировать целостность данных.
1. Проблема памяти в Python и класс SecureBytes
Самая большая уязвимость криптографических утилит на управляемых языках (Python, Java) — это работа с памятью. Когда вы храните пароль или ключ в переменной bytes, сборщик мусора (GC) может скопировать эти данные в другое место памяти при сборке мусора, оставив исходные копии «висеть» в RAM до неопределенного времени.
В моем движке я реализовал класс SecureBytes, который решает эту проблему:
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 # Регистрируем слабый финализатор self._weak_ref = weakref.ref(self, self._cleanup_callback) def wipe(self, passes: int = 3): if self._finalized or len(self._buffer) == 0: return # Проход 1: случайные данные self._buffer[:] = secrets.token_bytes(len(self._buffer)) # Проход 2: нули self._buffer[:] = b'\x00' * len(self._buffer) self._finalized = True gc.collect() def __del__(self): if not self._finalized: self.wipe()
Что здесь важно:
Использование
bytearray: В отличие от неизменяемыхbytes,bytearrayпозволяет перезаписывать данные по тому же адресу памяти.Многопроходная очистка: Перед освобождением памяти буфер перезаписывается случайными данными, затем нулями (согласно рекомендациям NIST SP 800-88).
Контекстный менеджер: Ключи используются только внутри блока
with secure_key(...):, что гарантирует очистку даже при возникновении исключений.
2. Потоковое шифрование больших файлов
Шифрование файла размером 10 ГБ на флешке с 4 ГБ оперативной памяти — нетривиальная задача. Загружать файл целиком в RAM нельзя.
Я реализовал MemorySensitiveReader, который автоматически переключается между режимами работы в зависимости от размера файла и доступной памяти:
class MemorySensitiveReader: def __init__(self, file_path: str, memory_threshold: int = 100 * 1024 * 1024): self.file_size = os.path.getsize(file_path) # Порог переключения на потоковый режим self.use_streaming = self.file_size > memory_threshold def iter_chunks(self, chunk_size: int = 8192): # Чтение и шифрование блоками ...
Проблема Nonce при потоковом шифровании:
В режимах AES-GCM и ChaCha20 нельзя использовать один и тот же nonce (номер однократного использования) для разных блоков с одним ключом. Это критическая уязвимость. Решение в моем коде — деривация уникального nonce для каждого блока на основе базового nonce и индекса блока:
def _derive_block_nonce_12bit(base_nonce: bytes, block_index: int) -> bytes: # Первые 8 байт — префикс, последние 4 — счётчик блока prefix = base_nonce[:8] block_counter = block_index.to_bytes(4, byteorder='big') return prefix + block_counter
Это позволяет шифровать файлы любого размера, не нарушая криптографические стандарты.
3. Отказоустойчивость: что если выдернуть флешку?
Самый страшный сценарий для пользователя — потеря данных из-за сбоя во время шифрования. Стандартный подход «зашифровать -> удалить оригинал» здесь не работает.
Я внедрил систему блокировок и отката (rollback):
Lock-файл (
.encryption_lock.json): Перед началом операции создается файл, где записывается статусin_progressи список уже обработанных файлов.Временные файлы: Шифрование идет в
.tmpфайл. Только после успешной проверки целостности оригинал удаляется, а временный файл переименовывается.Проверка целостности: Перед удалением оригинала я расшифровываю блок данных обратно и сравниваю HMAC и SHA-256 хеши. Если не совпадает — оригинал не трогается.
Восстановление: Если процесс прервался, утилита видит lock-файл и предлагает откатить операцию (
rollback_operation), расшифровав уже обработанные файлы обратно.
4. Алгоритмы и производительность
Движок поддерживает три алгоритма:
AES-256-GCM: Стандарт индустрии, аппаратное ускорение на большинстве CPU.
ChaCha20-Poly1305: Быстрее на устройствах без AES-NI (например, некоторые ARM-процессоры).
XChaCha20-Poly1305: Увеличенный nonce (24 байта), что снижает риск коллизий при очень больших объемах данных.
Для ускорения работы с множеством мелких файлов реализована параллельная обработка через ThreadPoolExecutor. Однако из-за GIL в Python прирост заметен скорее на операциях I/O, чем на чистом шифровании.
5. Интерфейс и использование
Хотя ядро написано на Python, для пользователей доступен GUI, чтобы не запускать скрипты через консоль.
6. Ограничения и Threat Model
Важно понимать, для чего этот инструмент подходит, а для чего — нет.
Метаданные не скрыты: Имена файлов и структура папок сохраняются в
.usb_crypt_meta.json. Злоумышленник с доступом к флешке увидит список файлов, но не сможет их открыть. Скрыть имена файлов без создания контейнера технически сложно и неудобно для навигации.Защита от физической потери: Инструмент защищает данные, если вы потеряли флешку. Он не защищает от кейлоггеров на компьютере, где вы вводите пароль.
Парольная политика: Встроенная валидация требует минимум 12 символов, цифры и спецсимволы. Слабые пароли блокируются на уровне кода.



Заключение
Написание собственного крипто-движка — это всегда баланс между безопасностью и удобством. Я постарался сделать акцент на безопасности управления памятью (что редко встречается в Python-скриптах) и отказоустойчивости операций.
Проект открыт, код доступен для аудита. Если вы найдете уязвимости или способы оптимизировать SecureBytes — добро пожаловать в Issues.
Комментарии (11)

K0Jlya9
28.03.2026 12:52Почему не VeraCrypt?
Почему питон? Если это вайбкодинг то можно использовать нормальный язык, го например, там и бинарник нормальный будет собран, и GILа не будет, и с байтоблудием нормально всё.
Что вообще происходит, какая задача решается. Почему не Bitlocker/7zip.
Вставил флешку с какими то файлами, скопировал на нее свой бинарник(или 3 бинарника для разных ос?), запустил, ввел пароль и ждешь пока он зашифрует каждый файл по отдельности? Потом передал получателю который в своем компе так же расшифровал? Звучит как будто сову натянули на глобус.

roma1052
28.03.2026 12:52Интересный подход к реализации
SecureBytes. Работа с памятью в Python и GC — вечная головная боль для криптографических утилит, и использованиеbytearrayс многопроходной затиркой по NIST SP 800-88 — это зрелое решение для проекта на управляемом языке.Особенно зацепил момент с деривацией nonce для каждого блока. Часто в самописных движках об этом забывают, что превращает AES-GCM в решето.
Кстати, как исследователь в области квантовой криптографии (сейчас как раз пишу статью про симуляцию BB84), не могу не спросить: не планировали ли вы добавить поддержку алгоритмов, устойчивых к квантовым угрозам (Post-Quantum Cryptography)? Для флешек с долгим сроком хранения данных стратегия «Harvest now, decrypt later» становится вполне реальной угрозой.
В остальном — отличный инженерный разбор, особенно за систему rollback при сбоях. Удачи проекту!

slimeopus Автор
28.03.2026 12:52Спасибо за такой подробный и профессиональный разбор! Очень приятно, что оценили именно технические детали — работу с памятью и деривацию nonce.
Насчёт Post-Quantum Cryptography — отличный вопрос! Честно признаюсь, пока не углублялся в эту тему достаточно глубоко, чтобы грамотно реализовать. Но для флешек с долгосрочным хранением это действительно актуально, стратегия «Harvest now, decrypt later» — серьёзный аргумент.
Если получится найти время и разобраться с PQC-алгоритмами (скорее всего начну с Kyber/Dilithium из финалистов NIST), то обязательно добавлю поддержку. Возможно даже как опциональный режим для особо важных данных.
Кстати, если у вас есть рекомендации по библиотекам или материалам для старта — буду очень благодарен! Всегда интересно учиться у экспертов в области квантовой криптографии.
Ещё раз спасибо за комментарий и удачи со статьёй про BB84!

roma1052
28.03.2026 12:52Рад, что тема PQC отозвалась! Для старта с Kyber (ML-KEM) и Dilithium (ML-DSA) в экосистеме Python очень рекомендую заглянуть в сторону библиотеки liboqs (через их официальные python-врапперы). Это сейчас стандарт де-факто для экспериментов с алгоритмами, прошедшими отбор NIST.
Для вашего кейса с флешками стоит учесть пару нюансов:
Размер ключей: PQC-алгоритмы генерируют ключи и подписи значительно большего размера, чем классика. При потоковом шифровании это может потребовать пересмотра структуры метаданных.
Гибридный режим: Сейчас хорошим тоном считается использовать "комбинированное" шифрование (например, классический AES + постквантовый инкапсулятор). Это гарантирует, что даже если в новых алгоритмах NIST найдут уязвимость, данные останутся защищены классикой.
Для глубокого погружения также советую ресурс PQC-Zoo и документацию проекта Open Quantum Safe. Удачи с внедрением, будет интересно увидеть реализацию Kyber в вашем движке!

Mirron11
28.03.2026 12:52Добрый день! Прекрасно, что вы стараетесь разбраться, как использовать криптографию.
Увидел проблемы:
из-за разницы в использовании nonce в зависимости от размера файла и из-за использования AEAD (шифртекст больше открытого текста на размер тэга -- 16 Б), файл, зашифрованный в режиме non-streaming (размером (100 МБ - 8 Б)) будет расшифровываться в режиме streaming. Nonce при зашифровании и расшифровании не совпадет.
заявленная отказоустойчивость недействительна, потому что расшифрование файла зависит еще и от франимых в метаданных nonce, а файл с метаданными сохраняется после зашифрования всех файлов.
Предложил бы использовать AI в том числе в качестве ревьюера. Он нашел больше проблем, чем я увидел своими глазами, например:
зашифрование в поточном режиме не работает, потому что последовательно делаются два цикла
for chunk in reader.iter_chunks()(второй цикл сразу выйдет);verify несовместим со streaming: делает
cipher.decryptвсего файла, независимо от размера.

slimeopus Автор
28.03.2026 12:52Большое спасибо за внимательный код-ревью! Вы абсолютно правы - все найденные проблемы реальны и критичны:
Проблема с nonce - действительно, из-за AEAD-тега (16 байт) зашифрованный файл превышает порог и пытается расшифроваться в потоковом режиме с другими nonce. Это фатальная ошибка.
Двойной цикл - классическая ошибка: итератор уже исчерпан после первого прохода для вычисления хеша.
verify_encryption_integrity - полностью загружает файл в память, что противоречит идее потоковой обработки.
Отказоустойчивость - метаданные сохраняются после шифрования, поэтому при сбое между этими операциями данные теряются.
Это отличный пример того, почему криптографический код требует особенно тщательного ревью. Исправлю все замечания в следующей версии.
Еще раз спасибо за детальный анализ!
SkifDS
У меня доверия к флешкам с шифрованием не было, нет и не будет. Поясню. В одной компании, где я работал лет 10 назад, пришел перец в качестве комдира и сказал, а давайте мы купим флеху с аппаратным шифрованием и будем хранить на ней базу бухгалтерии. Гендир сказал: О, а давайте, круто же. Все возражения ИТ-отдела (в том числе, официальную служебку. Я ж знал, чем все кончится) послали в центр скульптурной композиции статуи Давида. В итоге флеха сдохла (производитель послал туда же, куда ранее послали ИТ отдел) через пару месяцев, а бакапов базы не было (ну а зачем тогда флеха-то?). И бухи с матом, воем и стоном восстанавливали базу из первички полгода.
Я это к тому, что флешка, имхо, изначально не надежный носитель данных. И если в обычных условиях с нее есть шансы хоть что-то выдернуть, то в случае с шифрованием будет полный тухляк.
tuxi
Если я правильно понял, тут решалась задача относительно безопасного способа передачи данных, а не хранения как единственный экземпляр. Так что, "почему бы и нет".
Valsha
Так "флешка" это вроде как в качестве примера, можно же использовать SSD винт внешний, у него же надежность будет явно выше, но это тоже вроде как считается "флешкой". Разве нет?