# QSafe Приложение для Android и Windows для хранения паролей с зашифрованной синхронизацией на собственный Raspberry Pi. Android 1.1.0, Windows 1.0.4, самостоятельная реализация. SafeInCloud используется только как источник XML-импорта; его закрытый формат `.db` не поддерживается. ## Windows Готовое приложение: `artifacts/QSafe-Windows-1.0.4/QSafe.exe`, переносимый архив: `artifacts/QSafe-Windows-1.0.4.zip`. Сборка x64 включает .NET и работает без установки. Есть тёмный интерфейс, поиск, категории, избранное, редактор карточек, корзина, генератор, XML-импорт, резервные копии и синхронизация с тем же Pi. Для существующей Android-базы выбрать **Подключить Pi и синхронизировать**, импортировать личный `connection.qsafe`, сохранить настройки и ввести существующий мастер-пароль. Для знакомства доступна отдельная демонстрационная база с вымышленными данными. Настоящая база находится в `%LOCALAPPDATA%\Kusoft\QSafe`; последние 10 зашифрованных сохранений остаются в `backups`. Автосинхронизация Windows работает при разблокированном хранилище раз в минуту только внутри домашнего Wi-Fi. Это же обязательное ограничение действует для ручной синхронизации и восстановления базы. Вне дома приложение работает автономно. Ключ подключения защищён Windows DPAPI, мастер-пароль не сохраняется. Вход по Windows Hello и автозаполнение браузеров пока отсутствуют. EXE не подписан. Подробности, сборка и проверки: [windows/README.md](windows/README.md). ## Начало работы 1. Установить `artifacts/QSafe-1.1.0.apk` на Android 9 или новее либо скачать версию 1.1.0 из [QSafe в Аргусе](https://argus.kusoft.xyz/?app=qsafe). Публичная загрузка проверяется по размеру, SHA-256 и подписи APK. Устанавливать поверх прежней версии, без удаления приложения и очистки данных. 2. На новом устройстве нажать **Подключить Pi и синхронизировать**, выбрать обновлённый личный файл `connection-lan.qsafe` и проверить локальный IP Pi, SSID и отпечаток сертификата. На уже настроенном Android импортировать его через настройки подключения. Старые файлы с внешним адресом базы больше не принимаются. Это ключ доступа к синхронизации. Не публиковать файл и не отправлять его в чат. 3. Ввести мастер-пароль существующей базы и нажать **Разблокировать**: зашифрованная база скачивается с Pi и проверяется на устройстве. Пока настройка не завершена, её можно повторить после перезапуска. Для пустого Pi в первом подключении выбрать **Создать новое хранилище** и задать пароль от 12 символов. 4. После создания или восстановления базы на стартовом экране остаются поле мастер-пароля и кнопка **Разблокировать**. При включённом входе по отпечатку системный запрос появляется автоматически; повторить его можно кнопкой разблокировки с пустым полем пароля. 5. Вход по отпечатку предлагается после входа мастер-паролем на устройстве с зарегистрированным отпечатком. Его также можно включить или выключить переключателем **☰ → Защита → Вход по отпечатку**. Изменение зарегистрированных отпечатков аннулирует биометрический ключ; мастер-пароль остаётся способом входа. ## Карточки и обновления Поле поиска и меню **☰** закреплены над списком. Карточки имеют локальные пиктограммы категорий; обращения к внешним сервисам иконок и передача доменов не выполняются. Постоянная кнопка **+** открывает выбор шаблона: сайт или приложение, банковская карта, электронная почта, Wi-Fi, сервер, документ, секретная заметка или своя карточка. На Android **☰** открывает полноценную страницу меню с разделами **Внешний вид**, **Синхронизация**, **Защита**, **Данные** и **Приложение**. Автосинхронизация, фоновая синхронизация, вход по отпечатку и автозаполнение имеют отдельные переключатели. Состояние автозаполнения берётся из Android; включение подтверждается в системных настройках, выключение отключает только службу QSafe. При выключении фоновой синхронизации отменяются её задачи и сетевые подписки, а новые события не ставят отключённую функцию в очередь. Подключение Pi, домашняя сеть, импорт и резервные копии открываются отдельными страницами. В настройках Pi пустое поле ключа сохраняет уже установленный ключ. **☰ → Внешний вид → Тема приложения** предлагает **Как в системе**, **Светлая** и **Тёмная**. Выбор применяется сразу и сохраняется на устройстве. Оформление охватывает список, карточки, формы, генератор и диалоги. В системном режиме смена темы Android не сбрасывает открытый редактор и его несохранённые данные. Просмотр и редактирование карточки открываются полноценными страницами. Нажатие на поле копирует его значение; секретные поля скрыты до нажатия на значок глаза. Кнопка назад возвращает к списку, а редактор предупреждает о несохранённых изменениях. Если синхронизация получила новую версию во время редактирования, сохраняются обе версии. Ручная синхронизация и блокировка находятся в меню **☰**; автоматическая синхронизация сохранена. **Сгенерировать пароль** в редакторе Android и Windows открывает отдельное защищённое окно с видимым результатом. Режимы: **Сложный** (16–64 символа, по умолчанию 24; специальные символы можно отключить), **Только цифры** (4–64 цифры, по умолчанию 12) и **Запоминающийся** (2–12 случайных английских слов, по умолчанию 8; цифры и специальные знаки включены по умолчанию и отключаются отдельно). Цифры и знаки размещаются случайно перед первым словом, между словами или после последнего слова; порядок цифр и знаков также случаен. Слова остаются целыми и начинаются с заглавной буквы. Примеры: `57+LemonRiver`, `Lemon7+River5`, `LemonRiver+57`. В последнем режиме длина выбирается числом слов; рядом показано итоговое число символов. Длина запоминается отдельно для каждого режима до закрытия окна. Изменение настроек и **Другой вариант** обновляют предпросмотр; только **Использовать пароль** заменяет поле карточки. Отмена сохраняет прежнее значение. Результат не копируется в буфер автоматически. Слова и цифры выбираются криптографическим генератором случайных чисел; общий встроенный список содержит 256 слов и не требует сети. **☰ → Проверить обновления** проверяет stable-релиз в Аргусе по HTTPS. Перед установкой проверяются размер и SHA-256 загрузки, имя пакета, числовой код версии и совпадение сертификата подписи с установленным приложением. APK хранится во внутреннем кэше; установщику передаётся доступ только к этому файлу. При необходимости Android предлагает разрешить установку обновлений из QSafe, затем показывает обычное подтверждение установки. Мастер-пароль и база в каталог обновлений не передаются. ## Домашний Wi-Fi Открыть **☰ → Синхронизация → Домашний Wi-Fi**, указать точное имя домашней сети (SSID) и сохранить. Фоновая работа включается отдельным переключателем **Фоновая синхронизация** в разделе синхронизации. При нехватке параметров или разрешений открывается страница настройки сети. Для владельца этого Pi домашняя сеть — `gen`; личный файл подключения может передать эту настройку автоматически. В публичном APK имя сети и ключ подключения не зашиты. Чтобы Android разрешал читать SSID при закрытом приложении, нужны точный доступ к местоположению, системная геолокация и на Android 10+ разрешение местоположения **«Всегда»**. Приложение показывает недостающие разрешения и кнопки перехода к настройкам. Координаты не запрашиваются, история перемещений не ведётся. При неизвестном SSID, недостающих разрешениях или подключении к другой сети фоновая синхронизация не выполняется. Подключение к Wi-Fi отслеживается системным `ConnectivityManager`: живой callback обрабатывает повторные подключения, а `PendingIntent` позволяет принять событие после завершения процесса. После потери сети ожидание события регистрируется заново. Передача запускается отдельной задачей `JobScheduler` и привязывается к Wi-Fi-соединению, поэтому не переходит на мобильный интернет при обрыве Wi-Fi. Резервная периодическая задача раз в 15 минут восстанавливает работу при пропущенных событиях; экономия энергии и ограничения производителя могут задержать запуск. После принудительной остановки приложения в системных настройках его нужно открыть заново. Заблокированное приложение не читает мастер-ключ и открытые пароли. Оно отправляет только готовую зашифрованную базу с условием совпадения известной серверной версии. Изменённая копия Pi загружается в отдельный файл `pending.qsafe`, не заменяя доверенную локальную базу. При следующей разблокировке проверяются GCM-тег и принадлежность хранилищу, затем выполняется объединение. Повреждённая копия не заменяет локальные данные. Конфликтующие изменения сохраняются обеими версиями; окончательное объединение требует входа в хранилище. Все подключения к базе — автоматические, ручные и восстановление — требуют домашний Wi-Fi, локальный IPv4 Pi в той же подсети и закреплённый сертификат. Отключение фоновых задач не отменяет это ограничение. Интернет в домашней сети для базы не требуется. Обновления Android через Аргус остаются отдельным разрешённым внешним каналом. ## Перенос из SafeInCloud В SafeInCloud для Windows открыть **Файл → Экспорт → XML**. Если установленная мобильная версия предлагает XML-экспорт, можно использовать его. Скопировать XML на телефон, открыть в QSafe **Настройки → Импорт из SafeInCloud (XML)** и проверить число найденных карточек перед подтверждением. Переносятся все поля с типами и значениями, заметки, метки и избранное. Удалённые карточки и шаблоны пропускаются с явным счётчиком. Повторный импорт неизменённых карточек не создаёт дублей. Изменённая после предыдущего импорта карточка будет добавлена отдельной копией. Исходный XML каждой карточки, включая вложения и дополнительные данные, сохраняется внутри зашифрованной базы. Просмотр вложений и истории в интерфейсе первой версии не реализован. XML-экспорт содержит **незашифрованные пароли**. QSafe читает его локально и шифрует перед сохранением; сервер получает только шифротекст. После сверки количества карточек и нескольких записей удалить открытый экспорт с устройств. Сохранить исходную базу SafeInCloud до завершения проверки. Фактический перенос пользовательских паролей требует файла экспорта и подтверждения импорта в приложении; автоматического доступа к SafeInCloud нет. ## Защита и синхронизация - AES-256-GCM, случайный 96-битный nonce для каждого сохранения; заголовок файла включён в аутентифицируемые данные. - PBKDF2-HMAC-SHA256, 600 000 итераций, случайная 128-битная соль. Используются стандартные криптографические API Java/Android. Ограничения размера и параметров проверяются до выработки ключа. - Мастер-пароль и ключ базы не записываются на диск и не передаются серверу. В оперативной памяти расшифрованные данные доступны во время работы; гарантировать стирание всех копий строк в управляемой памяти Android нельзя. - Ключ подключения к Pi зашифрован ключом Android Keystore. Вход по биометрии отдельно оборачивает ключ базы аппаратным/системным ключом с обязательной сильной биометрической авторизацией. Мастер-пароль остаётся способом восстановления. - Блокировка при выходе из приложения и через 2 минуты бездействия. Все страницы и диалоги QSafe защищены `FLAG_SECURE` до показа окна; снимки в недавних приложениях также отключены на Android 13+. Содержимое форм не сохраняется в состоянии Activity. Буфер обмена помечается как чувствительный и очищается через 30 секунд, если приложение ещё работает и содержимое не заменено другим приложением. - Системные резервные копии/перенос данных Android отключены. Доступен ручной экспорт зашифрованного `.qsafe` и восстановление с проверкой мастер-пароля. - Для базы HTTPS проверяет IP, срок действия и закреплённый SHA-256 сертификата Pi; DNS, прокси, редиректы и сетевые загрузки сертификатов не используются. Незащищённый HTTP запрещён. Обновления APK через Аргус используют обычную системную проверку HTTPS и проверку подписи пакета. - Pi хранит только аутентифицированный зашифрованный файл, его SHA-256 и предыдущие версии. Сервер технически не может проверить GCM-тег без клиентского ключа; целостность проверяет клиент. - `ETag` и обязательные `If-Match`/`If-None-Match` предотвращают молчаливое затирание конкурентных записей. Клиент сравнивает общую исходную версию; независимые изменения объединяются, конкурентные правки сохраняются отдельными карточками. Конфликт удаления с редактированием сохраняет отредактированную карточку с пометкой. - На телефоне остаются 10 предыдущих зашифрованных копий. На Pi — до 100 предыдущих версий с ограничением 256 МиБ. Ежедневная проверенная копия SQLite хранится отдельно на SD-карте: до 30 копий и 512 МиБ. Обе копии находятся на одном Pi; это не защита от потери всего устройства. Приложение не проходило независимый аудит безопасности. Оно не заявляет полного функционального соответствия SafeInCloud. Смена мастер-пароля, passkeys и генерация TOTP пока не реализованы. ## Автозаполнение Включается пользователем в **☰ → Защита → Автозаполнение** с подтверждением Android. Тем же переключателем служба QSafe выключается. Для каждого запроса требуется разблокировка и подтверждение выбранной карточки с показом получателя. Поддерживаются приложения с подсказками Android `username`/`emailAddress`/`password`. Для веб-сайтов поддерживается только выбранный по умолчанию браузер, HTTPS и точное совпадение домена с полем сайта в карточке. Неоднозначные домены и другие браузеры отклоняются. Автоматическое сохранение новых паролей из сторонних приложений пока не реализовано. Совместимость зависит от конкретного приложения/браузера. ## Pi Сервер базы: `https://192.168.0.185:18443/qsafe/`, health: `/health`, база: `/v1/vault`. Caddy слушает только `192.168.0.185:18443` и принимает клиентов из `192.168.0.0/24`; бэкенд остаётся на `127.0.0.1:18792`. Публичный путь `/qsafe/*` на Аргусе возвращает 404. Каталог и загрузка обновлений Android через Аргус сохранены. Доступ к базе — по случайному 256-битному Bearer-ключу, сервер хранит только его SHA-256. APK ключей подключения, домашнего SSID и отпечатка пользовательского сертификата не содержит. Локальный TLS и закрытие публичного маршрута настраивает `deploy/lan-only.py --ip 192.168.0.185`; откат конфигурации сохраняется в `/var/backups/qsafe-lan/`. Личный `.private/connection-lan.qsafe` содержит локальный URL, прежний ключ доступа, `homeWifiSsid` и `certificateSha256` и не входит в APK/ZIP. Файлы базы и истории при переносе маршрута не изменяются. | Назначение | Путь / служба | |---|---| | Приложение сервера | `/opt/qsafe/server.py` | | Зашифрованная база и история | `/mnt/data/qsafe/data/vault.sqlite3` | | Хеш ключа подключения | `/etc/qsafe/token.sha256` | | Служба | `qsafe.service` | | Резервные копии на SD-карте | `/var/backups/qsafe` | | Таймер | `qsafe-backup.timer`, ежедневно около 04:25 по времени Pi | | Откат Caddy | `/var/backups/qsafe-install` | Сервер запускается от отдельного пользователя `qsafe`, запись разрешена только в каталог базы; требуется смонтированный внешний диск. Для остановки: `sudo systemctl disable --now qsafe.service qsafe-backup.timer`. Данные при этом сохраняются. Для восстановления старой версии файла из SQLite следует сначала остановить клиентов и службу, сохранить текущую базу, затем выгрузить выбранное поле `history.body` в `.qsafe` и восстановить его через приложение. Никогда не редактировать зашифрованное содержимое вручную. ## Сборка и проверка JDK 17, Android SDK 35, Gradle 8.14. В Windows при пути с кириллицей использовать временный диск, например `subst R: "путь-к-проекту"`, затем выполнять Gradle из `R:\`. Настроить `local.properties` с путём к Android SDK. ```powershell .\gradlew.bat :app:testDebugUnitTest :app:assembleDebug :app:assembleDebugAndroidTest :app:lintDebug :app:assembleRelease --no-daemon ``` Подпись релиза использует `.private/signing.properties` и личный `.p12`; ключи, пароли, соединения и локальные настройки исключены из Git. Ключ подписи необходимо сохранить для дальнейших обновлений. Тесты JVM проверяют шифрование, неверный пароль, повреждение, генератор, импорт и конфликты. Серверные тесты: `python3 -m unittest -v test_server` из `server/`. Android-тесты используют отдельный временный HTTPS-сервер и синтетические пароли; рабочая база пользователя в них не участвует. Файлы `deploy/qa-start.sh` и `deploy/qa-stop.sh` включают и выключают только временный маршрут проверки. Источники решений: [Android Cryptography](https://developer.android.com/privacy-and-security/cryptography), [Android Autofill](https://developer.android.com/identity/autofill/autofill-services), [OWASP PBKDF2](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html), [описание XML SafeInCloud от автора парсера](https://github.com/eduncan911/safeincloud). Фоновая сеть: [ConnectivityManager и PendingIntent](https://developer.android.com/reference/android/net/ConnectivityManager), [доступ к SSID в WifiInfo](https://developer.android.com/reference/android/net/wifi/WifiInfo), [JobInfo](https://developer.android.com/reference/android/app/job/JobInfo.Builder).