сайт в бете
нашли баг? напишите
левин. записаться
весь блог

Свой лицензионный сервер: как я защитил десктоп-продукт от копирования

Как выдавать офлайн-проверяемые лицензии на десктоп-продукт: Ed25519, подписанные токены и один лёгкий сервер вместо постоянных запросов в облако.

Л
Проект Левин
автор
Свой лицензионный сервер: как я защитил десктоп-продукт от копирования

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

Зачем городить свой сервер

Простой способ защиты — дёргать сервер при каждом запуске: «этому пользователю можно?». Работает, пока есть интернет и пока сервер жив. Отвалилась сеть — программа не открывается. Умер сервер через два года — продукт умер у всех, кто его купил. Для десктопной утилиты размен так себе.

Мне нужно было другое: чтобы клиент проверял лицензию сам, без обращения к серверу, но подделать её было нельзя. Классическая задача на асимметричную подпись, и решается она чище, чем кажется.

Идея: подпись вместо базы

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

Схема такая:

  • сервер держит приватный ключ и никому его не отдаёт;
  • в клиент зашит публичный ключ (у меня он лежит прямо в сборке .NET-приложения);
  • при покупке сервер собирает данные лицензии, подписывает их и отдаёт токен;
  • клиент проверяет подпись публичным ключом локально.

Приватный ключ на сервере и публичный ключ в клиенте, между ними подписанный токен

Подделать токен без приватного ключа нельзя. Проверить — можно где угодно и без интернета.

Формат токена

Токен у меня выглядит как payloadB64url.sigB64url — две части через точку. Похоже на JWT, но это не JWT, и разница здесь принципиальная.

Токен из двух частей, разделённых точкой: данные и печать-подпись

В payload лежит обычный JSON: версия, ключ лицензии, имя владельца, тип, дата выпуска, срок. Дальше идёт деталь, на которой легко споткнуться. Подписывать нужно именно байты уже закодированной строки payloadB64url. Собирать JSON заново для проверки нельзя: клиент и сервер соберут его чуть по-разному — порядок полей, пробелы, — и подписи разъедутся, валидные лицензии начнут отваливаться. Когда подписываешь байты строки, проверка идёт байт в байт, и проблема исчезает сама.

Ed25519 и почему он

Взял Ed25519. Причины приземлённые:

  • короткие ключи и подписи, токен получается компактным;
  • есть прямо в стандартной библиотеке — node:crypto на сервере, штатная криптография на стороне .NET;
  • быстрая проверка и никакой возни с выбором параметров, как у RSA.

Внешних крипто-библиотек тянуть не пришлось. Меньше зависимостей — меньше того, что сломается через год.

Стек, который не жалко поднять

Сервер — один сервис на Hono (TypeScript, запускаю через tsx). База — SQLite через better-sqlite3, файл лежит в Docker-томе. Сверху nginx как reverse-proxy. Всё.

Был соблазн взять «взрослую» СУБД, очередь, отдельный микросервис на подпись. Не понадобилось. Лицензии выдаются редко, нагрузка копеечная, а файловая SQLite в томе бэкапится копированием одного файла. Один контейнер, который спокойно живёт на дешёвом VPS.

Тот же сервис заодно отдаёт лендинг и два веб-кабинета — клиентский и владельца. Городить под это отдельный фронтенд-стек смысла не было.

Чего эта защита не делает

Честно про границы, потому что «защита от копирования» звучит громче, чем оно есть.

Подпись гарантирует, что лицензию нельзя подделать или выписать в обход сервера. Скопировать саму программу вместе с валидным токеном на другую машину она не помешает. Привязка к железу, отзыв лицензий, лимит активаций — это отдельный слой, который навешивается поверх, когда реально нужен. Многим продуктам он не нужен вовсе. Честный покупатель получает рабочий офлайн-продукт. А массовое воровство отсекается само собой: скачать бесплатный кряк проще, чем красть чужие подписанные токены.

Приватный ключ — единственная точка, которую надо беречь. Утёк ключ — вся схема рухнула. Он живёт только на сервере и не попадает ни в сборку клиента, ни в репозиторий.

С чего начать

Если хочется повторить, минимальный путь:

  1. сгенерировать пару Ed25519, публичный ключ вынести в клиент;
  2. определить формат payload и зафиксировать правило: подписываем байты строки;
  3. поднять любой лёгкий сервер с одним эндпоинтом выдачи токена;
  4. на клиенте написать проверку подписи — это буквально несколько строк.

Базы, кабинеты, привязки к железу наращиваются потом, по мере того как продукт этого запросит. Начинать со строительства полной крепости смысла нет. Начните с подписи, которую нельзя подделать, и офлайн-проверки, которая работает без вас.

теги #лицензирование#ed25519#десктоп#подпись#защита от копирования#.net

один разговор — и поймём, чем я могу помочь.

В эпоху ИИ человеку нужен человек. Сяду рядом и доведу до результата — встреча длится столько, сколько нужно. Без скрипта продаж и пакетов «за 999 000 ₽». Если пойму, что помочь не смогу, — скажу сразу.