Skip to content

Instantly share code, notes, and snippets.

@sukima
Last active May 9, 2024 23:49
Show Gist options
  • Save sukima/5613286 to your computer and use it in GitHub Desktop.
Save sukima/5613286 to your computer and use it in GitHub Desktop.
A Super simple encryption cipher using XOR and Base64 in JavaScript
// XORCipher - Super simple encryption using XOR and Base64
//
// Depends on [Underscore](http://underscorejs.org/).
//
// As a warning, this is **not** a secure encryption algorythm. It uses a very
// simplistic keystore and will be easy to crack.
//
// The Base64 algorythm is a modification of the one used in phpjs.org
// * http://phpjs.org/functions/base64_encode/
// * http://phpjs.org/functions/base64_decode/
//
// Examples
// --------
//
// XORCipher.encode("test", "foobar"); // => "EgocFhUX"
// XORCipher.decode("test", "EgocFhUX"); // => "foobar"
//
// Copyright © 2013 Devin Weaver <suki@tritarget.org>
//
// This program is free software. It comes without any warranty, to
// the extent permitted by applicable law. You can redistribute it
// and/or modify it under the terms of the Do What The Fuck You Want
// To Public License, Version 2, as published by Sam Hocevar. See
// http://www.wtfpl.net/ for more details.
/* jshint forin:true, noarg:true, noempty:true, eqeqeq:true, strict:true,
undef:true, unused:true, curly:true, browser:true, indent:2, maxerr:50 */
/* global _ */
(function(exports) {
"use strict";
var XORCipher = {
encode: function(key, data) {
data = xor_encrypt(key, data);
return b64_encode(data);
},
decode: function(key, data) {
data = b64_decode(data);
return xor_decrypt(key, data);
}
};
var b64_table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
function b64_encode(data) {
var o1, o2, o3, h1, h2, h3, h4, bits, r, i = 0, enc = "";
if (!data) { return data; }
do {
o1 = data[i++];
o2 = data[i++];
o3 = data[i++];
bits = o1 << 16 | o2 << 8 | o3;
h1 = bits >> 18 & 0x3f;
h2 = bits >> 12 & 0x3f;
h3 = bits >> 6 & 0x3f;
h4 = bits & 0x3f;
enc += b64_table.charAt(h1) + b64_table.charAt(h2) + b64_table.charAt(h3) + b64_table.charAt(h4);
} while (i < data.length);
r = data.length % 3;
return (r ? enc.slice(0, r - 3) : enc) + "===".slice(r || 3);
}
function b64_decode(data) {
var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, result = [];
if (!data) { return data; }
data += "";
do {
h1 = b64_table.indexOf(data.charAt(i++));
h2 = b64_table.indexOf(data.charAt(i++));
h3 = b64_table.indexOf(data.charAt(i++));
h4 = b64_table.indexOf(data.charAt(i++));
bits = h1 << 18 | h2 << 12 | h3 << 6 | h4;
o1 = bits >> 16 & 0xff;
o2 = bits >> 8 & 0xff;
o3 = bits & 0xff;
result.push(o1);
if (h3 !== 64) {
result.push(o2);
if (h4 !== 64) {
result.push(o3);
}
}
} while (i < data.length);
return result;
}
function keyCharAt(key, i) {
return key.charCodeAt( Math.floor(i % key.length) );
}
function xor_encrypt(key, data) {
return _.map(data, function(c, i) {
return c.charCodeAt(0) ^ keyCharAt(key, i);
});
}
function xor_decrypt(key, data) {
return _.map(data, function(c, i) {
return String.fromCharCode( c ^ keyCharAt(key, i) );
}).join("");
}
exports.XORCipher = XORCipher;
})(this);
@vanowm
Copy link

vanowm commented May 31, 2020

This version generates random encrypted string, meaning even when supplied identical text and key, the generated text will always be different every time it's encrypted (there is 1 in 65534^6 chance it might repeat)
This version accepts an optional third parameter "seed". This parameter will be used to randomize the encrypted output. If an empty string used as seed (or any static string) it will generate static (non-random) encryption output. If no seed provided (undefined) a random generated seed of 6 characters will be used. Provided XORCipher.seed(nn) function can be used to generate random nn long seed. The seed can only contain characters with 1-65535 charcode
i.e.
XORCipher.encode("test", "foobar", XORCipher.seed(10)); will encrypt with 10 characters long seed

Demo:
https://jsfiddle.net/vanowm/7suyfgo4/

// XORCipher - Super simple encryption using XOR and Base64
//
// As a warning, this is **not** a secure encryption algorithm. It uses a very
// simplistic keystore and will be easy to crack.
//
// The Base64 algorithm is a modification of the one used in phpjs.org
// * http://phpjs.org/functions/base64_encode/
// * http://phpjs.org/functions/base64_decode/
//
// stringToUtf8ByteArray() and utf8ByteArrayToString()
// https://github.com/google/closure-library/blob/466a34e7e2d4cb49ac1c731347e845235d8ce7cc/closure/goog/crypt/crypt.js#L117-L143
// https://github.com/google/closure-library/blob/466a34e7e2d4cb49ac1c731347e845235d8ce7cc/closure/goog/crypt/crypt.js#L151-L178

// Examples
// --------
//
//     XORCipher.encode("test", "foobar");   // => "EgocFhUX" (random each time)
//     XORCipher.decode("test", "EgocFhUX"); // => "foobar"
//     XORCipher.encode("test", "foobar", "");   // => "dHdrcGZiYw==" (static each time)
//     XORCipher.decode("test", "dHdrcGZiYw=="); // => "foobar"
//     XORCipher.encode("test", "foobar", XORCipher.seed(10));   // => "XAIFMUMbeURsFRdyHXMTcQM=" (random each time, 10 char long seed)
//     XORCipher.decode("test", "XAIFMUMbeURsFRdyHXMTcQM="); // => "foobar"
//
// Copyright © 2013 Devin Weaver <suki@tritarget.org>
//
// This program is free software. It comes without any warranty, to
// the extent permitted by applicable law. You can redistribute it
// and/or modify it under the terms of the Do What The Fuck You Want
// To Public License, Version 2, as published by Sam Hocevar. See
// http://www.wtfpl.net/ for more details.

/* jshint forin:true, noarg:true, noempty:true, eqeqeq:true, strict:true,
   undef:true, unused:true, curly:true, browser:true, indent:2, maxerr:50 */
/* global _ */
(function(exports) {
	"use strict";

	var XORCipher = {
		encode: function(key, data, seed) {
			data = xor_encrypt(key, data, seed);
			return b64_encode(data);
		},
		decode: function(key, data) {
			data = b64_decode(data);
			return xor_decrypt(key, data);
		},
		seed: function(n)	{
			return randString(n);
		}
	};

	function stringToUtf8ByteArray(str) {
		var out = [], p = 0;
		for (var i = 0; i < str.length; i++) {
			var c = str.charCodeAt(i);
			if (c < 128) {
				out[p++] = c;
			} else if (c < 2048) {
				out[p++] = (c >> 6) | 192;
				out[p++] = (c & 63) | 128;
			} else if (
					((c & 0xFC00) == 0xD800) && (i + 1) < str.length &&
					((str.charCodeAt(i + 1) & 0xFC00) == 0xDC00)) {
				// Surrogate Pair
				c = 0x10000 + ((c & 0x03FF) << 10) + (str.charCodeAt(++i) & 0x03FF);
				out[p++] = (c >> 18) | 240;
				out[p++] = ((c >> 12) & 63) | 128;
				out[p++] = ((c >> 6) & 63) | 128;
				out[p++] = (c & 63) | 128;
			} else {
				out[p++] = (c >> 12) | 224;
				out[p++] = ((c >> 6) & 63) | 128;
				out[p++] = (c & 63) | 128;
			}
		}
		return out;
	}

	function utf8ByteArrayToString(bytes) { // array of bytes
		var out = [], pos = 0, c = 0;
		while (pos < bytes.length) {
			var c1 = bytes[pos++];
			if (c1 < 128) {
				out[c++] = String.fromCharCode(c1);
			} else if (c1 > 191 && c1 < 224) {
				var c2 = bytes[pos++];
				out[c++] = String.fromCharCode((c1 & 31) << 6 | c2 & 63);
			} else if (c1 > 239 && c1 < 365) {
				// Surrogate Pair
				var c2 = bytes[pos++];
				var c3 = bytes[pos++];
				var c4 = bytes[pos++];
				var u = ((c1 & 7) << 18 | (c2 & 63) << 12 | (c3 & 63) << 6 | c4 & 63) -
						0x10000;
				out[c++] = String.fromCharCode(0xD800 + (u >> 10));
				out[c++] = String.fromCharCode(0xDC00 + (u & 1023));
			} else {
				var c2 = bytes[pos++];
				var c3 = bytes[pos++];
				out[c++] =
						String.fromCharCode((c1 & 15) << 12 | (c2 & 63) << 6 | c3 & 63);
			}
		}
		return out.join('');
	}

	var b64_table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

	function b64_encode(data) {
		var o1, o2, o3, h1, h2, h3, h4, bits, r, i = 0, enc = "";
		if (!data) { return data; }
		do {
			o1 = data[i++];
			o2 = data[i++];
			o3 = data[i++];
			bits = o1 << 16 | o2 << 8 | o3;
			h1 = bits >> 18 & 0x3f;
			h2 = bits >> 12 & 0x3f;
			h3 = bits >> 6 & 0x3f;
			h4 = bits & 0x3f;
			enc += b64_table.charAt(h1) + b64_table.charAt(h2) + b64_table.charAt(h3) + b64_table.charAt(h4);
		} while (i < data.length);
		r = data.length % 3;
		return (r ? enc.slice(0, r - 3) : enc) + "===".slice(r || 3);
	}

	function b64_decode(data) {
		var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, result = [];
		if (!data) { return data; }
		data += "";
		do {
			h1 = b64_table.indexOf(data.charAt(i++));
			h2 = b64_table.indexOf(data.charAt(i++));
			h3 = b64_table.indexOf(data.charAt(i++));
			h4 = b64_table.indexOf(data.charAt(i++));
			bits = h1 << 18 | h2 << 12 | h3 << 6 | h4;
			o1 = bits >> 16 & 0xff;
			o2 = bits >> 8 & 0xff;
			o3 = bits & 0xff;
			result.push(o1);
			if (h3 !== 64) {
				result.push(o2);
				if (h4 !== 64) {
					result.push(o3);
				}
			}
		} while (i < data.length);
		return result;
	}

	function rand(min, max){
		return Math.floor(Math.random() * (max - min + 1)) + min;
	}
	
	function randString(n)	{
		var r = "";
		for(var i = 0; i < n; i++)
			r += String.fromCharCode(rand(1, 65535));

		return r;
	}
	
	function xor_encrypt(key, data, seed) {
		if (typeof(seed) == "undefined")
			seed = randString(6);

		var d = stringToUtf8ByteArray(seed + String.fromCharCode(0) + data),
				k = stringToUtf8ByteArray(key),
				r = [];

		for(var i = 0; i < d.length; i++)
			r[i] = r[i-1] ^ d[i] ^ k[Math.floor(i % k.length)];

		return r;
	}

	function xor_decrypt(key, data) {
		var d = data,
				k = stringToUtf8ByteArray(key),
				r = [];

		for(var i = 0; i < d.length; i++)
			r[i] = d[i-1] ^ d[i] ^ k[Math.floor(i % k.length)];

		r.splice(0, r.indexOf(0) + 1);
		return utf8ByteArrayToString(r);
	}

	exports.XORCipher = XORCipher;

})(this);

@groupunknown
Copy link

Do you have this script in PHP with the functionality of encrypting using random seed? @vanowm

@ReeganExE
Copy link

Another version that uses a simple hex encode/decode

TypeScript (JavaScript below)
const XORCipher = {
  encode(key: string, plaintext: string) {
    const bin = xor_encrypt(key, plaintext);
    const hex = Array.from(bin, (b) => b.toString(16).padStart(2, '0')).join('');
    return hex;
  },

  decode(key: string, hexString: string) {
    const hexes = hexString.match(/.{2}/g) as string[];
    const bin = Uint8Array.from(hexes, (byte) => parseInt(byte, 16));
    return xor_decrypt(key, bin);
  },
};

function keyCharAt(key: string, i: number) {
  return key.charCodeAt(Math.floor(i % key.length));
}

function xor_encrypt(key: string, plaintext: string) {
  const bin = new Uint8Array(plaintext.length);
  for (let i = 0; i < plaintext.length; i++) {
    bin[i] = plaintext.charCodeAt(i) ^ keyCharAt(key, i);
  }
  return bin;
}

function xor_decrypt(key: string, bin: Uint8Array) {
  return Array.from(bin, (c, i) => String.fromCharCode(c ^ keyCharAt(key, i))).join('');
}

const encoded = XORCipher.encode('the lost key', 'Dep Trai Co Gi Sai');
console.log('Encoded:', encoded);
console.log(XORCipher.decode('the lost key', encoded));
JavaScript Version
const XORCipher = {
  encode (key, plaintext) {
      const bin = xor_encrypt(key, plaintext);
      const hex = Array.from(bin, (b)=>b.toString(16).padStart(2, '0')).join('');
      return hex;
  },
  decode (key, hexString) {
      const hexes = hexString.match(/.{2}/g);
      const bin = Uint8Array.from(hexes, (byte)=>parseInt(byte, 16));
      return xor_decrypt(key, bin);
  }
};

function keyCharAt(key, i) {
  return key.charCodeAt(Math.floor(i % key.length));
}

function xor_encrypt(key, plaintext) {
  const bin = new Uint8Array(plaintext.length);
  for(let i = 0; i < plaintext.length; i++){
      bin[i] = plaintext.charCodeAt(i) ^ keyCharAt(key, i);
  }
  return bin;
}

function xor_decrypt(key, bin) {
  return Array.from(bin, (c, i)=>String.fromCharCode(c ^ keyCharAt(key, i))).join('');
}

const encoded = XORCipher.encode('the lost key', 'Dep Trai Co Gi Sai');
console.log('Encoded:', encoded);
console.log(XORCipher.decode('the lost key', encoded));

Disclaimer: not secure as OP mentioned.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment