/**
* Title: RXCipher
* Description: Simple and relatively quick stream cipher algorithm - JavaScript implementation
* Author: Jonathan Voss
* Date: 8/7/2012 (v1.0); 12/16/2014 (v2.0); 11/19/2015 (v3.0)
* Version: 3.2
* Namespace: github.com/k98kurz
* License: MIT
*
* Basic byte-level algorithm
* t = plaintext byte, c = ciphertext byte, k = encryption keystream byte
* function enc (t, k) { return (t+k>255 ? t+k-256 : t+k)^k; }
* function dec (c, k) { return (c^k)-k<0 ? (c^k)-k+256 : (c^k)-k; }
*
*
* Class implementation
* Methods:
* encrypt ( plaintext )
* encrypts plaintext with key stream derived from skey
* decrypt ( ciphertext)
* decrypts ciphertext with key stream derived from skey
* getKeyStream ()
* returns a KeyStream for PRNG generation
* reset ()
* resets the internal KeyStream
*
* Changes:
* 2.0 (12/16/2014): added pseudo-random key stream; dropped automatic hex conversion of ciphertext
* 2.1 (12/29/2014): changed default seed value for XORShiftPlus PRNG
* 2.2 (11/18/2015): converts to/from UTF-8 encoding; new keystream uses multiple csprngs and XORs the key with csprng values
* 2.3 (11/18/2015): added explicit IV support; made base RXEncrypt & RXDecrypt use a Uint8 byte
* 3.0 (11/19/2015): changed KeyStream to use an internally mixed 64 byte state; improved IV handling; improved performance
* 3.1 (02/01/2016): updated KeyStream to use improved/fixed XORShiftPlus class; encrypt/decrypt use hex; encryptRaw/decryptRaw use Uint8Array
* 3.2 (03/08/2016): bug fix: getKeyStream now returns actual KeyStream; reset method returns this
*/
function RXCipher (key, iv) {
var skey, siv = iv, stream;
skey = (typeof key == 'object' && key instanceof Uint8Array) ? fromUTF8Array(key) : key;
siv = (typeof iv == 'object' && iv instanceof Uint8Array) ? fromUTF8Array(iv) : iv;
stream = new KeyStream(skey, siv);
function encrypt (plaintext) {
var ciphertext = new Uint8Array(plaintext.length), g, t, k, key;
key = stream.get(plaintext.length);
for (var i = 0, ij = plaintext.length; i < ij; ++i) {
ciphertext[i] = plaintext[i] ^ key[i];
}
return ciphertext;
}
function decrypt (ciphertext) {
var plaintext = new Uint8Array(ciphertext.length), g, c, k, key;
key = stream.get(ciphertext.length);
for (var i = 0, ij = ciphertext.length; i < ij; ++i) {
plaintext[i] = ciphertext[i] ^ key[i];
}
return plaintext;
}
this.encrypt = function (plaintext) {
var ciphertext = encrypt(toUTF8Array(plaintext));
return toHex(ciphertext);
};
this.decrypt = function (ciphertext) {
var plaintext = decrypt(fromHex(ciphertext));
return fromUTF8Array(plaintext);
};
this.encryptRaw = function (plaintext) {
return encrypt(toUTF8Array(plaintext));
};
this.decryptRaw = function (ciphertext) {
return fromUTF8Array(decrypt(ciphertext));
};
this.getKeyStream = function () {
return new KeyStream(skey, siv);
};
this.reset = function () {
stream = new KeyStream (skey, siv);
return this;
};
function RXEncrypt (t, k, workingByte) {
workingByte[0] = (t+k);
return workingByte[0]^k;
}
function RXDecrypt (c, k, workingByte) {
workingByte[0] = (c^k) - k;
return workingByte[0];
}
/**
* Class: XORShiftPlus: "non-linear" PRNG
* Description: Based upon the xorshift128+ generator, one of the fastest generators passing BigCrush.
* Methods:
* next(n) returns Uint32Array of n random numbers
* nextHex(n) returns Array of n hexidecimal numbers
* bytes(n) returns Uint8Array of n random bytes
* bytesHex(n) returns Array of n random bytes in hexidecimal
* hex(n) returns hexidecimal string of n bytes
* Note: This generates with higher periodicity and better random distribution than the linear XORShift
*/
function XORShiftPlus (seed) {
var x = new Uint32Array(1), y = new Uint32Array(1), z = new Uint32Array(1), w = new Uint32Array(1);
x[0] = seed ? seed|0 : 317973455;
y[0] = x[0]<<362436069;
z[0] = y[0]+x[0];
w[0] = z[0]^x[0]+y[0];
function next () {
var t = new Uint32Array(1);
t[0] = x[0]^(x<<11);
x[0] = y[0]; y[0] = z[0]; z[0] = w[0];
w[0] = w[0]^(w[0]>>19)^(t[0]^(t[0]>>8));
return w[0]+y[0];
}
// discard the first numbers as they are somewhat predictable
for (var i=0, n=next(), j=next()%256; i<=j; next(), ++i);
this.next = function (n, option) {
if (typeof n == 'undefined' || typeof n !== 'number')
var n = 1;
var t; n |= 0; n = n>0 ? n : 1;
if (typeof option == 'undefined' || typeof n !== 'number')
var option = 0;
// redundant code, but improved performance
switch (option) {
// nextHex
case 1:
t = [];
for (var i=0, il=n; i<il; t[i++] = next().toString(16));
break;
// bytes
case 2:
t = new Uint8Array(n);
for (var i=0, il=n; i<il; t[i++] = next());
break;
// bytesHex
case 3:
t = [];
for (var i=0, il=n; i<il; t[i] = (next()%256).toString(16), t[i] = t[i].length%2 ? '0'+t[i] : t[i], ++i);
break;
// hex
case 4:
t = [];
for (var i=0, il=n; i<il; t[i] = (next()%256).toString(16), t[i] = t[i].length%2 ? '0'+t[i] : t[i], ++i);
t = t.join('');
break;
// next
default:
t = new Uint32Array(n);
for (var i=0, il=n; i<il; t[i++] = next());
}
return t;
};
this.nextHex = function (n) { return this.next(n, 1); };
this.bytes = function (n) { return this.next(n, 2); };
this.bytesHex = function (n) { return this.next(n, 3); };
this.hex = function (n) { return this.next(n, 4); };
}
function KeyStream (key, iv) {
var csprngs, state, keyLength, keyIndex, stateIndex, workingByte = new Uint8Array(1), t, j;
if (typeof key == 'undefined')
throw new Error('RXCipher@KeyStream: key required');
if (typeof key == 'object' && key instanceof Array)
key = (typeof key[0] == 'number') ? new Uint8Array(key) : key.join('');
if (typeof key == 'string')
key = toUTF8Array(key);
if (typeof key !== 'object' && !(key instanceof Uint8Array))
throw new Error('RXCipher@KeyStream: expected key of type String, Array, or Uint8Array');
if (typeof iv == 'undefined')
iv = '0123456';
if (typeof iv == 'object' && iv instanceof Array)
iv = (typeof iv[0] == 'number') ? new Uint8Array(iv) : iv.join('');
if (typeof iv == 'string')
iv = toUTF8Array(iv);
if (!(iv instanceof Uint8Array) || iv.length == 0)
throw new Error('RXCipher@KeyStream: expected iv of type String, Array or Uint8Array, or undefined');
// initialize some internal state
csprngs = []; keyLength = key.length; keyIndex = 0; state = new Uint8Array(64); stateIndex = 0;
for (var i = 0; i < keyLength; i++) {
csprngs[i] = new XORShiftPlus(key[i]);
}
for (var i = 0, j = 0, c = 0; c < keyLength * 4; ++i, ++j, ++c) {
j = (j == keyLength) ? 0 : j;
i = (i == iv.length) ? 0 : i;
key[j] = key[j] + iv[i];
}
for (var i = 0, j = 0; i < 64; ++i, ++j) {
j = (j == keyLength) ? 0 : j;
state[i] = csprngs[j].next()[0];
}
for (var i = 63, j = 0, t, m; i >= 0; --i, ++j) {
j = (j == keyLength) ? 0 : j;
t = (csprngs[j].next()[0]+key[j])%64;
m = state[t];
state[t] = RXEncrypt(state[i], key[j], workingByte);
state[i] = RXDecrypt(m, t, workingByte);
}
function next () {
keyIndex = (++keyIndex == keyLength) ? 0 : keyIndex;
stateIndex = (++stateIndex == 64) ? 0 : stateIndex;
state[stateIndex] = RXEncrypt(state[stateIndex], key[keyIndex], workingByte);
state[63 - stateIndex] = RXEncrypt(state[63 - stateIndex], state[stateIndex], workingByte);
return state[stateIndex];
}
this.get = function (nBytes) {
nBytes = (nBytes|0 ? nBytes|0 : (parseInt(nBytes) ? parseInt(nBytes) : 1));
var k = new Uint8Array(nBytes);
for (var o=0; o<nBytes; ++o) {
k[o] = next();
}
return k;
};
}
// convert from JavaScript's internal UTF-16 strings to UTF-8
// ripped from http://stackoverflow.com/a/18729931
// credit: Joni Salonen
function toUTF8Array(str) {
var utf8 = [], charcode;
for (var i=0; i < str.length; i++) {
charcode = str.charCodeAt(i);
if (charcode < 0x80) utf8.push(charcode);
else if (charcode < 0x800) {
utf8.push(0xc0 | (charcode >> 6),
0x80 | (charcode & 0x3f));
} else if (charcode < 0xd800 || charcode >= 0xe000) {
utf8.push(0xe0 | (charcode >> 12),
0x80 | ((charcode>>6) & 0x3f),
0x80 | (charcode & 0x3f));
} else { // surrogate pair
i++;
// UTF-16 encodes 0x10000-0x10FFFF by
// subtracting 0x10000 and splitting the
// 20 bits of 0x0-0xFFFFF into two halves
charcode = 0x10000 + (((charcode & 0x3ff)<<10)
| (str.charCodeAt(i) & 0x3ff));
utf8.push(0xf0 | (charcode >>18),
0x80 | ((charcode>>12) & 0x3f),
0x80 | ((charcode>>6) & 0x3f),
0x80 | (charcode & 0x3f));
}
}
return new Uint8Array(utf8);
}
// convert from UTF-8 to JavaScript's internal UTF-16 strings
// ripped from https://github.com/coolaj86/TextEncoderLite
// credit: AJ ONeal (coolaj86) && Feross Aboukhadijeh (feross)
function fromUTF8Array (arr) {
var utf16 = '', tmp = '';
for (var i = 0, ij = arr.length; i < ij; i++) {
if (arr[i] <= 0x7F) {
utf16 += decodeUtf8Char(tmp) + String.fromCharCode(arr[i]);
tmp = '';
} else {
tmp += '%' + arr[i].toString(16);
}
};
return utf16 + decodeUtf8Char(tmp);
}
function decodeUtf8Char (str) {
try {
return decodeURIComponent(str);
} catch (err) {
return String.fromCharCode(0xFFFD); // UTF 8 invalid char
}
}
function toHex (raw) {
var t = [];
for (var i=0, il=raw.length; i<il; ++i) {
t[i] = raw[i].toString(16); t[i] = t[i].length%2 ? '0'+t[i] : t[i];
}
return t.join('');
}
function fromHex (hex) {
var t = new Uint8Array(hex.length/2);
for (var i=0, j=0, il=hex.length; i<il; ++i, ++j) {
t[j] = parseInt(hex[i] + hex[++i], 16);
}
return t;
}
};
Comments