Защищаем JavaScript от копирования


14-08-2018
Денис Л.
JavaScript
17
2570
Защищаем JavaScript от копирования

Не так давно я разработал JavaScript-калькулятор для расчёта стоимости услуг, с большим количеством взаимосвязанных параметров. Руководство поставило задачу защитить данный скрипт от копирования, чтобы конкуренты не смогли использовать его на своих сайтах. Искал различные решения, ничего подходящего не нашёл, поэтому начал писать собственное решение. Представляю его ниже.

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

По итогам работ в браузере Вы увидите нечто такое:

<script>glob('KKGZ1bmN0aW9uKCQpIHsNCgkkKGRvY3VtZW50KS5yZWFkeSggZnVuY3Rpb24gKCkgew0KDQoJCWlmKCAkKCdkaXYnKS5pcygnLmNhbGMnKSApIHsNCg0KCQkJJCgnLmNhbGMgPiAucm93JykuZWFjaChmdW5jdGlvbihpKSB7DQoJCQkJJCh0aGlzKS5hdHRyKCdkYXRhLXN0ZXAnLCBpKzEpOw0KCQkJfSk7DQoNCgkJCSQoJy5jYWxjID4gW2Rhdgetc3RlcD0iMSJdJykuYWRkQ2xhc3MoJ2FjdGl2ZScpOw0KDQoJCQkkKCdhW2hyZWY9Ii9zdG9pbW9zdC8iXScpLmNsaWNrKGZ1bmN0aW9uKGUpIHsNCgkJCQllLnByZXZlbnREZWZhdWx0KCk7DQoJCQkJJCgnLmNhbGMnKS5mYWRlVG9nZ2xlKCk7DQoJCQkJeWFDb3VudGVyMTM4ODc0NTcucmVhY2hHb2FsKCdjYWxjX29wZW4nKTsNCgkJCX0pOw0KDQoJCQlpZiAoICQodzZW5kJyk7DQoJCQkJCQl5YUNvdW50ZXIxMzg4NzQ1Ny5yZWFjaEdvYWwoJ2NhbGNfc2VuZCcpOw0KCQkJCQl9DQoJCQkJfSk7DQoJCQkJc2V0VGltZW91dChmdW5jdGlvbigpIHsNCgkJCQkJJCgnLmNhbGMnKS5mYWRlT3V0KDc3Nyk7DQoJCQkJCSQoJ2EuYnV0dG9uc0ZvckRvY3MnKS5jc3MoJ21hcmdpbi10b3AnLCAnMTVweCcpOw0KCQkJCX0sNzAwMCk7DQoJCQl9KTsNCg0KCQkJJCgnI3RoYW5rc0J1dHRvbicpLm9uKCdjbGljaycsIGZ1bmN0aW9uKCkgew0KCQkJCSQoJy5jYWxjJykuZmFkZU91dCgxMDApOw0KCQkJCSQoJ2EuYnV0dG9uc0ZvckRvY3MnKS5jc3MoJ21hcmdpbi10b3AnLCAnMTVweCcpOw0KCQkJCWNhbGNSZWJvb3QoKTsNCgkJCX0pOw0KDQoJCQkkKCcuY2FsYyAuY2xvc2UnKS5vbignY2xpY2snLCBmdW5jdGlvbigpIHsNCgkJCQkkKCcuY2FsYycpLnJlbW92ZSgpOw0KCQkJCSQoJ2EuYnV0dG9uc0ZvckRvY3MnKS5jc3MoJ21hcmdpbi10b3AnLCAnMTVweCcpOw0KCQkJfSk7DQoNCgkJfTsNCg0KCX0pOw0KfSkoalF1ZXJ5KTs=')</script>

При этом, все зашифрованные скрипты будут работать корректно. Визуально, опытный взгляд программиста сразу определит кодирование через base64. Но при попытке расшифровать строку любым base64 декодером, будет ошибка... Если вставить скрипт в alert (такой метод также рекомендуют на форумах для дешифровки кода), то результат также будет нулевым.

При этом, ведь никто не знает, что именно здесь зашифрован скрипт. Ведь это может быть какой-то параметр, либо текст, либо изображение. Через base64 можно зашифровать всё, что угодно. Это собьёт с толку любителей копипаста.

Поищем в коде функцию glob, которой передаётся шифрованная строка.

Вот она: glob=function(s){sfd(rty(s.substring(-~[])));

Видим ещё несколько функций sfd и rty. Ищем эти функции.

Вот они: sfd=this["\x65\x76\x61\x6C"];rty=this["\x61\x74\x6F\x62"];

На этом месте многие закончат попытки расшифровки и оставят Ваш сайт в покое.

А мы с Вами разберём всё подробнее.

Итак, как защитить javascript от копирования на своём сайте

Первым делом, указываем в футере сайта путь на наш скрипт и тут же кодируем его:


<?$filebase64='K'.base64_encode(file_get_contents('/home/bitrix/www/js/script.js'));?>

В строке выше мы говорим интерпретатору php взять файл script.js, далее закодировать его через base64, далее прибавить строку 'K' и всё это записать в переменную $filebase64

Добавление строки 'K' (это может быть любая латинская буква или комбинация букв или цифр) защищает нас от того, что желающий скопировать Ваш скрипт расшифрует его с помощью alert или онлайн-дешифратором. Ведь с дополнительными символами скрипт не будет работоспособен.

Затем где-то дальше в коде вызываем скрипт:


<script>glob('<?=$filebase64?>')</script>

Пусть этот скрипт вызывается отдельно, подальше от других скриптов и ссылок на скрипты.

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


sfd=this["\x65\x76\x61\x6C"];rty=this["\x61\x74\x6F\x62"];glob=function(s){sfd(rty(s.substring(-~[])));}

Разбираем подробно что здесь происходит.

Наша основная функция glob принимает один параметр 's'. Параметр 's' сразу передаётся функции substring с параметром '-~[]', (это равно 1 в зашифрованном виде) которая берёт строку в параметре 's', начиная с первого символа (который указан в параметре в зашифрованном виде) и до конца строки. Следовательно, если мы в php коде в качестве сроки прибавляли более одного символа, скажем 3, то нам нужно будет в функции substring указать 2+(-~[]). Либо, путём шифрования цифр через побитовые операторы мы можем создать запутанную формулу, часть переменных которой мы можем прятать в cookies или sessionStorage, что сделает крайне затруднительным понимание того, какое количество символов необходимо отбросить для дешифровки кода.

Пример замены цифр через побитовый оператор '~':

-1 можно заменить на: ~[]

1 можно заменить на: -~[]

0 можно заменить на: ~~[]

Аналогично можно заменить значения true и false (true аналогично 1 при нестрогом сравнении, а false аналогично 0, также при нестрогом сравнении).

Далее полученный результат принимает функция rty(). Эта функция представляет собой набор символов, в частности: this["\x61\x74\x6F\x62"];

Попробуйте ввести это в консоли браузера и Вы увидите, что на самом деле делает эта функция. В частности, Вы увидите:


ƒ atob() { [native code] }

Т.е., набор символов - это зашифрованная функция atob, которая, согласно описанию на MDN делает Decode a base-64 encoded string, т.е. декодирует строку, полученную из base-64.

Полученный результат декодирования получает функция sfd(). Она также представляет собой набор символов: this["\x65\x76\x61\x6C"];

Вы уже догадались, что нужно сделать? :) Выполните в консоли браузера и Вы увидите:


ƒ eval() { [native code] }

Здесь, думаю, уже объяснять ничего не надо. Все мы знаем, что функция eval выполняет скрипт, полученный из строки. "Bad practice", как сказали бы многие, для обычного кода, но в нашем случае это безопасная и нужная нам функция для реализации нашей идеи. К тому же, напрямую к этой функции пользователь не сможет получить доступ.

Наверное, Вы задались вопросом, а каким же образом функции зашифрованы в наборе символов? Очень просто: набор символов - это текст, преобразованный в шестнадцатеричную систему счисления. Т.е. это текст, в формате Hex (hexadecimal). Через hex можно зашифровать любые символы.

Таким образом, наша расшифрованная функция выглядит так:


  glob = function(s) {
    eval(
      atob(
        s.substring(1)
      )
    );
  }

Специально разбил по строчкам, чтобы было наглядно.

В итоге, отбрасываем первый символ шифрованной строки (при этом символов может быть хоть 353 и об этом никто не сможет быстро догадаться), потом дешифруем, потом выполняем через eval.

Вы можете пойти и далее. Если каким-то образом кто-то всё же расшифрует Ваш скрипт, немного усложните его, чтобы людям было сложнее модицифировать его. Например, можно поговорить о побитовом операторе ^, c помощью которого можно творить чудеса... Например, a^b^b будет равно a. В качестве b может быть использован ключ, который мы зашифруем где-то выше... Но об этом расскажу как-нибудь в других постах...

Всё будет работать как и раньше, но собьёт с толку нехороших копипастеров :)

Подписывайтесь на группу в ВКонтакте, чтобы всегда быть в курсе актуальных выпусков Web development blog!

Читайте также:

Node.js: как рекурсивно сжать все изображения на сайте за 1 час

Как запустить Node.js на обычном хостинге

Сохраняем данные формы на сайте при перезагрузке страницы, с помощью JavaScript и sessionStorage