k98kurz icon

rxcipher.3.2.js

k98kurz | PRO | 07/14/21 11:11:29 PM UTC (Edited) | 0 ⭐ | 679 👁️ | Never ⏰ | []
JavaScript |

9.96 KB

|

None

|

0 👍

/

0 👎

/**
 * 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