/** * XORShiftPlus "non-linear" PRNG * Author: k98kurz (@gmail) * License: MIT * Date: 2020-09-07 * 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. */ class XORShiftPlus { #booted = false; #x; #y; #z; #w; constructor (seed) { this.#x = new Uint32Array(1); this.#y = new Uint32Array(1); this.#z = new Uint32Array(1); this.#w = new Uint32Array(1); this.#x[0] = seed ? seed|0 : 317973455; this.#y[0] = this.#x[0]<<362436069; this.#z[0] = this.#y[0]+this.#x[0]; this.#w[0] = this.#z[0]^this.#x[0]+this.#y[0]; } #next () { let t = new Uint32Array(1); t[0] = this.#x[0]^(this.#x[0]<<11); this.#x[0] = this.#y[0]; this.#y[0] = this.#z[0]; this.#z[0] = this.#w[0]; this.#w[0] = this.#w[0]^(this.#w[0]>>19)^(t[0]^(t[0]>>8)); return this.#w[0]+this.#y[0]; } #boot () { // if not booted, discard the first numbers as they are somewhat predictable if (this.#booted) return; for (let i=0, n=this.#next(), j=this.#next()%256; i<=j; this.#next(), ++i); this.#booted = true; } next (n, option) { this.#boot(); n = (n === undefined || typeof n !== 'number') ? 1 : n; let t; n |= 0; n = n>0 ? n : 1; option = (option === undefined || typeof n !== 'number') ? 0 : option; // redundant code, but improved performance switch (option) { // nextHex case 1: t = []; for (let i=0, il=n; i= 0; --i, ++j) { j = (j == this.#keyLength) ? 0 : j; t = (this.#csprngs[j].next()[0] + this.#key[j]) % 64; m = this.#state[t]; this.#state[t] = KeyStream.RXEncrypt(this.#state[i], this.#key[j], this.#workingByte); this.#state[i] = KeyStream.RXDecrypt(m, t, this.#workingByte); } } #next () { this.#keyIndex = (++this.#keyIndex == this.#keyLength) ? 0 : this.#keyIndex; this.#stateIndex = (++this.#stateIndex == 64) ? 0 : this.#stateIndex; this.#state[this.#stateIndex] = KeyStream.RXEncrypt(this.#state[this.#stateIndex], this.#key[this.#keyIndex], this.#workingByte); this.#state[63 - this.#stateIndex] = KeyStream.RXEncrypt(this.#state[63 - this.#stateIndex], this.#state[this.#stateIndex], this.#workingByte); return this.#state[this.#stateIndex]; } getBytes (nBytes) { nBytes = (nBytes|0 ? nBytes|0 : (parseInt(nBytes) ? parseInt(nBytes) : 1)); let k = new Uint8Array(nBytes); for (let o=0; o> 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); } } /** * 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); 09/07/2020 (4.0) * Version: 4.1 * Namespace: pastebin.com/u/k98kurz; github.com/k98kurz * License: ISC * * 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 * Instance Methods: * encrypt ( plaintext ) * encrypts plaintext with key stream derived from key and iv * decrypt ( ciphertext) * decrypts ciphertext with key stream derived from key * getKeyStream () * returns a KeyStream for PRNG generation * reset () * resets the internal KeyStream * Static Methods: * rxencrypt ( key, plaintext, optional iv ) * creates ephemeral RXCipher instance, calls encrypt, and returns {ciphertext}.{iv} * rxdecrypt ( key, ciphertextString ) * decrypts rxencrypt output * * 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 * 4.0 (09/07/2020): converted to es6 class; added static helper methods * 4.1 (09/08/2020): added one-way hash functions */ class RXCipher { #key; #iv; #stream; constructor (key, iv) { this.#key = (typeof key == 'object' && key instanceof Uint8Array) ? RXCipher.fromUTF8Array(key) : key; this.#iv = (typeof iv == 'object' && iv instanceof Uint8Array) ? RXCipher.fromUTF8Array(iv) : iv; this.#stream = new KeyStream(this.#key, this.#iv); } #encrypt (plaintext) { let ciphertext = new Uint8Array(plaintext.length); let key = this.#stream.getBytes(plaintext.length); for (let i = 0, ij = plaintext.length; i < ij; ++i) { ciphertext[i] = plaintext[i] ^ key[i]; } return ciphertext; } #decrypt (ciphertext) { let plaintext = new Uint8Array(ciphertext.length); let key = this.#stream.getBytes(ciphertext.length); for (let i = 0, ij = ciphertext.length; i < ij; ++i) { plaintext[i] = ciphertext[i] ^ key[i]; } return plaintext; } encrypt (plaintext) { let ciphertext = this.#encrypt(RXCipher.toUTF8Array(plaintext)); return RXCipher.toHex(ciphertext); } decrypt (ciphertext) { let plaintext = this.#decrypt(RXCipher.fromHex(ciphertext)); return RXCipher.fromUTF8Array(plaintext); } encryptRaw (plaintext) { return this.#encrypt(RXCipher.toUTF8Array(plaintext)); } decryptRaw (ciphertext) { return RXCipher.fromUTF8Array(this.#decrypt(ciphertext)); } static rxencrypt (key, plaintext, iv) { if (iv === undefined) iv = (new XORShiftPlus(Date.now())).bytes(8); let rxc = new RXCipher(key, iv); let ciphertext = rxc.encrypt(plaintext); return ciphertext + '.' + RXCipher.toHex(iv); } static rxdecrypt (key, ciphertextString) { let ctSplit = ciphertextString.split('.'); let ciphertext = ctSplit[0]; let iv = RXCipher.fromHex(ctSplit[1]); let rxc = new RXCipher(key, iv); return rxc.decrypt(ciphertext); } getKeyStream () { return new KeyStream(this.#key, this.#iv); } reset () { this.#stream = new KeyStream (this.#key, this.#iv); return this; } static RXEncrypt (t, k, workingByte) { workingByte[0] = (t+k); return workingByte[0]^k; } static RXDecrypt (c, k, workingByte) { workingByte[0] = (c^k) - k; return workingByte[0]; } // one-way functions static digestRound (data, iv, size) { let ks1 = new KeyStream(data, iv); let ks2 = new KeyStream(ks1.getBytes(size), ks1.getBytes(size)); return ks2.getBytes(size); } static digest (data, iv, rounds, size) { iv = iv ?? 'yellowsubmarine'; rounds = rounds ?? 12; size = size ?? 32; for (let i=0; i String.fromCharCode(c)).join('')); } static hashHex (data, iv, rounds, size) { let hash = RXCipher.digest(data, iv, rounds, size); return RXCipher.toHex(hash); } // convert from JavaScript's internal UTF-16 strings to UTF-8 // ripped from http://stackoverflow.com/a/18729931 // credit: Joni Salonen static toUTF8Array(str) { let utf8 = [], charcode; for (let 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) static fromUTF8Array (arr) { let utf16 = '', tmp = ''; for (let i = 0, ij = arr.length; i < ij; i++) { if (arr[i] <= 0x7F) { utf16 += RXCipher.decodeUtf8Char(tmp) + String.fromCharCode(arr[i]); tmp = ''; } else { tmp += '%' + arr[i].toString(16); } } return utf16 + RXCipher.decodeUtf8Char(tmp); } static decodeUtf8Char (str) { try { return decodeURIComponent(str); } catch (err) { return String.fromCharCode(0xFFFD); // UTF 8 invalid char } } static toHex (raw) { let t = []; for (let i=0, il=raw.length; i