Согласен с вами, я в статье сформулировал слишком широко. Перепроверки спасают от класса «правка живого значения»: единый флаг «оплачено» это поле, которое живёт весь сеанс, его находят поиском в памяти и переключают, не пересобирая сборку и не читая код. Но это время, желание сломать и навыки с знаниями. Когда решение нигде не залипает, переключать нечего.
От правки самой сборки они не спасают - тут вы правы. Гейт это один метод, и патчится он один раз, сколько бы точек его ни звали. Мне наверное конечно стоит подправить свою формулировку: не «защищает от патча», а «поднимает атаку на ступеньку: с правки значения в памяти до правки кода». Ступенька, не стена, в статье стоило написать именно так.
Вторую причину я в текст не вынес. У платных функций в приложении есть путь запуска без интерфейса: фоновая очистка стартует из Планировщика с ключом --scheduled, без окна и кнопок. Проверка, живущая на кнопке, обходится штатно - не злоумышленником, а самой программой. Поэтому права сверяются там, где действие исполняется.
Про оверкил: крипта здесь стоила пары сотен строк на встроенной библиотеке и 0,19 мс на проверку. Настоящим оверкилом было бы городить обфускацию до первых реальных продаж - этот шаг я как раз отложил.
Про ИИ, который всё автоматизирует - согласен. Гонку «сделать невзламываемо» недорогая десктопная программа выигрывать не должна и не может. Задача скромнее: чтобы честный путь был проще нечестного для обычного человека.
Если речь про активацию в духе Windows XP: «позвоните, продиктуйте номер установки, получите код» то это не альтернатива подписи, а тот же самый механизм, только с человеком вместо интернета. Короткий код подтверждения в такой схеме ровно криптографический ответ сервера, просто усечённый до длины, которую можно продиктовать голосом; проверяет его всё равно программа на машине пользователя, всё той же математикой.
Есть и практическая сторона. Мой ключ самодостаточен: в нём лежат данные лицензии и подпись, потому что программе больше неоткуда взять правду: ни базы, ни сети. 202 символа в SMS не влезают (70 знаков кириллицей, 160 латиницей), а диктовать их по телефону это пытка для обеих сторон )). Короткими коды у Microsoft были не от хорошей жизни и не от лучшей криптографии: там строка, не документ, а челлендж к их базе, то есть решение принимает сервер, а телефон просто канал связи. Я же специально делал вариант, который работает без канала связи вообще.
Плюс два соображения, не технических. SMS - это платный шлюз, который должен жить круглосуточно и не падать, иначе покупатель не может включить то, за что заплатил. И это номер телефона, то есть персональные данные: у меня в базе сейчас хэш ключа и хэш почты с маской, ни одного телефона, и мне так спокойнее и юридически, и по-человечески.
Так что SMS не убирает криптографию, а добавляет к ней зависимость и персональные данные.
Здесь аналогичная тема ложащаяся в планируемое продолжение, где я бы раскрыл онлайн активацию и механизмы контроля, в текущей статье был условный первый этап, через который я сознательно решил пройти, так как любую тему пытаюсь пройти от "фундамента". Данные задачи я ставлю перед собой и разбираю в первую очередь для самообучения, так проще когда есть прикладная задача.
Согласен с вами по фактам и не согласен с выводом — попробую объяснить свою позицию.
Да, подпись не защищает от Ctrl+C, и не должна: её работа - доказать, что документ выписал я и что байты не переписали по дороге. «Сколько человек пользуется одним документом» это вопрос учёта, и в оффлайновой схеме, согласен, он не решается никак: счётчик внутрь ключа не положить (payload скреплён подписью, переподписать на машине пользователя нечем), а счётчик в файле рядом обнуляется копированием этого файла.
Причём описанный сценарий у меня не гипотетический. Это ровно первая редакция проекта: чистый офлайн, ключ из письма, никакого сервера. Я её зафиксировал и продолжил развивать примерно по вашим же аргументам, а статья описывает выбор алгоритма внутри слоя «транспорт прав», а не всю защиту целиком. J3QQ4-… из соседнего комментария жив как раз потому, что в его эпоху отклонять было нечему.
Что стоит поверх: активация на сервере. Ключ плюс обезличенный отпечаток машины: сервер занимает единственный слот и выдаёт подписанную им доверенность на 30 дней, привязанную к этому отпечатку. Выложенный на форум ключ активируется у первого пришедшего, все остальные получают «слот занят». Копирование ключа вместе с доверенностью не помогает: она выписана на чужое железо.
А теперь ирония в обратную сторону, ради которой я и отвечаю. Как только появляется сервер, криптография не становится лишней - она становится сильно актуальнее. Онлайн проверка без подписанных ответов ломается за десять минут: строка в hosts, свой «сервер активации» на localhost, свой сертификат и программа радостно слышат «лицензия подтверждена». HTTPS здесь не спасает: сертификат для собственного локального сервера пользователь на своей машине выпишет сам при должных знаниях и желании. Спасает то, что ответ подписан отдельным серверным ключом, публичная часть которого вшита в программу, плюс nonce от клиента, чтобы нельзя было подсунуть записанный вчера ответ. Подделать это - значит подделать подпись, то есть ровно та задача, про которую статья.
И вторая работа для той же подписи: она позволяет не ходить в сеть. Доверенность самоподписана сервером, поэтому программа спокойно живёт офлайн до 30 дней вместо безуспешного стука на сервер при каждом запуске.
Против: «вырезал проверку из бинарника» не работает ни подпись, ни сервер - только удорожание взлома, и я не топлю про 100% защиту. Задача этого слоя скромнее: закрыть массовый бытовой канал «скинь ключик», а он и составляет практически все потери у продуктов такого размера.
Сразу спойлер: эта тема по хорошему часть следующей статьи "продолжения", так как первая, текущая, описывала анализ и исследовательскую часть в варианте оффлайн, а вторая, ту которую я в итоге и допилил, уже построена на онлайн активации с участием сервера.
Коротко ответить к сожелению наверное не выйдет.
Ответ разделю условно на две части.
Ротация. Первый байт полезной нагрузки - номер ключа подписи (keyId). В приложении вшит не один публичный ключ, а словарь «номер → ключ». Смена выглядит так: генерирую новую пару, добавляю в словарь строку с новым номером, новые лицензии подписываю ею. Ранее выданные продолжают проверяться старым публичным ключом — ротация не должна "наказывать" тех, кто уже заплатил.
Если утёк приватный ключ, одной ротации мало: вор печатает валидные лицензии на старом. План на этот случай - релиз с новым keyId, старый помечается устаревшим, и проверка для него переводится в режим «доверяем только номерам лицензий из белого списка». Это работает потому, что у каждой лицензии есть собственный номер (4 байта в payload) и локальный реестр выданных: ключ, напечатанный вором, будет с номером, которого в реестре нет. Есть конечно и ограничение - и новый keyId, и список отзыва доезжают только до тех, кто обновился. Автообновление есть, но мгновенным это не делает.
Вторая пара ключей - у сервера активации, он подписывает короткоживущие тикеты, номер ключа едет прямо в тикете. Там ротация проще: тикет живёт 30 дней, обратная совместимость не нужна, достаточно, чтобы клиент при «подпись не сошлась» молча пошёл активироваться заново. Ровно эту ветку я сначала и не написал, а ключ сервера пришлось менять внепланово. На тесте вылезло: клиент со старым тикетом получал невалидную подпись и тихо уезжал в бесплатный режим вместо переактивации, то есть при смене ключа платный уровень погас бы разом у всех.
Два урока оттуда:
ротация не считается сделанной, пока не прогнал клиента, у которого на руках СТАРЫЙ артефакт;
порядок проверки «сначала ключ, потом тикет» пришлось перевернуть - негодная доверенность не должна мешать активации.
Один ключ в нескольких инстансах. Сама подписанная строка этого не умеет в принципе: подпись доказывает подлинность, а не уникальность. Счётчик активаций внутрь ключа не положить - payload скреплён подписью, а переподписать его на машине пользователя нечем, отдельный локальный счётчик рядом обнуляется копированием файла.
Единственное место, где такой счётчик может жить - это сервер.
Поэтому поверх офлайновой подписи стоит онлайн активация:
клиент шлёт ключ и обезличенный отпечаток машины — HMAC-SHA256 от MachineGuid и серийника системного тома, первые 16 байт;
сервер занимает слот (в рознице seats = 1) и возвращает подписанный СВОИМ ключом тикет: номер лицензии, хэш ключа, отпечаток устройства, слот, срок 30 дней, nonce клиента;
дальше программа работает офлайн: проверяет подпись сервера, пересчитывает отпечаток, смотрит срок. За подтверждением ходит не чаще раза в сутки, без сети живёт до 30 дней.
Второй экземпляр того же ключа получает отказ SEAT_TAKEN. Скопировать другу ключ вместе с тикетом тоже не помогает: доверенность выписана на чужое железо, отпечаток не сойдётся, а новую сервер не выдаст - слот занят. Переустановка Windows, поломка, переезд решаются не криптографией, а кнопкой «Освободить и активировать здесь» (доказательство владения - предъявить полный ключ), не чаще трёх раз в 30 дней, чтобы это не превратилось в «пользуемся вдвоём по очереди».
В базе при этом ни ключей, ни адресов в открытом виде: хэш ключа, хэш почты с маской, хэш отпечатка - утечка базы не даёт ни одной работающей лицензии.
И сразу предел всего происходящего, чтобы не создавать ложного впечатления: всё это защищает от копирования ключа, но не от патча бинарника. С ним конечно все обстоит куда сложнее.
Первая: API взят в самом невыгодном для него виде. Ни кэша промпта, ни batch, ни роутинга по моделям. При r=10:1 весь счёт — это вход, а кэш бьёт именно во вход. Плюс базлайн по Opus: в проде никто не гоняет весь трафик на топовой модели, обычно дешёвая + фронтир на сложном хвосте. На ваших же цифрах: против Opus порог ~1,3 млрд токенов/мес, против микса с Haiku — уже ~6,4 млрд, с кэшем ещё вдвое. Порог сдвигается почти в десять раз. То есть вы свой же вывод недооценили и он получается жёстче.
Вторая: утилизацию нельзя крутить свободно, её держит латентность. При U→1 очередь съедает p95, реальный потолок 60-70%. И, кажется, это и есть ответ на вопрос, почему провайдер выигрывает: не «нагрузка больше», а он держит высокую загрузку, не ломая latency, за счёт тысяч тенантов. Один арендатор так не умеет.
И момент по энергии: PUE применён к одним GPU (5,6×1,4), а остальная нода потерялась — CPU, вентиляторы, сеть. По факту забор 10-11 кВт, с PUE ~14-15. На итог это не влияет, но там ещё и модель оплаты другая: при аренде стойки в дата-центре платят не за фактические киловатт-часы, а за оговорённый лимит мощности. А 15 кВт на стойку - это уже серъезно.
Согласен с вами, я в статье сформулировал слишком широко. Перепроверки спасают от класса «правка живого значения»: единый флаг «оплачено» это поле, которое живёт весь сеанс, его находят поиском в памяти и переключают, не пересобирая сборку и не читая код. Но это время, желание сломать и навыки с знаниями. Когда решение нигде не залипает, переключать нечего.
От правки самой сборки они не спасают - тут вы правы. Гейт это один метод, и патчится он один раз, сколько бы точек его ни звали. Мне наверное конечно стоит подправить свою формулировку: не «защищает от патча», а «поднимает атаку на ступеньку: с правки значения в памяти до правки кода». Ступенька, не стена, в статье стоило написать именно так.
Вторую причину я в текст не вынес. У платных функций в приложении есть путь запуска без интерфейса: фоновая очистка стартует из Планировщика с ключом --scheduled, без окна и кнопок. Проверка, живущая на кнопке, обходится штатно - не злоумышленником, а самой программой. Поэтому права сверяются там, где действие исполняется.
Про оверкил: крипта здесь стоила пары сотен строк на встроенной библиотеке и 0,19 мс на проверку. Настоящим оверкилом было бы городить обфускацию до первых реальных продаж - этот шаг я как раз отложил.
Про ИИ, который всё автоматизирует - согласен. Гонку «сделать невзламываемо» недорогая десктопная программа выигрывать не должна и не может. Задача скромнее: чтобы честный путь был проще нечестного для обычного человека.
Если речь про активацию в духе Windows XP: «позвоните, продиктуйте номер установки, получите код» то это не альтернатива подписи, а тот же самый механизм, только с человеком вместо интернета. Короткий код подтверждения в такой схеме ровно криптографический ответ сервера, просто усечённый до длины, которую можно продиктовать голосом; проверяет его всё равно программа на машине пользователя, всё той же математикой.
Есть и практическая сторона. Мой ключ самодостаточен: в нём лежат данные лицензии и подпись, потому что программе больше неоткуда взять правду: ни базы, ни сети. 202 символа в SMS не влезают (70 знаков кириллицей, 160 латиницей), а диктовать их по телефону это пытка для обеих сторон )). Короткими коды у Microsoft были не от хорошей жизни и не от лучшей криптографии: там строка, не документ, а челлендж к их базе, то есть решение принимает сервер, а телефон просто канал связи. Я же специально делал вариант, который работает без канала связи вообще.
Плюс два соображения, не технических. SMS - это платный шлюз, который должен жить круглосуточно и не падать, иначе покупатель не может включить то, за что заплатил. И это номер телефона, то есть персональные данные: у меня в базе сейчас хэш ключа и хэш почты с маской, ни одного телефона, и мне так спокойнее и юридически, и по-человечески.
Так что SMS не убирает криптографию, а добавляет к ней зависимость и персональные данные.
Здесь аналогичная тема ложащаяся в планируемое продолжение, где я бы раскрыл онлайн активацию и механизмы контроля, в текущей статье был условный первый этап, через который я сознательно решил пройти, так как любую тему пытаюсь пройти от "фундамента". Данные задачи я ставлю перед собой и разбираю в первую очередь для самообучения, так проще когда есть прикладная задача.
Согласен с вами по фактам и не согласен с выводом — попробую объяснить свою позицию.
Да, подпись не защищает от Ctrl+C, и не должна: её работа - доказать, что документ выписал я и что байты не переписали по дороге. «Сколько человек пользуется одним документом» это вопрос учёта, и в оффлайновой схеме, согласен, он не решается никак: счётчик внутрь ключа не положить (payload скреплён подписью, переподписать на машине пользователя нечем), а счётчик в файле рядом обнуляется копированием этого файла.
Причём описанный сценарий у меня не гипотетический. Это ровно первая редакция проекта: чистый офлайн, ключ из письма, никакого сервера. Я её зафиксировал и продолжил развивать примерно по вашим же аргументам, а статья описывает выбор алгоритма внутри слоя «транспорт прав», а не всю защиту целиком. J3QQ4-… из соседнего комментария жив как раз потому, что в его эпоху отклонять было нечему.
Что стоит поверх: активация на сервере. Ключ плюс обезличенный отпечаток машины: сервер занимает единственный слот и выдаёт подписанную им доверенность на 30 дней, привязанную к этому отпечатку. Выложенный на форум ключ активируется у первого пришедшего, все остальные получают «слот занят». Копирование ключа вместе с доверенностью не помогает: она выписана на чужое железо.
А теперь ирония в обратную сторону, ради которой я и отвечаю. Как только появляется сервер, криптография не становится лишней - она становится сильно актуальнее. Онлайн проверка без подписанных ответов ломается за десять минут: строка в hosts, свой «сервер активации» на localhost, свой сертификат и программа радостно слышат «лицензия подтверждена». HTTPS здесь не спасает: сертификат для собственного локального сервера пользователь на своей машине выпишет сам при должных знаниях и желании. Спасает то, что ответ подписан отдельным серверным ключом, публичная часть которого вшита в программу, плюс nonce от клиента, чтобы нельзя было подсунуть записанный вчера ответ. Подделать это - значит подделать подпись, то есть ровно та задача, про которую статья.
И вторая работа для той же подписи: она позволяет не ходить в сеть. Доверенность самоподписана сервером, поэтому программа спокойно живёт офлайн до 30 дней вместо безуспешного стука на сервер при каждом запуске.
Против: «вырезал проверку из бинарника» не работает ни подпись, ни сервер - только удорожание взлома, и я не топлю про 100% защиту. Задача этого слоя скромнее: закрыть массовый бытовой канал «скинь ключик», а он и составляет практически все потери у продуктов такого размера.
Сразу спойлер: эта тема по хорошему часть следующей статьи "продолжения", так как первая, текущая, описывала анализ и исследовательскую часть в варианте оффлайн, а вторая, ту которую я в итоге и допилил, уже построена на онлайн активации с участием сервера.
Коротко ответить к сожелению наверное не выйдет.
Ответ разделю условно на две части.
Ротация. Первый байт полезной нагрузки - номер ключа подписи (keyId). В приложении вшит не один публичный ключ, а словарь «номер → ключ». Смена выглядит так: генерирую новую пару, добавляю в словарь строку с новым номером, новые лицензии подписываю ею. Ранее выданные продолжают проверяться старым публичным ключом — ротация не должна "наказывать" тех, кто уже заплатил.
Если утёк приватный ключ, одной ротации мало: вор печатает валидные лицензии на старом. План на этот случай - релиз с новым keyId, старый помечается устаревшим, и проверка для него переводится в режим «доверяем только номерам лицензий из белого списка». Это работает потому, что у каждой лицензии есть собственный номер (4 байта в payload) и локальный реестр выданных: ключ, напечатанный вором, будет с номером, которого в реестре нет. Есть конечно и ограничение - и новый keyId, и список отзыва доезжают только до тех, кто обновился. Автообновление есть, но мгновенным это не делает.
Вторая пара ключей - у сервера активации, он подписывает короткоживущие тикеты, номер ключа едет прямо в тикете. Там ротация проще: тикет живёт 30 дней, обратная совместимость не нужна, достаточно, чтобы клиент при «подпись не сошлась» молча пошёл активироваться заново. Ровно эту ветку я сначала и не написал, а ключ сервера пришлось менять внепланово. На тесте вылезло: клиент со старым тикетом получал невалидную подпись и тихо уезжал в бесплатный режим вместо переактивации, то есть при смене ключа платный уровень погас бы разом у всех.
Два урока оттуда:
ротация не считается сделанной, пока не прогнал клиента, у которого на руках СТАРЫЙ артефакт;
порядок проверки «сначала ключ, потом тикет» пришлось перевернуть - негодная доверенность не должна мешать активации.
Один ключ в нескольких инстансах. Сама подписанная строка этого не умеет в принципе: подпись доказывает подлинность, а не уникальность. Счётчик активаций внутрь ключа не положить - payload скреплён подписью, а переподписать его на машине пользователя нечем, отдельный локальный счётчик рядом обнуляется копированием файла.
Единственное место, где такой счётчик может жить - это сервер.
Поэтому поверх офлайновой подписи стоит онлайн активация:
клиент шлёт ключ и обезличенный отпечаток машины — HMAC-SHA256 от MachineGuid и серийника системного тома, первые 16 байт;
сервер занимает слот (в рознице seats = 1) и возвращает подписанный СВОИМ ключом тикет: номер лицензии, хэш ключа, отпечаток устройства, слот, срок 30 дней, nonce клиента;
дальше программа работает офлайн: проверяет подпись сервера, пересчитывает отпечаток, смотрит срок. За подтверждением ходит не чаще раза в сутки, без сети живёт до 30 дней.
Второй экземпляр того же ключа получает отказ SEAT_TAKEN. Скопировать другу ключ вместе с тикетом тоже не помогает: доверенность выписана на чужое железо, отпечаток не сойдётся, а новую сервер не выдаст - слот занят. Переустановка Windows, поломка, переезд решаются не криптографией, а кнопкой «Освободить и активировать здесь» (доказательство владения - предъявить полный ключ), не чаще трёх раз в 30 дней, чтобы это не превратилось в «пользуемся вдвоём по очереди».
В базе при этом ни ключей, ни адресов в открытом виде: хэш ключа, хэш почты с маской, хэш отпечатка - утечка базы не даёт ни одной работающей лицензии.
И сразу предел всего происходящего, чтобы не создавать ложного впечатления: всё это защищает от копирования ключа, но не от патча бинарника. С ним конечно все обстоит куда сложнее.
Фигура речи)) Конечно столько никто диктовать не будет ))
Благодарю вас, за наставление!) Внедрю!
Благодарю, учту.
Благодарю, учту.
С нетерпением буду ждать!
Плюсую за 2 раздел с харнессами.
По расчёту две придирки.
Первая: API взят в самом невыгодном для него виде. Ни кэша промпта, ни batch, ни роутинга по моделям. При r=10:1 весь счёт — это вход, а кэш бьёт именно во вход. Плюс базлайн по Opus: в проде никто не гоняет весь трафик на топовой модели, обычно дешёвая + фронтир на сложном хвосте. На ваших же цифрах: против Opus порог ~1,3 млрд токенов/мес, против микса с Haiku — уже ~6,4 млрд, с кэшем ещё вдвое. Порог сдвигается почти в десять раз. То есть вы свой же вывод недооценили и он получается жёстче.
Вторая: утилизацию нельзя крутить свободно, её держит латентность. При U→1 очередь съедает p95, реальный потолок 60-70%. И, кажется, это и есть ответ на вопрос, почему провайдер выигрывает: не «нагрузка больше», а он держит высокую загрузку, не ломая latency, за счёт тысяч тенантов. Один арендатор так не умеет.
И момент по энергии: PUE применён к одним GPU (5,6×1,4), а остальная нода потерялась — CPU, вентиляторы, сеть. По факту забор 10-11 кВт, с PUE ~14-15. На итог это не влияет, но там ещё и модель оплаты другая: при аренде стойки в дата-центре платят не за фактические киловатт-часы, а за оговорённый лимит мощности. А 15 кВт на стойку - это уже серъезно.