Skip to content

Instantly share code, notes, and snippets.

@sukima
Last active May 9, 2024 23:49
Show Gist options
  • Star 60 You must be signed in to star a gist
  • Fork 20 You must be signed in to fork a gist
  • 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);
@pfautrero
Copy link

Hi,
What is the license applied on XORCipher.js ? MIT ?
Thanks

@alphaville
Copy link

Thanks a lot for the code. I'm using it for SafeMessage which is an open-source freely-distributed project for exchanging messages securely. An alpha version is now running at http://opentox.ntua.gr/safemsg/create.php.

@thbwd
Copy link

thbwd commented Mar 21, 2015

@alphaville I hope you know this is a highly insecure encryption algorithm. (not meant to be used for encryption of secret messages) You won't be able to exchange message securely...

@grandrew
Copy link

@idmean XOR is a perfect encryption algorithm as long as the key is longer than data and it is used only once. For example, it is handy when you want to split the data in two parts so that compromising one site will not reveal the message

@alvarotrigo
Copy link

_.map ? what's _ ?

@ysmad
Copy link

ysmad commented Oct 13, 2016

For _.map see:

// Depends on Underscore.

@sukima
Copy link
Author

sukima commented Mar 3, 2017

@pfautrero OMG how have I not responded! I'm so sorry. Yeah this should probubly be listed in public domain. How about the WTFPL.

@PatriceBX
Copy link

Why not use btoa() and atob() ? Any difference with your code ?

@vanowm
Copy link

vanowm commented May 27, 2019

Does not work with UTF8 characters.
XORCipher.decode(XORCipher.encode("abcde абвгде", "1"), "1");
returns abcde 452745

P.S.
Why do you use underscore for this simple function?
Here is encrypt/decrypt functions without underscore dependency:

function xor_encrypt(key, data) {
  return data.split('').map(function(c, i) {
    return c.charCodeAt(0) ^ keyCharAt(key, i);
  });
}

function xor_decrypt(key, data) {
  return data.map(function(c, i) {
    return String.fromCharCode( c ^ keyCharAt(key, i) );
  }).join("");
}

@brotochola
Copy link

this version doesn't depend on any other library, and it's packed as an object

var XORCipher = {
encode: function (key, data) {
data = this.xor_encrypt(key, data);
return this.b64_encode(data);
},
decode: function (key, data) {
data = this.b64_decode(data);
return this.xor_decrypt(key, data);
},
b64_table: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
b64_encode: function (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 += this.b64_table.charAt(h1) + this.b64_table.charAt(h2) + this.b64_table.charAt(h3) + this.b64_table.charAt(h4);
} while (i < data.length);
r = data.length % 3;
return (r ? enc.slice(0, r - 3) : enc) + "===".slice(r || 3);
},

        b64_decode: function (data) {
            var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, result = [];
            if (!data) { return data; }
            data += "";
            do {
                h1 = this.b64_table.indexOf(data.charAt(i++));
                h2 = this.b64_table.indexOf(data.charAt(i++));
                h3 = this.b64_table.indexOf(data.charAt(i++));
                h4 = this.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;
        },
        keyCharAt: function (key, i) {
            return key.charCodeAt(Math.floor(i % key.length));
        },
        xor_encrypt: function (key, data) {
            let rta = []
            for (let i = 0; i < data.length; i++) {
                let c = data[i]
                rta.push(c.charCodeAt(0) ^ this.keyCharAt(key, i))
            }
            return rta;


        },
        xor_decrypt: function (key, data) {
            let rta = []
            for (let i = 0; i < data.length; i++) {
                let c = data[i]
                rta.push(String.fromCharCode(c ^ this.keyCharAt(key, i)))
            }             

           return  rta.join("")            
        }



    }; 

@pocc
Copy link

pocc commented Dec 5, 2019

Above code with no dependencies run over Google's Closure Compiler and Uglify:

var XORCipher={encode:function(t,e){return e=this.xor_encrypt(t,e),this.b64_encode(e)},decode:function(t,e){return e=this.b64_decode(e),this.xor_decrypt(t,e)},b64_table:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",b64_encode:function(t){var e=0,r="";if(!t)return t;do{var h=t[e++],n=t[e++],i=t[e++],a=h<<16|n<<8|i;h=a>>18&63,n=a>>12&63,i=a>>6&63,a&=63,r+=this.b64_table.charAt(h)+this.b64_table.charAt(n)+this.b64_table.charAt(i)+this.b64_table.charAt(a)}while(e<t.length);return((t=t.length%3)?r.slice(0,t-3):r)+"===".slice(t||3)},b64_decode:function(t){var e=0,r=[];if(!t)return t;t+="";do{var h=this.b64_table.indexOf(t.charAt(e++)),n=this.b64_table.indexOf(t.charAt(e++)),i=this.b64_table.indexOf(t.charAt(e++)),a=this.b64_table.indexOf(t.charAt(e++)),c=h<<18|n<<12|i<<6|a;h=c>>16&255,n=c>>8&255,c&=255,r.push(h),64!==i&&(r.push(n),64!==a&&r.push(c))}while(e<t.length);return r},keyCharAt:function(t,e){return t.charCodeAt(Math.floor(e%t.length))},xor_encrypt:function(t,e){for(var r=[],h=0;h<e.length;h++)r.push(e[h].charCodeAt(0)^this.keyCharAt(t,h));return r},xor_decrypt:function(t,e){for(var r=[],h=0;h<e.length;h++)r.push(String.fromCharCode(e[h]^this.keyCharAt(t,h)));return r.join("")}};

@sukima
Copy link
Author

sukima commented Dec 6, 2019

Wow, I don't believe this still gets views! I was so young when I wrote this.

@lyquix-owner
Copy link

lyquix-owner commented Apr 7, 2020

Thank you for this code, it inspired this super simple utility: https://gist.github.com/lyquix-owner/2ad6459672c1b8ec826b0b59f4e220d3

Demo: https://jsfiddle.net/lyquix/dyf05wh3/

@emwadde
Copy link

emwadde commented May 26, 2020

Thank you for this code. It helped me from installing yet another package.

@vanowm
Copy link

vanowm commented May 28, 2020

Here is a fixed version that works with non-ACSII (unicode/utf8) characters in string and/or in key and has no dependency on external libraries.
It uses stringToUtf8ByteArray() and utf8ByteArrayToString()

Test: https://jsfiddle.net/vanowm/jczf09ao/

// 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"
//     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);
   	}
   };

   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 xor_encrypt(key, data) {
   	key = stringToUtf8ByteArray(key);
   	return stringToUtf8ByteArray(data).map(function(c, i) {
   		return c ^ key[Math.floor(i % key.length)];
   	});
   }

   function xor_decrypt(key, data) {
   	key = stringToUtf8ByteArray(key);
   	return utf8ByteArrayToString(data.map(function(c, i) {
   		return c ^ key[Math.floor(i % key.length)];
   	}));
   }

   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