Advertisement
k98kurz

rxcipher.4.0.js

Sep 7th, 2020 (edited)
193
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /**
  2.  *  XORShiftPlus "non-linear" PRNG
  3.  *  Author: k98kurz (@gmail)
  4.  *  License: MIT
  5.  *  Date: 2020-09-07
  6.  *  Description: Based upon the xorshift128+ generator, one of the fastest generators passing BigCrush.
  7.  *  Methods:
  8.  *      next(n)         returns Uint32Array of n random numbers
  9.  *      nextHex(n)      returns Array of n hexidecimal numbers
  10.  *      bytes(n)        returns Uint8Array of n random bytes
  11.  *      bytesHex(n)     returns Array of n random bytes in hexidecimal
  12.  *      hex(n)          returns hexidecimal string of n bytes
  13.  *  Note: This generates with higher periodicity and better random distribution than the linear XORShift.
  14.  */
  15. class XORShiftPlus {
  16.   #booted = false;
  17.   #x;
  18.   #y;
  19.   #z;
  20.   #w;
  21.  
  22.   constructor (seed) {
  23.     this.#x = new Uint32Array(1);
  24.     this.#y = new Uint32Array(1);
  25.     this.#z = new Uint32Array(1);
  26.     this.#w = new Uint32Array(1);
  27.     this.#x[0] = seed ? seed|0 : 317973455;
  28.     this.#y[0] = this.#x[0]<<362436069;
  29.     this.#z[0] = this.#y[0]+this.#x[0];
  30.     this.#w[0] = this.#z[0]^this.#x[0]+this.#y[0];
  31.   }
  32.  
  33.   #next () {
  34.     let t = new Uint32Array(1);
  35.     t[0] = this.#x[0]^(this.#x[0]<<11);
  36.     this.#x[0] = this.#y[0];
  37.     this.#y[0] = this.#z[0];
  38.     this.#z[0] = this.#w[0];
  39.     this.#w[0] = this.#w[0]^(this.#w[0]>>19)^(t[0]^(t[0]>>8));
  40.     return this.#w[0]+this.#y[0];
  41.   }
  42.  
  43.   #boot () {
  44.     // if not booted, discard the first numbers as they are somewhat predictable
  45.     if (this.#booted)
  46.       return;
  47.     for (let i=0, n=this.#next(), j=this.#next()%256; i<=j; this.#next(), ++i);
  48.     this.#booted = true;
  49.   }
  50.  
  51.   next (n, option) {
  52.     this.#boot();
  53.     n = (n === undefined || typeof n !== 'number') ? 1 : n;
  54.     let t; n |= 0; n = n>0 ? n : 1;
  55.     option = (option === undefined || typeof n !== 'number') ? 0 : option;
  56.  
  57.     // redundant code, but improved performance
  58.     switch (option) {
  59.       // nextHex
  60.       case 1:
  61.         t = [];
  62.         for (let i=0, il=n; i<il; t[i++] = this.#next().toString(16));
  63.         break;
  64.       // bytes
  65.       case 2:
  66.         t = new Uint8Array(n);
  67.         for (let i=0, il=n; i<il; t[i++] = this.#next());
  68.         break;
  69.       // bytesHex
  70.       case 3:
  71.         t = [];
  72.         for (let i=0, il=n; i<il; t[i] = (this.#next()%256).toString(16), t[i] = t[i].length%2 ? '0'+t[i] : t[i], ++i);
  73.         break;
  74.       // hex
  75.       case 4:
  76.         t = [];
  77.         for (let i=0, il=n; i<il; t[i] = (this.#next()%256).toString(16), t[i] = t[i].length%2 ? '0'+t[i] : t[i], ++i);
  78.         t = t.join('');
  79.         break;
  80.       // next
  81.       default:
  82.         t = new Uint32Array(n);
  83.         for (let i=0, il=n; i<il; t[i++] = this.#next());
  84.     }
  85.     return t;
  86.   };
  87.  
  88.   nextHex (n) { return this.next(n, 1); };
  89.  
  90.   bytes (n) { return this.next(n, 2); };
  91.  
  92.   bytesHex (n) { return this.next(n, 3); };
  93.  
  94.   hex (n) { return this.next(n, 4); };
  95. }
  96.  
  97. /**
  98.  *  KeyStream
  99.  *  Author: k98kurz
  100.  *  License: MIT
  101.  *  Date: 2020-09-07
  102.  *  Description: Takes a key and iv, mixes them, and uses the result to seed several XORShiftPlus PRNGs.
  103.  *  Dependency: XORShiftPlus
  104.  *  Methods:
  105.  *
  106.  */
  107. class KeyStream {
  108.   #csprngs;
  109.   #state;
  110.   #keyLength;
  111.   #keyIndex;
  112.   #stateIndex;
  113.   #workingByte = new Uint8Array(1);
  114.   #key;
  115.   #iv;
  116.  
  117.   constructor (key, iv) {
  118.     if (typeof key == 'undefined')
  119.       throw new Error('KeyStream: key required');
  120.     if (typeof key == 'object' && key instanceof Array)
  121.       key = (typeof key[0] == 'number') ? new Uint8Array(key) : key.join('');
  122.     if (typeof key == 'string')
  123.       key = KeyStream.toUTF8Array(key);
  124.     if (typeof key !== 'object' && !(key instanceof Uint8Array))
  125.       throw new Error('KeyStream: expected key of type String, Array, or Uint8Array');
  126.  
  127.     if (typeof iv == 'undefined')
  128.       throw new Error('KeyStream: iv required');
  129.     if (typeof iv == 'object' && iv instanceof Array)
  130.       iv = (typeof iv[0] == 'number') ? new Uint8Array(iv) : iv.join('');
  131.     if (typeof iv == 'string')
  132.       iv = KeyStream.toUTF8Array(iv);
  133.     if (!(iv instanceof Uint8Array) || iv.length == 0)
  134.       throw new Error('KeyStream: expected iv of type String, Array or Uint8Array');
  135.  
  136.     // set the key and iv private properties
  137.     this.#key = key;
  138.     this.#iv = iv;
  139.  
  140.     // initialize some internal state
  141.     this.#csprngs = []; this.#keyLength = this.#key.length; this.#keyIndex = 0; this.#state = new Uint8Array(64); this.#stateIndex = 0;
  142.     for (let i = 0; i < this.#keyLength; i++) {
  143.       this.#csprngs[i] = new XORShiftPlus(this.#key[i]);
  144.     }
  145.     for (let i = 0, j = 0, c = 0; c < this.#keyLength * 4; ++i, ++j, ++c) {
  146.       j = (j == this.#keyLength) ? 0 : j;
  147.       i = (i == this.#iv.length) ? 0 : i;
  148.       this.#key[j] = this.#key[j] + this.#iv[i];
  149.     }
  150.     for (let i = 0, j = 0; i < 64; ++i, ++j) {
  151.       j = (j == this.#keyLength) ? 0 : j;
  152.       this.#state[i] = this.#csprngs[j].next()[0];
  153.     }
  154.     for (let i = 63, j = 0, t, m; i >= 0; --i, ++j) {
  155.       j = (j == this.#keyLength) ? 0 : j;
  156.       t = (this.#csprngs[j].next()[0] + this.#key[j]) % 64;
  157.       m = this.#state[t];
  158.       this.#state[t] = KeyStream.RXEncrypt(this.#state[i], this.#key[j], this.#workingByte);
  159.       this.#state[i] = KeyStream.RXDecrypt(m, t, this.#workingByte);
  160.     }
  161.   }
  162.  
  163.   #next () {
  164.     this.#keyIndex = (++this.#keyIndex == this.#keyLength) ? 0 : this.#keyIndex;
  165.     this.#stateIndex = (++this.#stateIndex == 64) ? 0 : this.#stateIndex;
  166.     this.#state[this.#stateIndex] = KeyStream.RXEncrypt(this.#state[this.#stateIndex], this.#key[this.#keyIndex], this.#workingByte);
  167.     this.#state[63 - this.#stateIndex] = KeyStream.RXEncrypt(this.#state[63 - this.#stateIndex], this.#state[this.#stateIndex], this.#workingByte);
  168.     return this.#state[this.#stateIndex];
  169.   }
  170.  
  171.   getBytes (nBytes) {
  172.     nBytes = (nBytes|0 ? nBytes|0 : (parseInt(nBytes) ? parseInt(nBytes) : 1));
  173.     let k = new Uint8Array(nBytes);
  174.     for (let o=0; o<nBytes; ++o) {
  175.       k[o] = this.#next();
  176.     }
  177.     return k;
  178.   }
  179.  
  180.   static RXEncrypt (t, k, workingByte) {
  181.     workingByte[0] = (t+k);
  182.     return workingByte[0]^k;
  183.   }
  184.  
  185.   static RXDecrypt (c, k, workingByte) {
  186.     workingByte[0] = (c^k) - k;
  187.     return workingByte[0];
  188.   }
  189.  
  190.   // convert from JavaScript's internal UTF-16 strings to UTF-8
  191.   // ripped from http://stackoverflow.com/a/18729931
  192.   // credit: Joni Salonen
  193.   static toUTF8Array(str) {
  194.     let utf8 = [], charcode;
  195.     for (let i=0; i < str.length; i++) {
  196.       charcode = str.charCodeAt(i);
  197.       if (charcode < 0x80) utf8.push(charcode);
  198.       else if (charcode < 0x800) {
  199.         utf8.push(0xc0 | (charcode >> 6),
  200.             0x80 | (charcode & 0x3f));
  201.       } else if (charcode < 0xd800 || charcode >= 0xe000) {
  202.         utf8.push(0xe0 | (charcode >> 12),
  203.             0x80 | ((charcode>>6) & 0x3f),
  204.             0x80 | (charcode & 0x3f));
  205.       } else { // surrogate pair
  206.         i++;
  207.         // UTF-16 encodes 0x10000-0x10FFFF by
  208.         // subtracting 0x10000 and splitting the
  209.         // 20 bits of 0x0-0xFFFFF into two halves
  210.         charcode = 0x10000 + (((charcode & 0x3ff)<<10)
  211.             | (str.charCodeAt(i) & 0x3ff));
  212.         utf8.push(0xf0 | (charcode >>18),
  213.             0x80 | ((charcode>>12) & 0x3f),
  214.             0x80 | ((charcode>>6) & 0x3f),
  215.             0x80 | (charcode & 0x3f));
  216.       }
  217.     }
  218.     return new Uint8Array(utf8);
  219.   }
  220. }
  221.  
  222. /**
  223.  * Title:    RXCipher
  224.  * Description:    Simple and relatively quick stream cipher algorithm - JavaScript implementation
  225.  * Author:    Jonathan Voss
  226.  * Date:    8/7/2012 (v1.0); 12/16/2014 (v2.0); 11/19/2015 (v3.0); 09/07/2020 (4.0)
  227.  * Version:    4.0
  228.  * Namespace:    pastebin.com/u/k98kurz; github.com/k98kurz
  229.  * License:    ISC
  230.  *
  231.  * Basic byte-level algorithm
  232.  * t = plaintext byte, c = ciphertext byte, k = encryption keystream byte
  233.  *    function enc (t, k) { return (t+k>255 ? t+k-256 : t+k)^k; }
  234.  *    function dec (c, k) { return (c^k)-k<0 ? (c^k)-k+256 : (c^k)-k; }
  235.  *
  236.  *
  237.  * Class implementation
  238.  * Instance Methods:
  239.  *    encrypt ( plaintext )
  240.  *      encrypts plaintext with key stream derived from skey
  241.  *    decrypt ( ciphertext)
  242.  *      decrypts ciphertext with key stream derived from skey
  243.  *    getKeyStream ()
  244.  *      returns a KeyStream for PRNG generation
  245.  *    reset ()
  246.  *      resets the internal KeyStream
  247.  * Static Methods:
  248.  *    rxencrypt ( key, plaintext, optional iv )
  249.  *      creates ephemeral RXCipher instance, calls encrypt, and returns {iv}.{ciphertext}
  250.  *    rxdecrypt ( key, ciphertextString )
  251.  *      decrypts rxencrypt output
  252.  *
  253.  * Changes:
  254.  *  2.0 (12/16/2014): added pseudo-random key stream; dropped automatic hex conversion of ciphertext
  255.  *  2.1 (12/29/2014): changed default seed value for XORShiftPlus PRNG
  256.  *  2.2 (11/18/2015): converts to/from UTF-8 encoding; new keystream uses multiple csprngs and XORs the key with csprng values
  257.  *  2.3 (11/18/2015): added explicit IV support; made base RXEncrypt & RXDecrypt use a Uint8 byte
  258.  *  3.0 (11/19/2015): changed KeyStream to use an internally mixed 64 byte state; improved IV handling; improved performance
  259.  *  3.1 (02/01/2016): updated KeyStream to use improved/fixed XORShiftPlus class; encrypt/decrypt use hex; encryptRaw/decryptRaw use Uint8Array
  260.  *  3.2 (03/08/2016): bug fix: getKeyStream now returns actual KeyStream; reset method returns this
  261.  *  4.0 (09/07/2020): converted to es6 class; added static helper methods
  262. */
  263. class RXCipher {
  264.   #key;
  265.   #iv;
  266.   #stream;
  267.  
  268.   constructor (key, iv) {
  269.     this.#key = (typeof key == 'object' && key instanceof Uint8Array) ? RXCipher.fromUTF8Array(key) : key;
  270.     this.#iv = (typeof iv == 'object' && iv instanceof Uint8Array) ? RXCipher.fromUTF8Array(iv) : iv;
  271.     this.#stream = new KeyStream(this.#key, this.#iv);
  272.   }
  273.  
  274.   #encrypt (plaintext) {
  275.     let ciphertext = new Uint8Array(plaintext.length);
  276.     let key = this.#stream.getBytes(plaintext.length);
  277.     for (let i = 0, ij = plaintext.length; i < ij; ++i) {
  278.       ciphertext[i] = plaintext[i] ^ key[i];
  279.     }
  280.     return ciphertext;
  281.   }
  282.  
  283.   #decrypt (ciphertext) {
  284.     let plaintext = new Uint8Array(ciphertext.length);
  285.     let key = this.#stream.getBytes(ciphertext.length);
  286.     for (let i = 0, ij = ciphertext.length; i < ij; ++i) {
  287.       plaintext[i] = ciphertext[i] ^ key[i];
  288.     }
  289.     return plaintext;
  290.   }
  291.  
  292.   encrypt (plaintext) {
  293.     let ciphertext = this.#encrypt(RXCipher.toUTF8Array(plaintext));
  294.     return RXCipher.toHex(ciphertext);
  295.   }
  296.  
  297.   decrypt (ciphertext) {
  298.     let plaintext = this.#decrypt(RXCipher.fromHex(ciphertext));
  299.     return RXCipher.fromUTF8Array(plaintext);
  300.   }
  301.  
  302.   encryptRaw (plaintext) {
  303.     return this.#encrypt(RXCipher.toUTF8Array(plaintext));
  304.   }
  305.  
  306.   decryptRaw (ciphertext) {
  307.     return RXCipher.fromUTF8Array(this.#decrypt(ciphertext));
  308.   }
  309.  
  310.   static rxencrypt (key, plaintext, iv) {
  311.     if (iv === undefined)
  312.       iv = (new XORShiftPlus(Date.now())).bytes(8);
  313.  
  314.     let rxc = new RXCipher(key, iv);
  315.     let ciphertext = rxc.encrypt(plaintext);
  316.  
  317.     return ciphertext + '.' + RXCipher.toHex(iv);
  318.   }
  319.  
  320.   static rxdecrypt (key, ciphertextString) {
  321.     let ctSplit = ciphertextString.split('.');
  322.     let ciphertext = ctSplit[0];
  323.     let iv = RXCipher.fromHex(ctSplit[1]);
  324.     let rxc = new RXCipher(key, iv);
  325.  
  326.     return rxc.decrypt(ciphertext);
  327.   }
  328.  
  329.   getKeyStream () {
  330.     return new KeyStream(this.#key, this.#iv);
  331.   }
  332.  
  333.   reset () {
  334.     this.#stream = new KeyStream (this.#key, this.#iv);
  335.     return this;
  336.   }
  337.  
  338.   static RXEncrypt (t, k, workingByte) {
  339.     workingByte[0] = (t+k);
  340.     return workingByte[0]^k;
  341.   }
  342.  
  343.   static RXDecrypt (c, k, workingByte) {
  344.     workingByte[0] = (c^k) - k;
  345.     return workingByte[0];
  346.   }
  347.  
  348.   // convert from JavaScript's internal UTF-16 strings to UTF-8
  349.   // ripped from http://stackoverflow.com/a/18729931
  350.   // credit: Joni Salonen
  351.   static toUTF8Array(str) {
  352.     let utf8 = [], charcode;
  353.     for (let i=0; i < str.length; i++) {
  354.       charcode = str.charCodeAt(i);
  355.       if (charcode < 0x80) utf8.push(charcode);
  356.       else if (charcode < 0x800) {
  357.         utf8.push(0xc0 | (charcode >> 6),
  358.             0x80 | (charcode & 0x3f));
  359.       } else if (charcode < 0xd800 || charcode >= 0xe000) {
  360.         utf8.push(0xe0 | (charcode >> 12),
  361.             0x80 | ((charcode>>6) & 0x3f),
  362.             0x80 | (charcode & 0x3f));
  363.       } else { // surrogate pair
  364.         i++;
  365.         // UTF-16 encodes 0x10000-0x10FFFF by
  366.         // subtracting 0x10000 and splitting the
  367.         // 20 bits of 0x0-0xFFFFF into two halves
  368.         charcode = 0x10000 + (((charcode & 0x3ff)<<10)
  369.             | (str.charCodeAt(i) & 0x3ff));
  370.         utf8.push(0xf0 | (charcode >>18),
  371.             0x80 | ((charcode>>12) & 0x3f),
  372.             0x80 | ((charcode>>6) & 0x3f),
  373.             0x80 | (charcode & 0x3f));
  374.       }
  375.     }
  376.     return new Uint8Array(utf8);
  377.   }
  378.  
  379.   // convert from UTF-8 to JavaScript's internal UTF-16 strings
  380.   // ripped from https://github.com/coolaj86/TextEncoderLite
  381.   // credit: AJ ONeal (coolaj86) && Feross Aboukhadijeh (feross)
  382.   static fromUTF8Array (arr) {
  383.     let utf16 = '', tmp = '';
  384.     for (let i = 0, ij = arr.length; i < ij; i++) {
  385.       if (arr[i] <= 0x7F) {
  386.         utf16 += RXCipher.decodeUtf8Char(tmp) + String.fromCharCode(arr[i]);
  387.         tmp = '';
  388.       } else {
  389.         tmp += '%' + arr[i].toString(16);
  390.       }
  391.     }
  392.  
  393.     return utf16 + RXCipher.decodeUtf8Char(tmp);
  394.   }
  395.  
  396.   static decodeUtf8Char (str) {
  397.     try {
  398.       return decodeURIComponent(str);
  399.     } catch (err) {
  400.       return String.fromCharCode(0xFFFD); // UTF 8 invalid char
  401.     }
  402.   }
  403.  
  404.   static toHex (raw) {
  405.     let t = [];
  406.     for (let i=0, il=raw.length; i<il; ++i) {
  407.         t[i] = raw[i].toString(16); t[i] = t[i].length%2 ? '0'+t[i] : t[i];
  408.     }
  409.     return t.join('');
  410.   }
  411.  
  412.   static fromHex (hex) {
  413.     let t = new Uint8Array(hex.length/2);
  414.     for (let i=0, j=0, il=hex.length; i<il; ++i, ++j) {
  415.         t[j] = parseInt(hex[i] + hex[++i], 16);
  416.     }
  417.     return t;
  418.   }
  419. }
  420.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement