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.

Начало работы

  1. Установить artifacts/QSafe-1.1.0.apk на Android 9 или новее либо скачать версию 1.1.0 из 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.

.\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, Android Autofill, OWASP PBKDF2, описание XML SafeInCloud от автора парсера.

Фоновая сеть: ConnectivityManager и PendingIntent, доступ к SSID в WifiInfo, JobInfo.

S
Description
No description provided
Readme
234 KiB
Languages
Java 59.6%
C# 32.1%
Python 6.5%
Shell 1%
PowerShell 0.8%