Comments 10
200 символов диктовать по телефону)
Зачем, если есть SMS?
Если речь про активацию в духе Windows XP: «позвоните, продиктуйте номер установки, получите код» то это не альтернатива подписи, а тот же самый механизм, только с человеком вместо интернета. Короткий код подтверждения в такой схеме ровно криптографический ответ сервера, просто усечённый до длины, которую можно продиктовать голосом; проверяет его всё равно программа на машине пользователя, всё той же математикой.
Есть и практическая сторона. Мой ключ самодостаточен: в нём лежат данные лицензии и подпись, потому что программе больше неоткуда взять правду: ни базы, ни сети. 202 символа в SMS не влезают (70 знаков кириллицей, 160 латиницей), а диктовать их по телефону это пытка для обеих сторон )). Короткими коды у Microsoft были не от хорошей жизни и не от лучшей криптографии: там строка, не документ, а челлендж к их базе, то есть решение принимает сервер, а телефон просто канал связи. Я же специально делал вариант, который работает без канала связи вообще.
Плюс два соображения, не технических. SMS - это платный шлюз, который должен жить круглосуточно и не падать, иначе покупатель не может включить то, за что заплатил. И это номер телефона, то есть персональные данные: у меня в базе сейчас хэш ключа и хэш почты с маской, ни одного телефона, и мне так спокойнее и юридически, и по-человечески.
Так что SMS не убирает криптографию, а добавляет к ней зависимость и персональные данные.
Фигура речи)) Конечно столько никто диктовать не будет ))
А как реализована ротация ключей и защита от ввода одного ключа (J3QQ4-...) в несколько инстансов софтины?
Сразу спойлер: эта тема по хорошему часть следующей статьи "продолжения", так как первая, текущая, описывала анализ и исследовательскую часть в варианте оффлайн, а вторая, ту которую я в итоге и допилил, уже построена на онлайн активации с участием сервера.
Коротко ответить к сожелению наверное не выйдет.
Ответ разделю условно на две части.
Ротация. Первый байт полезной нагрузки - номер ключа подписи (keyId). В приложении вшит не один публичный ключ, а словарь «номер → ключ». Смена выглядит так: генерирую новую пару, добавляю в словарь строку с новым номером, новые лицензии подписываю ею. Ранее выданные продолжают проверяться старым публичным ключом — ротация не должна "наказывать" тех, кто уже заплатил.
Если утёк приватный ключ, одной ротации мало: вор печатает валидные лицензии на старом. План на этот случай - релиз с новым keyId, старый помечается устаревшим, и проверка для него переводится в режим «доверяем только номерам лицензий из белого списка». Это работает потому, что у каждой лицензии есть собственный номер (4 байта в payload) и локальный реестр выданных: ключ, напечатанный вором, будет с номером, которого в реестре нет. Есть конечно и ограничение - и новый keyId, и список отзыва доезжают только до тех, кто обновился. Автообновление есть, но мгновенным это не делает.
Вторая пара ключей - у сервера активации, он подписывает короткоживущие тикеты, номер ключа едет прямо в тикете. Там ротация проще: тикет живёт 30 дней, обратная совместимость не нужна, достаточно, чтобы клиент при «подпись не сошлась» молча пошёл активироваться заново. Ровно эту ветку я сначала и не написал, а ключ сервера пришлось менять внепланово. На тесте вылезло: клиент со старым тикетом получал невалидную подпись и тихо уезжал в бесплатный режим вместо переактивации, то есть при смене ключа платный уровень погас бы разом у всех.
Два урока оттуда:
ротация не считается сделанной, пока не прогнал клиента, у которого на руках СТАРЫЙ артефакт;
порядок проверки «сначала ключ, потом тикет» пришлось перевернуть - негодная доверенность не должна мешать активации.
Один ключ в нескольких инстансах. Сама подписанная строка этого не умеет в принципе: подпись доказывает подлинность, а не уникальность. Счётчик активаций внутрь ключа не положить - payload скреплён подписью, а переподписать его на машине пользователя нечем, отдельный локальный счётчик рядом обнуляется копированием файла.
Единственное место, где такой счётчик может жить - это сервер.
Поэтому поверх офлайновой подписи стоит онлайн активация:
клиент шлёт ключ и обезличенный отпечаток машины — HMAC-SHA256 от MachineGuid и серийника системного тома, первые 16 байт;
сервер занимает слот (в рознице seats = 1) и возвращает подписанный СВОИМ ключом тикет: номер лицензии, хэш ключа, отпечаток устройства, слот, срок 30 дней, nonce клиента;
дальше программа работает офлайн: проверяет подпись сервера, пересчитывает отпечаток, смотрит срок. За подтверждением ходит не чаще раза в сутки, без сети живёт до 30 дней.
Второй экземпляр того же ключа получает отказ SEAT_TAKEN. Скопировать другу ключ вместе с тикетом тоже не помогает: доверенность выписана на чужое железо, отпечаток не сойдётся, а новую сервер не выдаст - слот занят. Переустановка Windows, поломка, переезд решаются не криптографией, а кнопкой «Освободить и активировать здесь» (доказательство владения - предъявить полный ключ), не чаще трёх раз в 30 дней, чтобы это не превратилось в «пользуемся вдвоём по очереди».
В базе при этом ни ключей, ни адресов в открытом виде: хэш ключа, хэш почты с маской, хэш отпечатка - утечка базы не даёт ни одной работающей лицензии.
И сразу предел всего происходящего, чтобы не создавать ложного впечатления: всё это защищает от копирования ключа, но не от патча бинарника. С ним конечно все обстоит куда сложнее.
Какой-то оверкил для недорогой десктопной программы.
подпись перепроверяется в каждой точке, где платная функция реально исполняется
Если вызовом функции - то это патчится в одном месте, если как-то инлайнится - то тоже не на много сложнее, чем в одном месте поправить, тем более ИИ может всё это эвтоматизировать.
Согласен с вами, я в статье сформулировал слишком широко. Перепроверки спасают от класса «правка живого значения»: единый флаг «оплачено» это поле, которое живёт весь сеанс, его находят поиском в памяти и переключают, не пересобирая сборку и не читая код. Но это время, желание сломать и навыки с знаниями. Когда решение нигде не залипает, переключать нечего.
От правки самой сборки они не спасают - тут вы правы. Гейт это один метод, и патчится он один раз, сколько бы точек его ни звали. Мне наверное конечно стоит подправить свою формулировку: не «защищает от патча», а «поднимает атаку на ступеньку: с правки значения в памяти до правки кода». Ступенька, не стена, в статье стоило написать именно так.
Вторую причину я в текст не вынес. У платных функций в приложении есть путь запуска без интерфейса: фоновая очистка стартует из Планировщика с ключом --scheduled, без окна и кнопок. Проверка, живущая на кнопке, обходится штатно - не злоумышленником, а самой программой. Поэтому права сверяются там, где действие исполняется.
Про оверкил: крипта здесь стоила пары сотен строк на встроенной библиотеке и 0,19 мс на проверку. Настоящим оверкилом было бы городить обфускацию до первых реальных продаж - этот шаг я как раз отложил.
Про ИИ, который всё автоматизирует - согласен. Гонку «сделать невзламываемо» недорогая десктопная программа выигрывать не должна и не может. Задача скромнее: чтобы честный путь был проще нечестного для обычного человека.
Про 571-символьную "простыню" от RSA – прямо больно и знакомо. Есть ирония уровнем выше: вся эта аккуратная криптография честно защищает от того, чего никто не делает. Ключ никто не станет подделывать – его просто купят один раз и выложат на форум, а без сервера программа будет с гордостью проверять валиднейшую подпись на тысяче чужих машин. Математика подписи безупречна ровно до того момента, как один довольный пользователь нажмёт Ctrl+C.
Здесь аналогичная тема ложащаяся в планируемое продолжение, где я бы раскрыл онлайн активацию и механизмы контроля, в текущей статье был условный первый этап, через который я сознательно решил пройти, так как любую тему пытаюсь пройти от "фундамента". Данные задачи я ставлю перед собой и разбираю в первую очередь для самообучения, так проще когда есть прикладная задача.
Согласен с вами по фактам и не согласен с выводом — попробую объяснить свою позицию.
Да, подпись не защищает от Ctrl+C, и не должна: её работа - доказать, что документ выписал я и что байты не переписали по дороге. «Сколько человек пользуется одним документом» это вопрос учёта, и в оффлайновой схеме, согласен, он не решается никак: счётчик внутрь ключа не положить (payload скреплён подписью, переподписать на машине пользователя нечем), а счётчик в файле рядом обнуляется копированием этого файла.
Причём описанный сценарий у меня не гипотетический. Это ровно первая редакция проекта: чистый офлайн, ключ из письма, никакого сервера. Я её зафиксировал и продолжил развивать примерно по вашим же аргументам, а статья описывает выбор алгоритма внутри слоя «транспорт прав», а не всю защиту целиком. J3QQ4-… из соседнего комментария жив как раз потому, что в его эпоху отклонять было нечему.
Что стоит поверх: активация на сервере. Ключ плюс обезличенный отпечаток машины: сервер занимает единственный слот и выдаёт подписанную им доверенность на 30 дней, привязанную к этому отпечатку. Выложенный на форум ключ активируется у первого пришедшего, все остальные получают «слот занят». Копирование ключа вместе с доверенностью не помогает: она выписана на чужое железо.
А теперь ирония в обратную сторону, ради которой я и отвечаю. Как только появляется сервер, криптография не становится лишней - она становится сильно актуальнее. Онлайн проверка без подписанных ответов ломается за десять минут: строка в hosts, свой «сервер активации» на localhost, свой сертификат и программа радостно слышат «лицензия подтверждена». HTTPS здесь не спасает: сертификат для собственного локального сервера пользователь на своей машине выпишет сам при должных знаниях и желании. Спасает то, что ответ подписан отдельным серверным ключом, публичная часть которого вшита в программу, плюс nonce от клиента, чтобы нельзя было подсунуть записанный вчера ответ. Подделать это - значит подделать подпись, то есть ровно та задача, про которую статья.
И вторая работа для той же подписи: она позволяет не ходить в сеть. Доверенность самоподписана сервером, поэтому программа спокойно живёт офлайн до 30 дней вместо безуспешного стука на сервер при каждом запуске.
Против: «вырезал проверку из бинарника» не работает ни подпись, ни сервер - только удорожание взлома, и я не топлю про 100% защиту. Задача этого слоя скромнее: закрыть массовый бытовой канал «скинь ключик», а он и составляет практически все потери у продуктов такого размера.
Лицензионный ключ на 202 символа: почему не Ed25519 и не RSA