66

I'm looking for a way to obfuscate and deobfuscate a string in JavaScript; by which I mean encryption and decryption when security is not an issue. Ideally something native to JS (like base64_encode() and base64_decode() in PHP) to "turn a string into something else and back again" without having to write a function.

Any suggestions welcome!

User
  • 129
  • 2
  • 16
Rich Jenks
  • 1,723
  • 5
  • 19
  • 32
  • The part about "encryption and decryption when security is not an issue" has me really confused. If security of the content is of no consequence, what possible value does encryption provide? – Paul Turner Jan 22 '13 at 12:46
  • this makes completely no sense to me. If you obfuscate a string with a native function everybody can just call the decrypt function. There is no improvement at all and you can just leave the string as it is. Also this is very likely to break on unicode strings. – Christoph Jan 22 '13 at 13:07
  • 19
    @Christoph It's useful in deterring the average Joe from inspecting a given logic in your application. Strictly speaking, even windows binaries can be reverse engineered, and as such, you could say compiling a C++ source into executable doesn't provide any additional security. In my opinion, this is just a matter of making the life of thieves more miserable, albeit on different levels. – John Weisz May 13 '15 at 09:53
  • 1
    I sometimes find it useful, for elegance rather than for security, to obfuscate long URLs containing cleartext params that are to be shared. – oskare Aug 13 '15 at 11:16
  • 9
    This topic is useful to me because I need to have a json list of 'backlisted' words which will live on a client's page, but I dont want an exhaustive list of naughty strings directly in the source. – Jon Church Jun 01 '18 at 07:44
  • Here's another perfectly reasonable scenario for using obfuscation -- for writing tests to verify encryption/decryption are being applied properly that are isolated from any specific encryption algorithm, not to mention faster. – mtjhax May 14 '19 at 15:03
  • Yet another use-case for insecure obfuscation--you may be using the strings as keys in a totally internal, distributed SSTable, and you want to spread out the unpredictable input evenly over the keyspace. – cognalog Aug 10 '20 at 16:24

5 Answers5

106

You can use btoa() and atob(). btoa() is like base64_encode() and atob() like base64_decode().

Here is an example:

btoa('Some text'); // U29tZSB0ZXh0
atob('U29tZSB0ZXh0'); // Some text

Keep in mind that this is not a secure way to keep secrets. Base64 is a binary-to-text encoding scheme that represents binary data in an ASCII string format by translating it into a radix-64 representation.

Minko Gechev
  • 25,304
  • 9
  • 61
  • 68
  • 2
    Seems to work in most modern browsers so suits my requirements perfectly. Thank you! – Rich Jenks Jan 22 '13 at 13:07
  • 3
    Worth noting that the encoded string will be larger than the non-encoded one. – UpTheCreek Sep 19 '13 at 07:05
  • Cross browser support seems good now. https://caniuse.com/#search=btoa It's safe to use. – Aniket Suryavanshi Mar 26 '18 at 09:47
  • Thank you. I love the use-cases for this. – ethancrist May 16 '18 at 15:27
  • It is very important to note this will fail on strings containing unicode characters that do not belong to the 0-127 range of the ASCII character set, and therefore this function is insufficient for use on user input from almost any modern web app where people might use non-ascii chars. The MDN docs discuss how to deal with this, you must first convert the unicode characters to a byte string. – Yetanotherjosh Apr 16 '21 at 20:22
  • Id like to add for anyone looking in the future, btoa and atob are deprecated and are only kept around for legacy web APIs, converting between base 64 encoded strings and binary data should be performed using Buffer.from(str, 'base64') and buf.toString('base64') – Robby Hoover Oct 17 '21 at 00:15
41

It's worth noting that

(![]+[])[+[]]+(![]+[])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]

evaluates to the string "fail" without ever looking like a string. Seriously, enter it into node and be amazed. You can spell anything in JavaScript by being crazy.

Gant Laborde
  • 6,484
  • 1
  • 21
  • 30
19

I'm obviously too late for an answer, but I was just working on another solution for the problem and base64 seemed to be to weak.

It works like this:

"abc;123!".obfs(13) // => "nopH>?@."
"nopH>?@.".defs(13) // => "abc;123!"

Code:

/**
 * Obfuscate a plaintext string with a simple rotation algorithm similar to
 * the rot13 cipher.
 * @param  {[type]} key rotation index between 0 and n
 * @param  {Number} n   maximum char that will be affected by the algorithm
 * @return {[type]}     obfuscated string
 */
String.prototype.obfs = function(key, n = 126) {
  // return String itself if the given parameters are invalid
  if (!(typeof(key) === 'number' && key % 1 === 0)
    || !(typeof(key) === 'number' && key % 1 === 0)) {
    return this.toString();
  }

  var chars = this.toString().split('');

  for (var i = 0; i < chars.length; i++) {
    var c = chars[i].charCodeAt(0);

    if (c <= n) {
      chars[i] = String.fromCharCode((chars[i].charCodeAt(0) + key) % n);
    }
  }

  return chars.join('');
};

/**
 * De-obfuscate an obfuscated string with the method above.
 * @param  {[type]} key rotation index between 0 and n
 * @param  {Number} n   same number that was used for obfuscation
 * @return {[type]}     plaintext string
 */
String.prototype.defs = function(key, n = 126) {
  // return String itself if the given parameters are invalid
  if (!(typeof(key) === 'number' && key % 1 === 0)
    || !(typeof(key) === 'number' && key % 1 === 0)) {
    return this.toString();
  }

  return this.toString().obfs(n - key);
};
enzian
  • 1,607
  • 3
  • 18
  • 19
  • 3
    This code breaks `for...in` enumeration of strings, don't use this in production. – Patrick Roberts May 21 '18 at 22:35
  • 1
    @PatrickRoberts `for ... in` should always bundle with `.hasOwnProperty()`. You can also define `obfs` and `defs` without modifying `String.prototype` – Gan Quan Feb 21 '20 at 20:28
  • 1
    @GanQuan that was my point. The answer shouldn't be polluting builtin prototypes at all. The `.hasOwnProperty()` "good practice" (/s) came about due to the frequency of polluting builtins, not because it's actually made necessary by the language design itself. Because of how common it is for libraries to pollute builtins with arbitrary extensions like this, I think `for...in` has unfortunately become rarely, if ever, the appropriate choice for enumeration of _anything_, strings included. – Patrick Roberts Feb 21 '20 at 22:24
  • 1
    You have a nice little obfuscator but then it's exposed globally for anyone to see on the String prototype. Kinda beats the purpose. – Etienne Martin May 11 '20 at 22:17
1

1. Using strange strings

Go to http://www.jsfuck.com/, enter the input string, and get the result in the text box below.

Test this in the console and it will return "hi":

(+(+!+[]+[+[]]+[+!+[]]))[(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([]+[])[([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]][([][[]]+[])[+!+[]]+(![]+[])[+!+[]]+((+[])[([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]]](!+[]+!+[]+[+!+[]])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]

console.log("Original: hi")
console.log("Converted: " + (+(+!+[]+[+[]]+[+!+[]]))[(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([]+[])[([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]][([][[]]+[])[+!+[]]+(![]+[])[+!+[]]+((+[])[([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]]](!+[]+!+[]+[+!+[]])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]])

It doesn't even look like a string!

2. Using atob and btoa

To use this method, just convert the string to base64 using atob("string") and then decrypt the string with btoa("encoded")

Tiago Rangel
  • 1,159
  • 15
  • 29
0

A simple method to obfuscate a string is to use a script like this, using Node.js. This code is in Typescript.

import readline from 'readline';

const rl = readline.createInterface({
    input:process.stdin,
    output:process.stdout
});
const randNum = (max:number=10000)=>Math.floor(Math.random() * max);
// finds an equation that will make the target number.
const findEquation = (target:number)=>{
    let equation = "";
    const rand = randNum();
    const diff = target - rand;
    const rand2 = randNum()
    const product = diff * rand2;
    equation = `${rand}+(${product} / ${rand2})`;
    return equation;
}
const randCharSequence = (length:number)=>{
    let str = "";
    for(let i = 0; i < length; i++){
        str += String.fromCharCode(randNum(256));
    }
    return str
}
const sep = randCharSequence(8)
rl.question("Enter the string to obfuscate:\n", (str)=>{
    let obfuscated = "(''";
    str.split("").forEach(char=>{
        const code = findEquation(char.charCodeAt(0));
        obfuscated += `+(String.fromCharCode(${code})+\"${sep}\")`
    })
    obfuscated += `).split(\"${sep}\").join('')`;
    
    console.log("Obfuscated String:");
    console.log(obfuscated);
    rl.close();
});

When you run this script, you are prompted to enter the string to obfuscate. Then, the script prints out an obfuscated string. It uses equations that involve numbers of up to 10000 to generate numbers, that then get converted and concatonated into a string. But, that's not all. It adds a randomly generated string of 8 characters to the output, that gets removed when it is ran. This makes it harder to get a look at the string when reading the code, but keep in mind, that a person with access to the code, can easily add a console.log statement, and then get the string. Note, this isn't encryption, it just makes the code harder to understand and read.

Fighter178
  • 303
  • 2
  • 9