Pull to refresh

Асимметричная криптография при лицензировании подписочного ПО на практическом примере

Reading time 12 min
Views 12K
Речь пойдет о том, как устроена защита десктопных программ, а также о типичной системе лицензирования и активации ключей. Активация применяется практически в любом коммерческом ПО, и то, на каких принципах она строится, довольно интересно, поэтому я решил написать эту статью.

В статье читайте:
  • Про «лицензию», «активацию», «хэш-функции», «цифровую подпись», «асимметричную криптографию» и (вкратце) про RSA и DSA, без формул и математики.
  • Чем механизм подписки (subscription) отличается от традиционного лицензирования.
  • Применение описанных принципов на примере EXE-протектора VMProtect.
Статья сугубо практическая, т.к. эти же самые идеи мы применяем в нашем стартапе — платформе RentSoft (мы рассказывали о нем в предыдущей статье). Фактически, я описываю, что находится у нас «под капотом», а также сообщаю о тех «граблях», на которые мы в свое время наступили. Ближе к концу статьи будет практическая демонстрация — иллюстрация механизма взаимодействия нашей платформы с протектором EXE-файлов VMProtect, нашим партнером.

Итак, приступим.

Как подписочная программа работает с сервером лицензий


Т.к. мы продвигаем идею ежемесячных микросписаний за использование коммерческого ПО (вместо традиционной продажи дорогих «разовых» ключей), для нас представляет интерес то, как программа должна взаимодействовать с сервером лицензий на периодической основе, чтобы ее нельзя было «обмануть».
Подписка — это возможность работы программы за ежемесячную абонентскую плату. Когда оплата не поступает, программа блокируется — и наоборот, если деньги пришли, она продолжает работать.
Естественно, продукт должен как-то определять, может ли он запускаться в каждый конкретный момент времени или нет (заблокировалась ли подписка из-за недостатка средств на счете). Для этого он использует доступ в интернет и регулярные (например, раз в 5 дней) обращения к специальному серверу лицензий.

Существует один ненадежный способ определения, разрешено ли программе работать. Она может спросить сервер лицензий: «Вот ключ, который ввел в меня пользователь. Я могу с ним сейчас работать?». Сервер лицензий ответит — «Да, можешь» или «Нет, подписка блокирована». Конечно, этот способ плох тем, что позволяет злоумышленнику легко подделать ответ сервера, к тому же программе потребуется доступ в интернет при каждом своем запуске (ведь она не может легко сохранить статус «могу работать» без того, чтобы это место было обнаружено злоумышленником).

Чтобы обеспечить безопасность, применяют другую схему, когда сервер лицензий выдает не ответ «да/нет», а некоторый квант информации — как бы временное разрешение на работу в течение небольшого срока (те же самые 5 дней), и программа при принятии решения опирается уже на этот квант.

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

Что такое активация ключа?


Традиционно при электронной покупке той или иной программы пользователю высылают сгенерированный специально для него ключ (или лицензию, или серийный номер — кто как называет). Пользователь затем вводит этот ключ в запущенный продукт и, в конечном итоге, активирует его (заставляет работать).

Как программа, приняв ключ, определяет, что он достоверен? Для этого существует два основных способа:
  1. Можно проверить, что ключ был сгенерирован по специальному алгоритму, известному программе, и, если это не так, блокировать работу. Так поступали в старину, когда еще не было всеобщей интернетизации. Естественно, в данном случае ключ может быть украден и в дальнейшем использован на другом компьютере, так что данный способ ненадежен.
  2. Программа может попросить для активации «обратной связи» у своего разработчика: пользователь сообщит разработчику ключ и какую-то дополнительную информацию о себе, а разработчик на основе этих сведений поймет, разрешить ли программе работать (активировать ее или нет).
Ясно, что второй способ лучше защищен от кражи ключа: ведь разработчик может контролировать количество активаций. Наличие «обратной связи» в этом случае обязательно: пользователь должен как-то сообщить разработчику свой ключ и другую информацию о себе, а разработчик — передать разрешение активации. Раньше, когда интернет еще не был столь распространен, все делалось по телефону: пользователь звонил в службу поддержки, диктовал свой ключ и, например, идентификатор оборудования своего компьютера. Взамен ему сообщали код активации — некоторую последовательность символов, которая, будучи введена в программу вместе с ключом, активирует ее.

Обратите внимание, что в данном примере в качестве «дополнительной информации о себе» использовался идентификатор оборудования — хэш-код, более-менее уникально идентифицирующий машину пользователя. У того, кто ключ украдет, этот хэш-код будет уже другим, так что, позвонив в службу поддержки, он ничего не добьется — там поймут, что производится попытка повторной активации.

Активация через интернет


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

В общем, у нас образовались следующие сущности:
  1. Ключ: то, что выдается пользователю при покупке.
  2. Идентификатор оборудования: некоторое число, с той или иной степенью уникальности характеризующая компьютер пользователя (на разных компьютерах оно будет разным).
  3. Код активации: закодированное «разрешение» программе запуститься, если пользователь имеет «на руках» определенный ключ и определенный идентификатор оборудования.
  4. Сервер лицензий: некоторый сервер в интернете, подконтрольный разработчику и умеющий по паре «ключ» + «идентификатор оборудования» возвращать пользователю код активации.
Очень важно понимать, чем Ключ отличается от Кода активации. Они связаны между собой через Идентификатор оборудования, что обеспечивает гарантию невозможности использования одного и того же Ключа одновременно на разных машинах.

Открытый и закрытый ключ: асимметричная цифровая подпись


Казалось бы, можно вычислять значение activationCode = md5(key + hardwareID): в этом случае и сервер лицензии сможет его сгенерировать, и программа у клиента — проверить правильность.

Однако здесь кроется серьезная опасность: раз сервер лицензий может вычислить это значение, то и злоумышленник — тоже. А значит, злоумышленник может сделать свой собственный сервер лицензий, «подставить» его вместо исходного и… получить неограниченный контроль над программой. Что же делать?

Говорят, что у каждой сложной задачи есть одно простое, логичное, легкое для понимания… неправильное решение. В данном случае таким неправильным решением была бы формула

activationCode = md5(key + hardwareID + salt)

где salt — некоторая «секретная» строка, известная только серверу лицензий разработчика.
Вообще, значение S = md5(X + salt) называют «цифровой подписью данных X с солью salt». Проверить цифровую подпись S означает снова вычислить md5(X + salt), уже на другой стороне, и сравнить результат с S: если совпало, то подпись верна, если нет — то имеет место подделка.
К сожалению, такой трюк с «солью» не пройдет, потому что программа у клиента тоже должна иметь возможность вычислить значение md5(key + hardwareID + salt), а значит, salt должен быть «вшит» в нее изначально. Конечно же, злоумышленник сможет дизассемблировать продукт и найти в нем salt, после чего организовать поддельный сервер лицензий. Мы вернулись к тому же, с чего начинали.

Но решение есть, и называется оно DSA. Оказывается, существует такой алгоритм, который, грубо говоря, позволяет получать хэш-код activationCode при помощи одного значения salt0, а проверять «правильность» этого хэш-кода — уже с совершенно другим значением salt1. Значение salt0 нужно держать в секрете — оно хранится только на сервере лицензий, в то время как salt1 никакой ценности для злоумышленника не представляет — зная его, он все равно не сможет получить salt0 и вычислить правильные коды активации.

Таким образом, сервер лицензий вычисляет цифровую подпись activationCode = DSA(X, salt0) и передает значение программе клиента, а программа — проверяет ее правильность при помощи алгоритма DSA_check(activationCode, X, salt1). В этом случае salt0 называют «закрытым ключом» (private key), а salt1 — «публичным ключом» (public key). «Публичным» ключ называется потому, что его можно хоть в газете напечатать — это не нанесет вред безопасности.
Подробности про алгоритм цифровой подписи DSA и основанный на том же принципе алгоритм шифрования данных RSA можно почитать в Википедии на русском языке. Вкратце — принцип работы RSA основан на так называемой «проблеме факторизации» — сложности разложения больших чисел на множители. Действительно, имея два каких-нибудь 2000-значных простых числа X и Y, можно за микросекунду вычислить их 4000-значное произведение S = X * Y. Однако, имея это произведение S и не зная X или Y изначально, за время существования Вселенной невозможно восстановить исходные X и Y, из которых оно составлено. На качественном уровне можно считать, что для составления цифровой подписи DSA используются числа X и Y (закрытый ключ), а для проверки — общеизвестный S.

Вернемся снова к подписке


В самом начале мы говорили, что подписка — это просто более частое обращение к серверу лицензий, который возвращает все новые и новые коды активации, в то время как старые коды активации — устаревают. Для такого устаревания алгоритм создания кода активации должен быть более сложным: в коде как минимум «зашивают» даты начала и окончания срока его действия (начала — чтобы пользователь не переводил часы слишком уж назад).

Работа «подписочной» программы с сервером лицензий выглядит следующим образом:
  1. Регулярно и в фоновом режиме программа делает попытку обращения к серверу лицензий, чтобы получить от него свежий код активации. На сервер передается ключ и идентификатор оборудования, упрощенно —
    GET /license_server?key=abcdef&hwid=1234
  2. Если попытка была неудачной, то ничего не происходит — никаких сообщений не выдается.
  3. В случае же удачи свежий код активации, выданный сервером, записывается в какое-нибудь постоянное хранилище (например, в Реестр) — все равно он не представляет интереса для злоумышленника, т.к. он не сможет его использовать у себя (код активации зависит от идентификатора оборудования, который будет у злоумышленника другим).
  4. Независимо от этого программа берет текущий код активации из постоянного хранилища и проверяет его цифровую подпись, используя ключ, идентификатор оборудования и открытый параметр salt1. Если код активации верен, то программа продолжает работать (возможно, попутно извлекая из кода какие-то необходимые данные — например, параметры шифрования кода виртуальной машины, как это делается в VMProtect).
Теперь, даже если интернет недоступен в течение нескольких дней, программа все равно будет работать — она извлечет текущий код активации из постоянного хранилища. И только если доступа в интернет нет длительное время, она заблокируется.

Т.е. при работе по подписке код активации запрашивается регулярно, а в случае работы «традиционной» схемы с активацией ключа — единоразово. В этом заключается техническое различие. Конечно, сервер лицензий должен работать надежно и выдерживать высокие нагрузки, поэтому у нас их две штуки в балансировке, написаны они на Java Netty — фреймворке для создания асинхронных веб-серверов, том самом, на который недавно перевел свой поиск Twitter. Но это уже тема для отдельной статьи.

Пример создания подписочной версии вашей программы


Выше была теория о том, как может работать сервер лицензий, обслуживающий запросы на проверку статуса подписок. Теперь немного коснемся практики и посмотрим, как разработчики VMProtect Software реализовали эту теорию при создании подписочной версии протектора VMProtect для нашей платформы RentSoft.

Создание приложения HelloWorld и его MAP-файла


Для начала создадим программу типа «Hello, world!», которую и будем защищать VMProtect-ом. Будем использовать MS Visual Studio 2010: создадим в ней новый проект типа «Win32 Application» и назовем его HelloWorld. Главный файл будет выглядеть примерно вот так:
#include "stdafx.h"

ATOM MyRegisterClass(HINSTANCE hInstance)
{ ... }

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{ ... }
...
Сразу переключим режим построения проекта на Release в Диспетчере конфигураций. После этого включим генерацию MAP-файла: он будет содержать адреса всех функций внутри EXE-файла, чтобы их смог потом найти VMProtect. Это делается в свойствах проекта, в русской версии путь такой: кликнуть правой кнопкой по имени проекта в Обозревателе решений, дальше выбрать Свойства конфигурации — Компоновщик — Отладка — Создавать файл сопоставления.

Теперь нажмем F5 и убедимся, что проект откомпилировался и запустился. Мы получили в директории Release 2 файла: HelloWorld.exe и HelloWorld.map, с которыми и будем работать.



Мастер регистрации продукта на сервере лицензий RentSoft


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

Это довольно просто: по адресу http://rentsoft.ru/soft/add/ выбираем вариант «VMProtect RentSoft Edition (бесплатно для клиентов RentSoft)», вводим свой e-mail (подтверждать его, кстати, не потребуется) и попадаем в Мастер регистрации продукта. В нем мы вводим следующие данные:
  • Название продукта, краткое и подробное описание (можно использовать HTML).
  • Изображение коробки с продуктом (или его логотип).
  • Выбираем желаемую стоимость подписки в месяц — например, 79 рублей.
После сохранения этой информации откроются следующие пункты мастера.



Файл лицензии необходим для привязки защищаемой программы к продукту, который мы только что зарегистрировали на сервере лицензий. Сохраните его в директорию с исходным кодом проекта — в нашем случае рядом с HelloWorld.cpp.

Запуск VMProtect для создания подписочной версии


Далее запустите VMProtect RentSoft Edition (ссылка на его скачивание его инсталлятора приведена на следующем шаге мастера). Это протектор EXE-файлов, созданный нашими партнерами — компанией VMProtect Software. Откройте через Файл — Открыть EXE-файл проекта Release/HelloWorld.exe и установите параметры, как на скриншоте (главное — укажите путь к файлу лицензии проекта):



Теперь перейдите на вкладку Процедуры для защиты и выберите на ней те процедуры и функции, которые хотите защитить VMProtect-ом (код этих процедур будет переведен в код виртуальной машины VMProtect и привязан к продукту, зарегистрированному ранее на сервере лицензий).


В нашем случае мы указали 2 функции: MyRegisterClass и WndProc — именно их теперь хакер не сможет «взломать». Обратите внимание: нужно выбрать Тип компиляции «Ультра» (самый надежный), а также поставить флажок «Привязать к серийному номеру» в «Да». Старайтесь защищать те функции программы, которые некритичны к производительности, потому что виртуальная машина работает, естественно, медленнее, чем «родной» процессор. (Не забудьте после всех манипуляций сохранить файл проекта VMProtect: в нем содержатся все опции, которые вы выбрали, чтобы при следующей защите не нужно было их выбирать заново: Файл — Сохранить проект как...)
Что будет, если злоумышленник все же сможет расшифровать код виртуальной машины VMProtect для какой-то защищенной программы (что крайне сложно само по себе — вспомните, сколько попыток было расшифровать, например, Skype, который тоже использует технологию виртуализации кода)? Это не даст ему доступа к другим программам: ведь для работы виртуальной машины требуется информация, содержащаяся в коде активации, а она для каждой программы — разная. Так что, не имея кода активации, выданного сервером лицензий, заставить программу работать физически невозможно. Подделать сервер лицензий тоже не получится — помните про технологию открытых/закрытых ключей?
Наконец, выберем Проект — Компиляция и подождем несколько секунд. В результате рядом с HelloWorld.exe появится файл HelloWorld.vmp.exe — его защищенная версия. Именно этот файл и является «подписочной» версией вашего продукта.

Проверяем, что получилось


Если теперь запустить HelloWorld.vmp.exe, мы увидим вот что:


Т.е. программа при первом запуске просит пользователя ввести «код подписки», который он получил на одной из витрин RentSoft, когда подключил себе ваш продукт. Естественно, сервер лицензий RentSoft в курсе всех активных кодов подписки, что позволяет ему обрабатывать запросы от программы и проверять код, введенный пользователем.

Теперь нужно ввести код подписки. Где его взять для проверки? Мы могли бы здесь отправить читателя на витрину продукта и сказать «подпишитесь на продукт — получите код подписки», но можно поступить проще. Мастер регистрации продукта RentSoft, в котором мы только что находились, позволяет создавать тестовые подписки на сервере лицензий. Т.е. все, что вам требуется, — это нажать кнопку Создать в форме:


В итоге вы получите тестовый код подписки, который останется ввести в окно активации программы:



После этого программа запустится и больше не будет ничего спрашивать у пользователя до тех пор, пока на его счете имеются деньги (т.е. пока подписка активна).

Предположим, денег на счете для продления нет...


Что произойдет, когда у пользователя кончатся деньги и подписка заблокируется? Не обязательно ждать месяц, чтобы это увидеть: можно воспользоваться все той же формой работы с тестовыми подписками, которая теперь примет вот такой вид:


Заблокируйте тестовую подписку, после чего перезапустите программу дважды: во второй раз она заблокируется с выводом сообщения о недостатке средств на счете. (Почему дважды? Считайте, вам дается последний шанс воспользоваться продуктом перед тем, как он заблокируется.)

Возможно, вы спросите, что будет, если подписку заблокировать, а интернет — отключить, и уже после этого запустить программу. Тогда программа, естественно, не узнает, что она не может функционировать, и будет работать… но только следующие 5 дней. Через 5 дней программа потребует обязательный доступ в интернет и откажется запускаться, если связи не будет: истечет так называемый «период толерантности». Так что пользователь все равно должен будет пополнить свой счет, рано или поздно.

Что касается автоматической разблокировки при поступлении средств на счет, то вы можете проверить, как это работает, нажав кнопку «Включить» и перезапустив программу. Если связь с интернетом имеется, программа немедленно продолжит работу.

В заключение — про наши каналы продаж


Хорошая защита без высоких продаж имеет, разве что, академический интерес. Мы в RentSoft это понимаем, поэтому предоставляем разработчикам десктопного софта не только механизм подписочного лицензирования, но также и каналы продаж — витрины полусотни крупных интернет-провайдеров с общей абонентской базой более 4 млн человек. К слову, когда мы опубликовали предыдущую статью, их было на 2 меньше: за неделю добавился крупный московский провайдер NetByNet и питерский InterZet (ссылки ведут на наши витрины у них). В среднем у нас еженедельно прибывают по 2-3 провайдера, которые подключаются к нам полностью самостоятельно — мы уже не успеваем их модерировать.

Главное преимущество продажи по схеме ежемесячной подписки через интернет-провайдеров в том, что абоненты и так уже ежемесячно приносят провайдеру 400-500 рублей оплаты за интернет. Мы умеем списывать оплату за ПО (которое в этом случае выглядит, как дополнительная услуга провайдера) прямо со счета абонента. Поэтому, когда пользователь подключает вашу программу на сайте провайдера или в его личном кабинете, он оплачивает ее в том же чеке, которым платит за интернет! Одним словом, это очень эффективный канал.

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

Присоединяйтесь!
Tags:
Hubs:
+35
Comments 96
Comments Comments 96

Articles