Парольная политика (ADR-013) и привязка экспорта к аккаунту (ADR-014)

ADR-013: PBKDF2-HMAC-SHA256 ≥600k итераций (цель 1M), параметры KDF
версионируются рядом с блобом; пароль от 12 символов, длина важнее
состава. ADR-014: парольная фраза экспорта отменена, ключ экспорта —
HKDF от 32-байтового секрета аккаунта из ключевого блоба; архив .bare
криптографически бесполезен вне аккаунта-владельца. Перекрёстно
обновлены ADR-006, ADR-010, architecture, threat-model; в открытые
вопросы добавлен серверный «перец» для блоба.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SW4ZXtKn5NjX8NH3Lhm2ND
This commit is contained in:
Claude
2026-08-20 09:41:33 +00:00
co-authored by Claude Fable 5
parent 700256c8ef
commit b9bf4ea04e
7 changed files with 55 additions and 5 deletions
+6 -2
View File
@@ -14,11 +14,13 @@ Bare — это PWA-клиент на ванильных веб-технолог
Серверная аутентификация: Argon2id, сессия в httpOnly cookie.
Пароль — не короче 12 символов; правил про регистры и спецсимволы нет: длина важнее состава. UI рекомендует парольную фразу из нескольких слов и при регистрации прямо говорит: пароль — это ключ шифрования, а не запись в базе; восстановления нет.
## E2EE
Вся клиентская криптография — WebCrypto, без крипто-библиотек.
Идентичность пользователя — ECDH-пара (P-256). Приватный ключ шифруется ключом, выведенным из пароля (PBKDF2), и хранится на сервере как блоб — сервер видит только шифротекст. Отсюда два следствия. Сброс пароля невозможен by design. Мультидевайс-вход прост: новый девайс вводит пароль, скачивает блоб, расшифровывает ключ.
Идентичность пользователя — ECDH-пара (P-256). Приватный ключ шифруется ключом, выведенным из пароля (PBKDF2-HMAC-SHA256, не менее 600 000 итераций, целевое значение — 1 000 000), и хранится на сервере как блоб — сервер видит только шифротекст. Параметры KDF лежат рядом с блобом и читаются клиентом при входе: их можно повышать без миграции всех аккаунтов разом. Рядом с приватным ключом в блобе живёт случайный 32-байтовый секрет аккаунта — из него выводятся ключи экспорта истории. Отсюда два следствия. Сброс пароля невозможен by design. Мультидевайс-вход прост: новый девайс вводит пароль, скачивает блоб, расшифровывает ключ.
Чаты 1:1: ECDH shared secret → AES-GCM.
@@ -36,7 +38,9 @@ Forward secrecy — осознанный non-goal v1.
## Экспорт и импорт истории
Экспорт: вся локальная история сериализуется, шифруется отдельной парольной фразой (запрашивается в момент экспорта) и сохраняется одним файлом `.bare`. Импорт: файл плюс фраза, merge в IndexedDB по messageId, идемпотентно — повторный импорт и склейка истории с двух устройств не создают дублей.
Экспорт: вся локальная история сериализуется, шифруется и сохраняется одним файлом `.bare`. Ключ экспорта выводится из секрета аккаунта: HKDF(секрет, случайный salt, info="bare-export-v1") → AES-GCM, всё на WebCrypto. Отдельной парольной фразы нет: архив криптографически привязан к аккаунту и вне его бесполезен — у чужого клиента нет секрета аккаунта, расшифровка невозможна в принципе.
Заголовок файла открытый: magic, версия формата, salt, отпечаток публичного ключа владельца. Ника в заголовке нет — лишняя утечка. Импорт: клиент сверяет отпечаток со своим (при несовпадении — «архив создан другим аккаунтом», сверка — UX-вежливость, не защита) и делает идемпотентный merge в IndexedDB по messageId — повторный импорт и склейка истории с двух устройств не создают дублей.
Это единственный механизм переноса истории между устройствами. Осознанно.
+2 -2
View File
@@ -6,11 +6,11 @@
## Решение
Только WebCrypto. Идентичность пользователя — ECDH-пара P-256. Приватный ключ шифруется ключом, выведенным из пароля (PBKDF2 — единственный KDF в WebCrypto), и хранится на сервере как блоб. Чаты 1:1: ECDH shared secret → AES-GCM.
Только WebCrypto. Идентичность пользователя — ECDH-пара P-256. Приватный ключ шифруется ключом, выведенным из пароля (PBKDF2 — единственный KDF в WebCrypto; параметры и парольная политика — ADR-013), и хранится на сервере как блоб. Рядом с приватным ключом в блобе — секрет аккаунта для экспорта истории (ADR-014). Чаты 1:1: ECDH shared secret → AES-GCM.
## Следствия
- Сервер видит только шифротекст — и блоба, и сообщений.
- Сброс пароля невозможен by design: пароль — материал ключа. Забыл — значит забыл.
- Мультидевайс-вход: новый девайс вводит пароль, скачивает блоб, расшифровывает ключ.
- Стойкость блоба к оффлайн-перебору равна стойкости пароля.
- Стойкость блоба к оффлайн-перебору равна стойкости пароля. Контрмеры — ADR-013.
+2
View File
@@ -1,5 +1,7 @@
# ADR-010: Перенос истории — только ручной экспорт/импорт
Частично заменён [ADR-014](014-export-account-binding.md): парольная фраза экспорта отменена, ключ экспорта выводится из секрета аккаунта. Принцип «перенос — только ручной экспорт/импорт» в силе.
## Контекст
История живёт на устройстве (ADR-009), но людям нужны перенос на новое устройство и склейка истории с нескольких. Облачная синхронизация сделала бы сервер архивом — non-goal.
+18
View File
@@ -0,0 +1,18 @@
# ADR-013: Парольная политика и параметры KDF
## Контекст
Ключевой блоб лежит на сервере, и его стойкость к оффлайн-перебору равна стойкости пароля (ADR-006). Раз пароль — материал ключа, его качество и цена перебора — не UX-детали, а параметры криптосистемы.
## Решение
- KDF: PBKDF2-HMAC-SHA256, не менее 600 000 итераций, целевое значение — 1 000 000.
- Число итераций (поле версии KDF) хранится рядом с блобом; при входе клиент читает параметры из блоба. Так итерации можно повышать в будущем без миграции всех аккаунтов разом.
- Минимальная длина пароля — 12 символов. Правил про регистры и спецсимволы нет: длина важнее состава. UI рекомендует парольную фразу из нескольких слов.
- UI при регистрации явно проговаривает: пароль — это ключ шифрования, а не запись в базе; восстановления нет.
## Следствия
- Гарантия «оператор не читает сообщения» действует в пределах стойкости пароля пользователя; слабый пароль — слабое E2EE. Это осознанная цена парольного мультидевайса, зафиксировано в threat-model.
- Перебор дорожает на порядки, но не отменяется: словарный пароль умирает при любых итерациях.
- Параметры повышаются со временем: новые аккаунты получают их сразу, существующие — перешифровкой блоба при очередном входе.
@@ -0,0 +1,23 @@
# ADR-014: Экспорт привязан к аккаунту криптографически
Заменяет часть [ADR-010](010-export-import.md): парольной фразы экспорта больше нет. Принцип «перенос истории — только ручной экспорт/импорт» в силе.
## Контекст
Экспортный файл, зашифрованный отдельной парольной фразой, переносим кем угодно: файл плюс фраза — и чужой клиент импортирует чужую историю. Требование: архив должен быть бесполезен вне аккаунта-владельца. Проверка на уровне «клиент сверяет ник» неприемлема — клиент открытый, такая проверка тривиально выпиливается. Запрет должен быть криптографическим.
## Решение
- В ключевом блобе аккаунта, рядом с приватным ECDH-ключом, хранится случайный 32-байтовый секрет аккаунта. Он генерируется при регистрации, доступен любому устройству, вошедшему в аккаунт, и никогда не существует в открытом виде вне клиента.
- Ключ конкретного экспорта: HKDF(секрет аккаунта, salt, info="bare-export-v1"), salt случайный на каждый экспорт. Шифрование — AES-GCM. Всё на WebCrypto: HKDF там есть нативно.
- Отдельной парольной фразы для экспорта больше нет — сущность удалена.
- Формат `.bare`: заголовок открытым текстом — magic, версия формата, salt, отпечаток публичного ключа владельца; далее шифротекст. Ник владельца в заголовок не пишется — лишняя утечка.
- Импорт: клиент сверяет отпечаток со своим; при несовпадении показывает «архив создан другим аккаунтом» и не пытается расшифровать. Сверка — только UX-вежливость: реальная защита в том, что у чужого клиента нет секрета аккаунта и расшифровка невозможна в принципе.
- Импорт по-прежнему идемпотентный merge по messageId.
## Следствия
- Архив открывается только устройствами, вошедшими в аккаунт-владелец. Потеря пароля — потеря ключей — архив нечитаем навсегда. Это последовательное продолжение принципа «забыл — значит забыл», а не новое ограничение.
- Смена ключевой пары (например, перерегистрация под тем же ником) делает старые архивы нечитаемыми: секрет аккаунта другой.
- Привязка защищает файл, а не содержимое от самого владельца: скриншоты и модифицированный клиент никуда не деваются, а у собеседников есть собственные копии тех же сообщений. «Аналоговая дыра» криптографией не закрывается — зафиксировано в threat-model.
- Побочный плюс: исключено загрязнение чужого клиента историей чатов, в которых пользователь не участник.
+1
View File
@@ -7,5 +7,6 @@
- Лимиты: длина сообщения, rate limiting, антиспам.
- Смена пароля (= перешифровка ключевого блоба): в v1 или позже?
- Идентификация устройства для per-device очередей.
- Серверный «перец» для ключевого блоба: дополнительное шифрование блоба серверным ключом, хранящимся вне базы. Плюс: дамп базы сам по себе перестаёт быть материалом для оффлайн-перебора. Минус: не защищает от оператора; потеря серверного ключа — невозможность входа с новых устройств для всех. Решение отложено.
- Язык интерфейса (ru/en); нужна ли i18n.
- Визуальная айдентика: отдельный бриф будет добавлен в `docs/identity/`.
+3 -1
View File
@@ -24,10 +24,12 @@
**Компрометация устройства.** История лежит на устройстве в открытом виде (IndexedDB). Доступ к устройству — доступ к истории. Защита устройства — зона ответственности пользователя и ОС.
**Слабый пароль.** Пароль — материал ключа. Ключевой блоб хранится на сервере, и его стойкость к оффлайн-перебору равна стойкости пароля. Слабый пароль ослабляет и вход, и шифрование.
**Слабый пароль.** Пароль — материал ключа. Ключевой блоб хранится на сервере, и его стойкость к оффлайн-перебору равна стойкости пароля. Гарантия «оператор не читает сообщения» действует в пределах стойкости пароля пользователя: слабый пароль слабое E2EE. Это осознанная цена парольного мультидевайса. Контрмеры (ADR-013): PBKDF2-HMAC-SHA256 с не менее чем 600 000 итераций, пароль от 12 символов, рекомендация парольной фразы в UI.
**Собеседник.** E2EE не защищает от участника чата: получатель может сохранить, переслать, сфотографировать. «Удалить у всех» после доставки не существует.
**Сам владелец истории.** Привязка экспорта к аккаунту (ADR-014) защищает файл, а не содержимое от владельца. Пользователь, желающий отдать переписку третьему лицу, сделает это скриншотами или модифицированным клиентом — «аналоговая дыра» криптографией не закрывается. К тому же у собеседников есть собственные копии тех же сообщений. Смысл привязки: перемещённый — случайно или злонамеренно — файл `.bare` бесполезен для посторонних.
## Осознанные пределы v1
**Forward secrecy отсутствует.** Компрометация приватного ключа пользователя раскрывает ранее записанные атакующим шифротексты его чатов 1:1. Осознанный non-goal v1.