Hiding JavaScript code on the front-end from outsiders

Let’s imagine a situation where you and your team write interesting and complex JavaScript code. Moreover, this code should be used in the project as soon as possible. If its functionality is truly unique, then during the development process, both you and the team members quite reasonably have the question: “How to protect the code from being copied?”.

© Designed by Vecteezy

How to protect the code: web sockets, cryptors and obfuscation

Of course, we all understand perfectly well that JavaScript runs on the browser side. And any encrypted code can always be decrypted, if you know the principles of the browser. Therefore, you can only try to make it difficult to understand this code, and this, in turn, will greatly prevent an attacker from modifying it to fit his needs.

So, there are several options for protecting the code:

  1. Use web sockets.
  2. Use cryptors.
  3. Obfuscate the code.

Cryptors bring the code into an unreadable form, using, as a rule, base64 (which inevitably leads to an increase in the amount of code by about 30%). Then, the so-called “salt” is added to the result, a set of characters that is used as a key when the code is interpreted by the decoder function. Well, then the entire line of code is usually executed via eval(). The problem of cryptors is that if you understand the principle of their work, cut off the “salt” and decode, then immediately all the code becomes available in its original form.

Obfuscators, on the other hand, change the code itself by inserting unreadable characters between operators, changing the names of variables and functions to a set of visually incomprehensible characters. At the same time, the amount of code is also greatly increased due to the insertion of additional pseudo-code, as well as the replacement of characters with hex, when any characters are converted to their hex values (for example, the Latin letter 'e' can be written as '\ x65', and this is fine interpreted by any browser). You can see how the translation to hex works through any Text To Hex service, for example on Crypt Online.

The use of obfuscators greatly complicates further debugging of the code, since this is an irreversible process. In addition, in some cases they may affect the functionality of the code. You can try obfuscators on any obfuscation service, for example this or that. Also on the web, you can find paid obfuscator cryptors, in the settings of which you will be able to specify the degree of protection, the lifetime of the script, etc. In this case, the script will be tightly bound to your domain, i.e. unique value for your host will be used for decryption. The cost of such cryptors starts from $45. In addition, before obfuscation, you can pre-minimize the code by replacing all the names of variables and functions with their single-character synonyms. A great and very popular tool on Node.js is UglifyJS, which works both in automatic mode (say, through Gulp) and in command line mode.

Go to UglifyJS

There is also the well-known Closure Compiler from Google, which, apart from minimization, analyzes JavaScript code, deletes dead code, rewrites and minimizes what is left. It also checks syntax, variable and type references, and warns about common JavaScript errors. It has a well documented API.

In addition to the proposed methods, you can do the following:

  • use WebStorage and hide JavaScript code there;
  • hide part of the code in a separate file on the server and call it via XMLHttpRequest;
  • use bitwise operators to replace numbers with sets of brackets and signs ~;
  • substitute standard JavaScript functions and methods.

All this, of course, will not be one hundred percent protection. Nevertheless, the more complicated the decryption process, the greater the chances that after many unsuccessful attempts, fans to copy someone else’s code will leave your site alone.

Encryption of code on the example of a JavaScript calculator

Not so long ago, I developed a JavaScript calculator for calculating the cost of services, with a large number of interrelated parameters. The management set the task of protecting this script from being copied so that competitors could not use it on their websites. I searched for various solutions, found nothing suitable, so I started writing my own. I present it below.

I draw your attention to the fact that any code can be decrypted, just for this it takes time. Therefore, this solution is, of course, not perfect. However, it takes time, attentiveness and perseverance to uncover it. And this may alienate your competitors from the idea of ​​copying your script. After several unsuccessful attempts, most of them will simply look for an analogue of a similar script on other resources.

As a result of the work in the browser, you will see something like this:

<script>glob('KKGZ1bmN0aW9uKCQpIHsNCgkkKGRvY3VtZW50KS5yZWFkeSggZnVuY3Rpb24gKCkgew0KDQoJCWlmKCAkKCdk
aXYnKS5pcygnLmNhbGMnKSApIHsNCg0KCQkJJCgnLmNhbGMgPiAucm93JykuZWFjaChmdW5jdGlvbihpKSB7DQoJCQkJJCh0aGl
zKS5hdHRyKCdkYXRhLXN0ZXAnLCBpKzEpOw0KCQkJfSk7DQoNCgkJCSQoJy5jYWxjID4gW2Rhdgetc3RlcD0iMSJdJykuYWRkQ2xh
c3MoJ2FjdGl2ZScpOw0KDQoJCQkkKCdhW2hyZWY9Ii9zdG9pbW9zdC8iXScpLmNsaWNrKGZ1bmN0aW9uKGUpIHsNCgkJCQllLn
ByZXZlbnREZWZhdWx0KCk7DQoJCQkJJCgnLmNhbGMnKS5mYWRlVG9nZ2xlKCk7DQoJCQkJeWFDb3VudGVyMTM4ODc0NTcuc
mVhY2hHb2FsKCdjYWxjX29wZW4nKTsNCgkJCX0pOw0KDQoJCQlpZiAoICQodzZW5kJyk7DQoJCQkJCQl5YUNvdW50ZXIxMzg4N
zQ1Ny5yZWFjaEdvYWwoJ2NhbGNfc2VuZCcpOw0KCQkJCQl9DQoJCQkJfSk7DQoJCQkJc2V0VGltZW91dChmdW5jdGlvbigpIHsN
CgkJCQkJJCgnLmNhbGMnKS5mYWRlT3V0KDc3Nyk7DQoJCQkJCSQoJ2EuYnV0dG9uc0ZvckRvY3MnKS5jc3MoJ21hcmdpbi10b3A
nLCAnMTVweCcpOw0KCQkJCX0sNzAwMCk7DQoJCQl9KTsNCg0KCQkJJCgnI3RoYW5rc0J1dHRvbicpLm9uKCdjbGljaycsIGZ1bmN
0aW9uKCkgew0KCQkJCSQoJy5jYWxjJykuZmFkZU91dCgxMDApOw0KCQkJCSQoJ2EuYnV0dG9uc0ZvckRvY3MnKS5jc3MoJ21hcm
dpbi10b3AnLCAnMTVweCcpOw0KCQkJCWNhbGNSZWJvb3QoKTsNCgkJCX0pOw0KDQoJCQkkKCcuY2FsYyAuY2xvc2UnKS5vbig
nY2xpY2snLCBmdW5jdGlvbigpIHsNCgkJCQkkKCcuY2FsYycpLnJlbW92ZSgpOw0KCQkJCSQoJ2EuYnV0dG9uc0ZvckRvY3MnKS5jc3
MoJ21hcmdpbi10b3AnLCAnMTVweCcpOw0KCQkJfSk7DQoNCgkJfTsNCg0KCX0pOw0KfSkoalF1ZXJ5KTs=')</script>

At the same time, all encrypted scripts will work correctly. Experienced view of the programmer will immediately visually determine the encoding via base64. But when trying to decode a string with any base64 decoder, there will be an error. If you insert the script in the alert (this method is also recommended on the forums to decrypt the code), then the result will also be zero.

At the same time, no one knows that the script is encrypted here. This may be some parameter, text or image. Through base64 you can encrypt anything.

Let’s look in the code for the function glob(), which is passed an encrypted string. Here it is: glob=function(s){sfd(rty(s.substring(-~[])));

We see some more functions of sfd() and rty(). We are looking for these features. Here they are:

sfd=this["\x65\x76\x61\x6C"];rty=this["\x61\x74\x6F\x62"];

At this point, many will finish the decryption attempt and leave your site alone.

Consider the algorithm in more detail.

How to protect JavaScript from copying on your site

First of all, specify the path to the script in the footer of the site and immediately encode it:

<?$filebase64='K'.base64_encode(file_get_contents('<script file path>'));?>

In the line above, we tell the PHP interpreter to take the file script.js, then encode it through base64, add the line ‘K’ and write all this into the $filebase64 variable.

Adding the string ‘K’ (it can be any Latin letter or a combination of letters or numbers) protects us from wanting to copy your script to decrypt it using alert() or an online decoder. After all, with additional characters, the script will not work.

Then somewhere in the code we call the script:

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

Let this script be called separately, away from other scripts and links to scripts.

Next, somewhere in the file with common site scripts, separately from other scripts, insert the call to the decryption functions. You can insert independently of other functions and libraries.

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

We analyze in detail what is happening here.

Our main function glob() takes one parameter s. It is immediately passed to the substring() function with the -~[] parameter (this is 1 in encrypted form), which extracts the string from s from the first character to the end. Therefore, if we in the PHP code add more than one character as a string, say three, then we will need to specify 2+(-~[]) in the substring() function. Or, by encrypting digits through bitwise operators, we can create a confusing formula, some of the variables of which we can hide in cookies or sessionStorage, which will make it extremely difficult to figure out how many characters need to be dropped to decrypt the code.

An example of replacing numbers with a bitwise operator ~:

  • -1 can be replaced by ~[]
  • 1 can be replaced by -~[]
  • 0 can be replaced by ~~[]

Next, the result is taken by the rty() function. This function is a set of characters, in particular: this["\x61\x74\x6F\x62"];

Try typing this into a browser console and you will see what this feature actually does. For example, you will see:

ƒ atob() { [native code] }

That is, the character set is an encrypted atob() function, which, as described in MDN, decodes a string encoded using base64.

The decoding result is obtained by the sfd() function. It is also a set of characters: this ["\x65\x76\x61\x6C"];.

Have you guessed what to do? Run in the browser console and you will see:

ƒ eval() { [native code] }

Here, I think, it is not necessary to explain anything. We all know that the eval() function executes the script obtained from the string. “Bad practice”, as many would say, for regular code, but in our case it is a safe and necessary function for implementing our idea. In addition, the user cannot directly access this function.

You probably wondered how, in the same way, are the functions encrypted in the character set? Very simple: a character set is text converted to hexadecimal notation. That is, it is a hex (hexadecimal) text in which you can encrypt any characters.

Thus, our decoded function looks like this (it’s specially broken down into lines so that it is visual):

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

As a result, we discard the first character of the encrypted string (at the same time there may be at least 353 characters, and no one can quickly guess about this), then decrypt it, then execute it with eval().

You can go further. If in some way, someone still decrypts your script, complicate it a bit to make it harder for people to modify it. For example, the bitwise operator ^, which works great. For example, a^b^b will be equal to a. As b, a key can be used, which we encrypt somewhere above.

Everything will work as before, but it will be confusing for bad lovers to copy your code.

By Denis Lisogorskii