Защитить свой продукт

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


Генерация лицензионного ключа

Как мы уже знаем, для генерации ключа можно воспользоваться "привязкой" либо к пользовательским имени и фамилии, адресу электронной почты, и другим данным, либо же к идентификационным данным компьютера. Достоинства и недостатки каждого из этих способов мы с вами достаточно подробно обсуждали, поэтому повторяться здесь я не вижу смысла. Вместо этого давайте посмотрим, как на практике может быть реализован каждый из этих подходов. Для реализации выберем старый добрый Delphi, который наверняка знаком каждому из наших читателей и который, кроме того, наиболее часто используется для написания небольших shareware-приложений, как раз и нуждающихся в подобного рода защите.

Для начала рассмотрим наиболее простой вариант генерации лицензионного ключа, подразумевающий работу исключительно на основе данных, предоставленных пользователем по своей доброй воле. В принципе, написать подобного рода генератор ключей совсем не сложно: вы можете взять буквально отдельные буквы из имени, фамилии, адреса электронной почты и других заполняемых при покупке программного продукта полей, и из них уже соорудить что-то, что будет, с точки зрения пользователя, напоминать регистрационный ключ. При этом ключ должен быть достаточно длинным для того, чтобы пользователь не мог ввести его, просто случайно "тыкая клавиши". Длина ключа является также признаком его солидности в глазах пользователя, однако лучше не усердствовать слишком уж сильно, потому что слишком длинные ключи (которые обычно используются при криптографической защите) передаются в виде отдельных файлов. Можно, конечно, реализовать работу и с ключами-файлами; это не слишком усложнит вашу программу. Но мы все-таки пока что обойдемся даже без таких достаточно, в общем-то, скромных "наворотов" и рассмотрим простейшую ситуацию с генерацией ключа оптимального размера, который будет напоминать пользователю ключи от множества других купленных им программных продуктов, начиная с Windows и заканчивая какими-нибудь казуальными игрушками.

Схема генерации ключа, реализацию которой вы можете увидеть в листинге 1, более чем проста. Мы берем два входных параметра (имя и фамилию пользователя), из которых, для того чтобы не накладывать каких-то особых ограничений на их длину, воспользуемся только двумя первыми буквами. Эти буквы расставим в определенных местах нашего регистрационного ключа, который будет иметь длину 32 знаков и разделяться на три равные части. Оставшееся пространство заполним случайным образом буквами и цифрами, чтобы ключ выглядел именно так, как должны выглядеть регистрационные ключи для программного обеспечения. В общем-то, именно это все вы и можете увидеть в листинге 1.

Листинг 1

function GenerateSerialNumber(Name1, Name2: string): string;
var
n1, n2: string;
i: integer;
const
Letters: string = '1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ';
begin
Randomize;
Result := '';
//. First section
for I := 0 to 9 do
Result := Result + Letters[Random(36) + 1];
Result := Result + '-';
//. Second section
for I := 0 to 9 do
Result := Result + Letters[Random(36) + 1];
Result := Result + '-';
//. Third section
for I := 0 to 9 do
Result := Result + Letters[Random(36) + 1];
n1 := UpperCase(Name1);
n2 := UpperCase(Name2);
//. Filling in 'key characters'
Result[2] := n2[1];
Result[4] := n1[2];
Result[8] := n2[2];
Result[14] := n1[1];
Result[18] := n2[2];
Result[24] := n1[2];
Result[30] := n2[1];
end;

Собственно говоря, наверное, сам по себе код листинга вряд ли способен вызвать вопросы, но, думаю, кое-какие вещи не помешает пояснить. Во-первых, конечно, совсем не обязательно разбивать ключ с помощью символов "-". Просто это, скажем так, общепринятая практика, пришедшая еще из тех времен, когда ключи печатались на коробках с программными продуктами, продававшимися в магазине, или в печатных пользовательских руководствах. Сегодня, конечно, острой потребности делать ключ читабельным для человека уже нет, потому что каждому пользователю доступна вся мощь передовой технологии copy/paste, но множество разработчиков ПО по-прежнему применяют такие ключи. Во-вторых, совсем не обязательно делать таким широким набор символов, используемых в ключе. Если ограничиться, например, первыми пятью буквами английского алфавита, которые будут составлять тот "информационный шум", из которого, в основном, и состоит регистрационный ключ, то это может служить дополнительным механизмом для контроля его подлинности, потому что наверняка создатели генератора ключей не обратят внимание на подобную тонкость. В-третьих, не обязательно расставляемые в разных местах ключа символы должны быть частью пользовательских данных. С тем же успехом можно вставить и заранее заданные буквы или цифры. Но хоть какая-то часть ключа от пользовательских данных должна зависеть.


Проверка ключей

Что ж, теперь, когда у нас есть генератор ключей, нужен код, который будет в программе проверять их подлинность. Давайте рассмотрим такой программный код, который будет соответствовать нашему листингу 1 - то есть, который сможет проверять сгенерированные записанной в нем программой ключи.

Этот код тоже достаточно прост и базируется на всей информации о ключе, изложенной выше. Так, код проверяет наличие на своих местах "ключевых" символов, удостоверяющих подлинность данного ключа и его принадлежность конкретному пользователю, проводит проверку нужной длины ключа и наличия в нем на нужных местах разделителей-минусов. Больше, собственно говоря, ему проверять в нашем случае особенно и нечего. Так что давайте взглянем на листинг 2.

Листинг 2

function VerifySerialNumber(Name1, Name2, SN: string): boolean;
var
n1, n2: string;
i: integer;
begin
Result := true;
//. Checking length of registration key
Result := Result or (Length(SN) = 32);
//. Checking dividers
Result := Result or (SN[11] = '-');
Result := Result or (SN[22] = '-');
n1 := UpperCase(Name1);
n2 := UpperCase(Name2);
//. Checking 'key characters'
Result := Result or (SN[2] = n2[1]);
Result := Result or (SN[4] = n1[2]);
Result := Result or (SN[8] = n2[2]);
Result := Result or (SN[14] = n1[1]);
Result := Result or (SN[18] = n2[2]);
Result := Result or (SN[24] = n1[2]);
Result := Result or (SN[30] = n2[1]);
end;

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

На самом деле объединять все проверки регистрационного ключа в одну функцию, да еще и называть ее при этом таким говорящим названием, как в приведенном в статье примере - это не просто плохая, а очень плохая идея. Почему? Дело в том, что при взломе программ тот, кто взламывает их, обязательно занимается тем, что в английском языке называется reverse engineering - то есть, переводя буквально, разборка программы на составные части с последующим выяснением того, что каждая из таких частей делает. Для этого, конечно, используются соответствующие инструменты - дизассемблеры, отладчики и т.д. и т.п. При исследовании программы в отладчике вызов функции, которая имеет столь недвусмысленное название, взломщик просто не сможет не заметить, и ваша программа будет взломана в два счета. Поэтому лучше немного усложнить жизнь любителям халявы и не выставлять свою проверку регистрационного кода перед отладчиком в совсем уж голом виде. Еще лучше будет разнести её по времени и месту по всему коду программы. Даже если взломщик найдет несколько проверок (а так, скорее всего, и будет), то он вполне удовлетворится ими и не будет искать абсолютно все те участки кода, где проверяется ваш регистрационный ключ. Впрочем, вопросы подобной "психологической" защиты мы обсудим с вами несколько позже.

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

(Продолжение следует)

Вадим СТАНКЕВИЧ

Версия для печатиВерсия для печати

Номер: 

39 за 2010 год

Рубрика: 

Software
Заметили ошибку? Выделите ее мышкой и нажмите Ctrl+Enter!