gnirts

Obfuscate string literals in JavaScript code.

Usage no npm install needed!

<script type="module">
  import gnirts from 'https://cdn.skypack.dev/gnirts';
</script>

README

gnirts

npm GitHub issues dependencies license

Obfuscate string literals in JavaScript code.

Online Demonstration https://anseki.github.io/gnirts/

gnirts mangles string literals more than hexadecimal escape like "\x66\x6f\x6f".
String literals that were escaped by the hexadecimal escape can be found out too easily, and those can be decoded too easily. Those stand out in the code. Stealers can get secret text (e.g. password) easily by pasting that on a console (e.g. Developer Tools of web browser).

gnirts mangles string literals by using some codes instead of hexadecimal escape. gnirts might not be able to protect the string from stealers perfectly, but it forces a troublesome work upon them. (See Note.)

For example, a string that should be hidden is here:

var password = 'open sesame';

Add the directives:

var password = /* @mangle */ 'open sesame' /* @/mangle */;

And then, pass this code to gnirts. The string literal between /* @mangle */ and /* @/mangle */ is obfuscated:

var password = (function(){var v=Array.prototype.slice.call(arguments),V=v.
shift();return v.reverse().map(function(U,o){return String.fromCharCode(U-V-0-o)
}).join('')})(6,119,117)+(527).toString(36).toLowerCase()+(function(){var N=
Array.prototype.slice.call(arguments),O=N.shift();return N.reverse().map(
function(R,T){return String.fromCharCode(R-O-41-T)}).join('')})(36,193,109)+(532
).toString(36).toLowerCase()+(function(){var R=Array.prototype.slice.call(
arguments),E=R.shift();return R.reverse().map(function(g,v){return String.
fromCharCode(g-E-62-v)}).join('')})(52,224,211)+(14).toString(36).toLowerCase();

(For this document, line-breaks were added to the code above.)

However, the code above is no good because the password variable can be shown by the debugger (e.g. Developer Tools of web browser).
Therefore, using no variable is better way. And gnirts supports the checking that the string matches.
For example, check whether an input from user is matched to a string literal:

if (userInput === 'open sesame') {
  console.log('OK, the door will be opened.');
}

Add the directives (Note that all of the condition expression is included in the directive):

if (/* @mangle */ userInput === 'open sesame' /* @/mangle */) {
  console.log('OK, the door will be opened.');
}

And then, pass this code to gnirts. The condition expression between /* @mangle */ and /* @/mangle */ is obfuscated:

if ((userInput).indexOf((function(){var f=Array.prototype.slice.call(arguments),
L=f.shift();return f.reverse().map(function(U,d){return String.fromCharCode(U-L-
54-d)}).join('')})(43,200,207,194),8)===8&&(new RegExp('^[^]{5}'+(18).toString(
36).toLowerCase().split('').map(function(p){return String.fromCharCode(p.
charCodeAt()+(-13))}).join('')+(43023).toString(36).toLowerCase()+(18).toString(
36).toLowerCase().split('').map(function(u){return String.fromCharCode(u.
charCodeAt()+(-13))}).join('')+(42989).toString(36).toLowerCase()+(18).toString(
36).toLowerCase().split('').map(function(S){return String.fromCharCode(S.
charCodeAt()+(-13))}).join('')+(43023).toString(36).toLowerCase())).test(
userInput)&&(userInput).indexOf((function(){var I=Array.prototype.slice.call(
arguments),s=I.shift();return I.reverse().map(function(E,t){return String.
fromCharCode(E-s-8-t)}).join('')})(38,82,159,149,159,157),0)===0) {
  console.log('OK, the door will be opened.');
}

(For this document, line-breaks were added to the code above.)

Directive

2 styles of the directive are supported:

/* @mangle */ TARGET_CODE /* @/mangle */
// @mangle
TARGET_CODE
// @/mangle

TARGET_CODEs are string literal or condition expression.

The comments in the target code are ignored.

/* @mangle */ 'open' + /* Color: */' black' + ' sesame' /* @/mangle */
// @mangle
'open' +
// Color of sesame is here:
' black' +
' sesame'
// @/mangle

Don't put a directive into a comment (i.e. don't make that be nested comment).

The target code in the directive are replaced to obfuscated codes.
The replaced code differs depending on the inside code of the directive:

String literal

The string literals like 'foo', "foo" or 'foo' + 'bar' are replaced to the codes that return an original string.

For example:

var password = /* @mangle */ 'open sesame' /* @/mangle */;

The following strings at the left side and the right side of the string literal are copied to the same position of the replaced code:

  • (, ), +, ,, :, ;, =, ?, [, ], {, }

For example:

password =
  'open' +
  // @mangle
  ' white' +
  ' sesame' + // <- This `+` is copied.
  // @/mangle
  ' street';
data = {
  password:
    // @mangle
    'open sesame', // <- This `,` is copied.
    // @/mangle
  userName: 'Ali Baba'
};

Condition expression

The condition expressions like SOMETHING === 'string literal' are replaced to the codes that return a boolean to indicate whether it matches.
SOMETHING may be a variable, a reference to a string like fooObject.barProperty or a function that returns a string. Note that SOMETHING may be referenced multiple times (i.e. if that is a function, that is called multiple times).
A comparison operator must be ===, ==, !== or !=.
The string literal may be 'foo', "foo" or 'foo' + 'bar'.

For example:

if (/* @mangle */ userInput === 'open sesame' /* @/mangle */) {
  console.log('OK, the door will be opened.');
}

The following strings at the left side and the right side of the condition expression are copied to the same position of the replaced code:

  • &&, ||, (, ), ,, :, ;, =, ?, [, ], {, }

For example:

if (userName === 'Ali Baba' &&
    // @mangle
    userInput === 'open sesame' && // <- This `&&` is copied.
    // @/mangle
    cave.hasTreasure) {
  console.log('OK, the door will be opened.');
}
var message =
  // @mangle
  userInput === 'open sesame' ? // <- This `?` is copied.
  // @/mangle
  'OK' :
  'NO';

Installation

npm install gnirts

Methods

mangle

obfuscatedCode = gnirts.mangle(sourceCode)

Parse and mangle sourceCode, and return an obfuscated code.

For example:

var gnirts = require('gnirts'),
  fs = require('fs'),
  js;

js = fs.readFileSync('src.js', {encoding: 'utf8'});
js = gnirts.mangle(js);
fs.writeFileSync('dest.js', js);

getCode

stringCode = gnirts.getCode(stringValue)

Return a obfuscated code that returns a stringValue.

Note

This mangling is not the cryptography to keep the data secure. It is used to avoid the hacking, the stealing something or the reverse engineering for such as the hybrid applications or the web applications. If your program uses the sensitive information such as the user's accounts, you should consider the standard secure system such as the cryptography by key pair.