Дежурный админ отдаёт коллеге пароль от базы, API-ключ или код восстановления — и почти всегда вставляет строку в Telegram, Slack или корпоративную почту. Сообщение синхронизируется на все устройства, ищется по слову и через полгода всё ещё лежит в истории. Когда вместо «просто вставь в чат» берут «одноразовую ссылку», ключ расшифровки часто пишут как ?key= сразу после идентификатора. Тогда сервер, обратный прокси и журнал доступа видят ключ. Шифрование сделано наполовину.

Здесь нет инструкции «какие кнопки нажать, чтобы получить самоуничтожающуюся ссылку» — это страница инструмента. Вопрос один: когда пароль нужно отдать коллеге один раз, почему ключ ставят после # в адресе, чего из-за этого не видит сервер и где эта защита кончается. В предыдущей заметке разобрали, как во вкладке «Сеть» проверить, что открытый текст не ушёл. Здесь та же панель отвечает на другой объект: в каком куске URL живёт ключ. Одноразовая ссылка MyPassGen держит эту границу: AES-256-GCM считают в браузере, сервер временно хранит только шифротекст, ключ стоит после #, создать и открыть можно без аккаунта.

Сначала разделите адрес на два объекта

После вопросительного знака ? идёт query: он входит в строку HTTP-запроса и попадает в журнал на той стороне. После решётки # — фрагмент: протокол оставляет его клиенту и по умолчанию не отправляет с запросом. Ключ в неправильном куске — и любое «сервер ничего не знает» уже не держится.

Три способа отдать один пароль

Возьмите одну одноразовую тестовую фразу orange-lake-7. Её можно отправить минимум тремя путями. Разница не в слогане на странице. Разница в том, стал ли открытый текст шифротекстом до отправки, попал ли ключ в HTTP и можно ли через полгода найти исходник поиском по чату.

Способ Что получает хост или мессенджер Что можно найти позже
Вставить пароль прямо в чат Весь открытый текст Искомый исходник
Зашифрованная ссылка, ключ как ?key= Шифротекст и ключ (оба в запросе) Ключ в логах доступа; полный URL в чате
Зашифрованная ссылка, ключ после # Сервер видит только номер шифротекста; мессенджер может сохранить адрес целиком В логах сервера ключа нет; в истории чата полная ссылка может остаться

Третья строка отвечает только на вопрос «машина, которая хранит шифротекст, не должна получить ключ». Она не отвечает «сохранит ли мессенджер весь адрес». Полная ссылка — это допуск: кто скопировал адрес с #, тот расшифрует во вкладке. Ключ после решётки решает доверие к серверу. Пересланную ссылку он не лечит.

По этому пути имеет смысл гонять короткий текст. В MyPassGen одна заметка — не больше 32 КБ: пароль, кусок ключа, короткая инструкция. Пакет сертификатов, выгрузка таблицы или что-то крупнее этого лимита лучше отдать в шифрование файлов: на этом устройстве получить .lock / .enc, а пароль отправить другим каналом. Файловый случай разобран в заметке кто увидит открытый текст до загрузки в облако.

Что HTTP реально отправляет

RFC 3986 §3.5 называет кусок после # идентификатором фрагмента: он указывает вторичное место, которое клиент разбирает уже после того, как пришёл основной ресурс. Браузер сначала забирает страницу по пути и query. Когда документ на месте, фрагмент решает, куда прокрутить, или его забирает скрипт страницы. Самому HTTP-запросу этот кусок не нужен.

У действующей семантики HTTP формулировка жёстче. RFC 9110 §7.1 пишет: целевой URI исключает компонент фрагмента из ссылки, потому что фрагмент оставлен клиенту. Законная origin-form в строке запроса — путь плюс необязательный query. Производства с # там нет. В адресной строке вы видите s.html?id=abc#ключ. Документный запрос, который уходит из вкладки, должен быть GET /ru/s.html?id=abc. Хвост после решётки остаётся на этой машине.

Заголовок Referer тоже снимает фрагмент. MDN Referer прямо говорит: в заголовке могут быть источник, путь и query; фрагмент URL и пару логин/пароль туда класть нельзя. В W3C Referrer Policy на шаге «очистить перед отправкой как referrer» фрагмент тоже обнуляется. Значит, «ключ после # не уходит по HTTP на машину с шифротекстом» — поведение протокола, а не обещание конкретного сайта.

Вопросительный знак и решётка не взаимозаменяемы

По стандарту URL WHATWG, при открытии http / https кусок после ? входит в строку запроса. Напишите s.html?id=abc&key=ключ — ключ появится в журналах доступа, на обратном прокси и в части записей CDN. Какие метки снимать с рекламной ссылки — другой вопрос, см. какие параметры URL снимать при пересылке. Ключ расшифровки в query класть нельзя.

Есть ещё ловушка кодирования. %23 — это процентная запись символа #. Написанное в пути или в query %23 уйдёт на сервер и после декодирования станет буквальной решёткой. Это не фрагмент. Ключ должен стоять после незакодированной # в адресной строке, а не после %23, засунутого в путь.

Чего нет в логах сервера

Полная одноразовая передача делится на два шага. При создании вкладка генерирует случайный ключ на 32 байта, шифрует открытый текст AES-256-GCM (IV 12 байт, лежит рядом с шифротекстом) и POST-ит шифротекст, срок жизни и число прочтений. При чтении скрипт берёт ключ из location.hash, у сервера запрашивает только шифротекст и расшифровывает на этом устройстве. Вид ссылки — s.html?id={id}#{key}: в query только номер, ключ только после решётки.

NIST SP 800-38D задаёт GCM как аутентифицированное шифрование: если шифротекст поменяли, расшифровка должна упасть, а не выдать кашу, которую можно принять за текст. В RFC 5116 AEAD_AES_256_GCM — ключ 32 байта, nonce 12 байт, тег подлинности 16 байт. В MDN AesGcmParams тот же совет: IV 96 бит, и при одном ключе каждый проход шифрования берёт новый IV. Эти числа можно сверить в реализации.

Поэтому в логах сервера должно быть видно: номер шифротекста id, поля JSON при создании ciphertext / ttl_hours / max_reads, позже GET к интерфейсу шифротекста. В логах не должно быть: открытого текста, 32-байтового ключа и хвоста после # из адресной строки. Срок — 1 час, 24 часа или 7 дней; можно сжечь только по числу прочтений. Диапазон прочтений — от 1 до 10. Это ограничения, которые видны на странице создания, а не придуманный после факта SLA.

Протокол не следит, что скрипт страницы отправит сам

То, что HTTP не шлёт фрагмент, не значит, что ключ никогда не покинет этот компьютер. Скрипт страницы может прочитать window.location.hash и сам сделать fetch. Если аналитика отдаёт полный location.href как адрес страницы, хвост после решётки попадёт в статистику. Смотрите запрос аналитики во вкладке «Сеть», а не останавливайтесь на «HTTP всё равно не возьмёт».

Полная ссылка — это допуск

Ключ после # прячет его только от HTTP-сервиса, который хранит шифротекст. Мессенджер, почтовый клиент, история браузера и синхронизированные вкладки сохраняют адрес так, как его видел человек, включая хвост после решётки. У кого полная ссылка — тот откроет страницу чтения и расшифрует. Это ссылка-носитель: сам адрес и есть допуск. Второй «пароль, который знает только получатель», по умолчанию нет — его можно добавить только руками.

Более чувствительную передачу можно разрезать: в одном сообщении только s.html?id=…, во втором канале — телефон, разговор у стола или другой мессенджер — только хвост после #. По одной половине не расшифровать. Это приём использования, не заводская настройка. По умолчанию страница всё равно выдаёт одну целую ссылку, чтобы коллега открыл её один раз.

Скриншот, копирование и пересылку уже расшифрованного текста протокол тоже не останавливает. Одноразовая ссылка уменьшает повторные открытия и долгое хранение открытого текста на сервере. Если получатель сразу снимет экран или вставит исходник в другой чат, протокол здесь ни при чём. Годится, когда вы доверяете, что человек прочитает и остановится, и когда секрет можно отозвать и выдать заново. Долгий мастер-пароль, закрытый ключ или сид-фразу этим путём лучше не возить.

Проверка во вкладке «Сеть»

Шаги ниже не опираются на обещание бренда. Берите одноразовую тестовую фразу. Живой пароль от базы для тренировки не используйте.

  1. Откройте инструменты разработчика, вкладку «Сеть», включите «Не очищать журнал». Затем откройте страницу создания одноразовой ссылки, введите тестовую фразу orange-lake-7, число прочтений поставьте 1 и сгенерируйте ссылку.
  2. Посмотрите POST создания: в теле должно быть поле шифротекста, строки orange-lake-7 быть не должно. Запишите полную ссылку: вид s.html?id=…#…, после решётки есть хвост, после вопросительного знака только id.
  3. Вставьте полную ссылку в новую вкладку. Строка документного запроса должна быть …/s.html?id=…. В ней не должно быть # и ключа после неё.
  4. Дальше смотрите Fetch / XHR: в запросе шифротекста должен быть номер, не ключ. Аналитика тоже не должна унести хвост после # или исходную тестовую фразу.
  5. Страница должна расшифровать тестовую фразу. В другой вкладке вставьте только часть до решётки: должно быть «ссылка неполная», а не исходник.
  6. Вернитесь к уже прочитанной ссылке и обновите: должно быть «сожжено» или «истекло», не исходник. Чтобы передать снова, создайте новую. Резервной копии открытого текста на сервере нет.

Одноразовая ссылка MyPassGen работает внутри этой границы: AES-256-GCM, потолок открытого текста 32 КБ, ключ после #, создать и открыть можно без аккаунта. Верить стоит вкладке «Сеть» и проверке «без решётки не расшифруется», а не трём словам «нулевое знание» на странице. Полный список ограничений — на странице безопасности.

Чего решётка не закрывает

Корпоративная почта и часть мессенджеров делают превью ссылки: бэкенд один раз делает GET страницы, чтобы взять заголовок или отрывок. Превью, которое забирает HTML и не исполняет скрипт страницы, фрагмент не видит и обычно не вызывает интерфейс шифротекста. Сканер, который скрипт исполняет, может закончить прочтение раньше получателя — и шифротекст сгорит. Это не дыра протокола. Это цена правила «открыли страницу чтения — скрипт заберёт шифротекст». Если коллега пишет, что ссылка уже мертва, сначала спросите, не съело ли её превью. Чтобы передать снова, создайте новую.

История браузера, отчёты о сбоях и часть синхронизированных аккаунтов хранят полный URL. Оставили одноразовую ссылку в адресной строке — оставили ключ в локальной истории. После чтения не вставляйте адрес с # в шаблон тикета и не кладите его в закладки как «вечный бэкап». Потерянную ссылку не восстановить: сброса у поддержки нет, в подвале сайта нет ящика, из которого вернут шифротекст.

Когда уходит длинная инструкция, ссылка и текст — отдельная работа. Адрес с utm_source чистят по таблице параметров; телефон, паспорт и СНИЛС в тикете маскируют по заметке какие поля маскировать перед отправкой. Одноразовая ссылка отвечает на «хост не видит ключ и открытый текст». Она не отвечает на «в переписке не должно быть полного номера счёта».

Типичные ошибки

«Ключ в адресе — значит, сервер его видит.» Зависит от куска. После ? — да. После # по RFC 9110 документный запрос и Referer этот кусок несут не должны. Смотрите строку запроса во вкладке «Сеть», а не спорьте с интуицией.

«Если ключ после #, история чата безопасна.» Мессенджер сохраняет ту строку, которую вы вставили. Отсутствие ключа в логе сервера не значит, что его нет у всех в чате или у того, кто потом разблокирует этот телефон.

«Самоуничтожающаяся ссылка мешает сделать скриншот.» Нет. Она уменьшает повторные открытия и хранение открытого текста на сервере. Копирование и фото экрана она не уменьшает. Передавайте только тот пароль, который можно отозвать.

«Получатель тоже должен зарегистрироваться, чтобы открыть.» На этом сайте и создание, и чтение работают без аккаунта. Страница чтения открыта получателю. «Открывается сразу» не значит «у кого ссылка — тот сначала логинится».

С чего начать

Начните с того пароля, который сегодня уже собирались отправить. Сначала пройдите шесть шагов выше на одноразовой тестовой фразе: в запросе создания нет исходника, в строке запроса страницы чтения нет ключа после #, без решётки не расшифровывается, повторное открытие показывает сожжённое. Потом отправляйте настоящий секрет.

Настоящий пароль сгенерируйте на этом устройстве в генераторе. Случайный режим — длина 6–128, по умолчанию 16; меньше 8 символов считайте слабым. Отправьте полную ссылку. Если передача чувствительнее, разрежьте путь и хвост после решётки на два канала. Тот же пароль ещё раз в чат «на всякий случай» не вставляйте. После одной такой передачи вы уже можете ответить на вопрос статьи: ключ ставят после #, чтобы HTTP-сервис с шифротекстом его не видел. Историю чата и скриншот это не закрывает.

Частые вопросы

Если ключ после #, сервер его правда не видит?

По RFC 9110 целевой URI документного запроса не содержит фрагмент; по MDN Referer этот кусок тоже не несёт. Откройте вкладку «Сеть»: в строке запроса страницы чтения ключа после решётки быть не должно. Если скрипт страницы сам отдаёт location.href, это уже дыра реализации — её ищите в списке Fetch.

Получателю нужна регистрация, чтобы открыть ссылку?

Нет. И создание, и чтение работают без аккаунта. Получатель открывает страницу чтения и расшифровывает. После заданного числа прочтений или по истечении TTL повторное открытие покажет «сожжено» или «истекло», а не исходник.

Я скопировал только часть до решётки. Откроется?

Расшифровать нельзя. Без ключа после # страница чтения должна сказать, что ссылка неполная. Попросите у отправителя полный адрес. Потерянную или уже сожжённую ссылку не восстановить: чтобы передать снова, создайте новую.

Это мешает мессенджеру сохранить запись?

Нет. Мессенджер обычно сохраняет весь адрес, который вы вставили, вместе с ключом. Решётка только убирает ключ с HTTP-сервиса, который хранит шифротекст. Не хотите, чтобы долгий чат держал допуск, — не вставляйте туда полную ссылку.