Як я зробив E2E encrypted файлообмін на Python — і що з цього вийшло
Привіт, DOU! Хочу розповісти про свій пет-проєкт — SecureShare. Це десктопний застосунок для прямої передачі файлів між двома комп’ютерами з наскрізним шифруванням.
Стаття не про те, який я молодець, а скоріше про технічні рішення, граблі, і чому деякі речі зроблені саме так. Буду радий почути думки і критику.
Проблема
Мені треба було відправити конфіденційний документ людині в іншому місті. Варіанти:
- Google Drive / Dropbox — файл лежить на чужих серверах, компанія має доступ
- Telegram — зручно, але файл зберігається на серверах, і «секретні чати» не працюють для файлів нормально
- Email — без коментарів
- WeTransfer — файл тимчасово зберігається на їхніх серверах
Всі ці варіанти мають спільну проблему: мій файл потрапляє на чийсь сервер, і я не контролюю, що з ним буде далі.
Я хотів просту штуку: відправити файл напряму, щоб він був зашифрований на моєму пристрої і розшифрований тільки на пристрої отримувача. Без реєстрацій, без акаунтів.
Як це працює
Архітектура досить проста:
Відправник ←—WSS—→ Relay-сервер ←—WSS—→ Отримувач
- Обидва клієнти підключаються до relay-сервера через WebSocket (TLS)
- Відбувається обмін ключами X25519 (Diffie-Hellman на еліптичних кривих)
- Обидва бачать код верифікації — як у Signal, щоб переконатися, що ніхто не втрутився
- Файл шифрується AES-256-GCM і передається чанками
- В кінці — перевірка SHA-256 хешу
Relay-сервер — це буквально труба. Він отримує зашифровані байти від відправника і передає їх отримувачу. Сервер не знає ні імені файлу, ні його вмісту, ні навіть розміру (бо метадані теж зашифровані).
Чому саме так
X25519 + AES-256-GCM
Тут я довго думав. Є XChaCha20-Poly1305, який часто рекомендують для нових проєктів — він простіший і менш чутливий до помилок з nonce. Але я зупинився на AES-256-GCM з кількох причин:
- Широка підтримка в бібліотеці
cryptography(Python) - Апаратне прискорення AES-NI на більшості процесорів
- Достатньо безпечний при правильному управлінні nonce
Для захисту від повторного використання nonce зробив систему з префіксами: кожна сторона отримує свій
Чесно кажучи, якби починав зараз — можливо, обрав би XChaCha20. Менше головного болю з nonce. Але AES-GCM працює, і поки що міняти не планую.
Код верифікації
Одна з речей, яка мене бісила в інших рішеннях — відсутність захисту від MITM. Якщо relay-сервер зламали, він може підмінити ключі. Тому обидва користувачі бачать короткий код (перші 8 символів SHA-256 від shared secret), і мають його порівняти. Як у Signal, коли ви скануєте QR-код контакту.
Це не ідеально — користувачі можуть ігнорувати верифікацію. Але хоча б механізм є.
WebSocket relay замість P2P
Перша версія намагалася працювати peer-to-peer через MQTT. Це був кошмар: NAT traversal, STUN/TURN, файрволи... В кінці я здався і зробив простий WebSocket relay на VPS. Так, це додає точку відмови, але:
- Працює через будь-який NAT і файрвол
- Не потрібна конфігурація мережі
- Relay можна self-hosted — підняти свій за 5 хвилин через Docker
# docker-compose.yml — весь сервер services: relay: build: . restart: unless-stopped caddy: image: caddy:2 ports: ["80:80", "443:443"] volumes: - ./Caddyfile:/etc/caddy/Caddyfile
Auto-reconnect і resume
Це фіча, яку я не планував, але яка виявилася критичною. Уявіть: передаєте файл на 3 ГБ, і на 80% Wi-Fi відвалюється на 10 секунд.
Без resume — починай спочатку. З resume — клієнт автоматично перепідключається, повторно обмінюється ключами, і продовжує з місця зупинки. Отримувач зберігає .resume маніфест з інформацією про отримані чанки.
Це було складно зробити правильно. Особливо момент з тим, що при реконнекті потрібен новий key exchange (бо з’єднання нове), але верифікацію можна пропустити (бо reconnect_token підтверджує ідентичність).
Стек
| Компонент | Технологія | |-----------|-----------| | Клієнт | Python + CustomTkinter | | Шифрування | cryptography (X25519, AES-256-GCM, HKDF) | | Relay-сервер | Python asyncio + websockets | | TLS | Caddy + Let’s Encrypt | | DNS | DuckDNS | | Збірка | PyInstaller (.exe / Linux binary) |
Чому Python? Бо це пет-проєкт, і я хотів швидко. CustomTkinter дає нормально виглядаючий GUI з мінімумом коду. Так, .exe виходить ~30 МБ (PyInstaller), і стартує не миттєво. Якби це був комерційний продукт — обрав би щось інше.
Що не подобається
Буду чесний — є речі, які мене самого дратують:
- Один файл за сесію. Хочеш відправити 5 файлів — запакуй в архів. Це обмеження протоколу, і міняти його — це по суті переписати transfer logic
- Немає macOS збірки. PyInstaller + CustomTkinter + macOS = страждання. Запускається з сирців, але .app не збираю
- 5 ГБ ліміт. Це серверне обмеження щоб один користувач не забив канал. Для більшості задач вистачає, але розумію що не для всіх
- GUI міг бути кращим. CustomTkinter — це компроміс. Виглядає нормально, але це не Electron і не нативний UI
Як виглядає
Інтерфейс підтримує три мови (українська, англійська, німецька) з живим перемиканням без перезапуску. Dark theme за замовчуванням.
Ось реальна передача файлу на 2.9 ГБ між двома пристроями:



Відправник генерує код сесії(ooxv-zhef), ділиться ним з отримувачем. Після підключення обидва бачать код верифікації (FD79-FDBB) — якщо збігається, значить ніхто не втрутився. Далі файл летить зашифрованим потоком зі швидкістю ~15 МБ/с.
Внизу вікна — лог з усіма етапами: хеш файлу, підключення до relay, обмін ключами, верифікація, передача. Повна прозорість того, що відбувається.
Що далі
Є кілька ідей, але не впевнений в пріоритетах:
- Веб-версія (щоб не качати .exe)
- Підтримка передачі папок
- Покращення UX верифікації (може QR-код?)
- macOS збірка
Якщо у вас є думки, що з цього найважливіше — буду радий почути.
Лінки
- Сайт: secureshare-relay.duckdns.org
- GitHub: github.com/artmarchenko/SecureShare
- Ліцензія: MIT
Буду вдячний за будь-який фідбек — особливо по частині криптографії і архітектури. Якщо бачите дірки в security model — кажіть, це найцінніше.
Дякую що прочитали!
18 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів