View difference between Paste ID: nkQEVYEr and EdSbT7WN
SHOW: | | - or go back to the newest paste.
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
223+
 * Title:        RXCipher
224-
 * Description:    Simple and relatively quick stream cipher algorithm - JavaScript implementation
224+
 * Description:  Simple and relatively quick stream cipher algorithm - JavaScript implementation
225-
 * Author:    Jonathan Voss
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)
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
227+
 * Version:      4.1
228
 * Namespace:    pastebin.com/u/k98kurz; github.com/k98kurz
229-
 * License:    ISC
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
 * Class implementation
237
 * Instance Methods:
238
 *    encrypt ( plaintext )
239
 *      encrypts plaintext with key stream derived from key and iv
240-
 *      encrypts plaintext with key stream derived from skey
240+
241
 *      decrypts ciphertext with key stream derived from key
242-
 *      decrypts ciphertext with key stream derived from skey
242+
243
 *      returns a KeyStream for PRNG generation
244
 *    reset ()
245
 *      resets the internal KeyStream
246
 * Static Methods:
247
 *    rxencrypt ( key, plaintext, optional iv )
248
 *      creates ephemeral RXCipher instance, calls encrypt, and returns {ciphertext}.{iv}
249-
 *      creates ephemeral RXCipher instance, calls encrypt, and returns {iv}.{ciphertext}
249+
250
 *      decrypts rxencrypt output
251
 *
252
 * Changes:
253
 *  2.0 (12/16/2014): added pseudo-random key stream; dropped automatic hex conversion of ciphertext
254
 *  2.1 (12/29/2014): changed default seed value for XORShiftPlus PRNG
255
 *  2.2 (11/18/2015): converts to/from UTF-8 encoding; new keystream uses multiple csprngs and XORs the key with csprng values
256
 *  2.3 (11/18/2015): added explicit IV support; made base RXEncrypt & RXDecrypt use a Uint8 byte
257
 *  3.0 (11/19/2015): changed KeyStream to use an internally mixed 64 byte state; improved IV handling; improved performance
258
 *  3.1 (02/01/2016): updated KeyStream to use improved/fixed XORShiftPlus class; encrypt/decrypt use hex; encryptRaw/decryptRaw use Uint8Array
259
 *  3.2 (03/08/2016): bug fix: getKeyStream now returns actual KeyStream; reset method returns this
260
 *  4.0 (09/07/2020): converted to es6 class; added static helper methods
261
 *  4.1 (09/08/2020): added one-way hash functions
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
  // one-way functions
349
  static digestRound (data, iv, size) {
350
    let ks1 = new KeyStream(data, iv);
351
    let ks2 = new KeyStream(ks1.getBytes(size), ks1.getBytes(size));
352
    return ks2.getBytes(size);
353
  }
354
355
  static digest (data, iv, rounds, size) {
356
    iv = iv ?? 'yellowsubmarine';
357
    rounds = rounds ?? 12;
358
    size = size ?? 32;
359
    for (let i=0; i<rounds; ++i) {
360
      data = RXCipher.digestRound(data, iv, size);
361
    }
362
363
    return data;
364
  }
365
366
  static hashB64 (data, iv, rounds, size) {
367
    let hash = RXCipher.digest(data, iv, rounds, size);
368
  	return btoa([].slice.call(hash).map(c => String.fromCharCode(c)).join(''));
369
  }
370
371
  static hashHex (data, iv, rounds, size) {
372
    let hash = RXCipher.digest(data, iv, rounds, size);
373
    return RXCipher.toHex(hash);
374
  }
375
376
  // convert from JavaScript's internal UTF-16 strings to UTF-8
377
  // ripped from http://stackoverflow.com/a/18729931
378
  // credit: Joni Salonen
379
  static toUTF8Array(str) {
380
    let utf8 = [], charcode;
381
    for (let i=0; i < str.length; i++) {
382
      charcode = str.charCodeAt(i);
383
      if (charcode < 0x80) utf8.push(charcode);
384
      else if (charcode < 0x800) {
385
        utf8.push(0xc0 | (charcode >> 6),
386
            0x80 | (charcode & 0x3f));
387
      } else if (charcode < 0xd800 || charcode >= 0xe000) {
388
        utf8.push(0xe0 | (charcode >> 12),
389
            0x80 | ((charcode>>6) & 0x3f),
390
            0x80 | (charcode & 0x3f));
391
      } else { // surrogate pair
392
        i++;
393
        // UTF-16 encodes 0x10000-0x10FFFF by
394
        // subtracting 0x10000 and splitting the
395
        // 20 bits of 0x0-0xFFFFF into two halves
396
        charcode = 0x10000 + (((charcode & 0x3ff)<<10)
397
            | (str.charCodeAt(i) & 0x3ff));
398
        utf8.push(0xf0 | (charcode >>18),
399
            0x80 | ((charcode>>12) & 0x3f),
400
            0x80 | ((charcode>>6) & 0x3f),
401
            0x80 | (charcode & 0x3f));
402
      }
403
    }
404
    return new Uint8Array(utf8);
405
  }
406
407
  // convert from UTF-8 to JavaScript's internal UTF-16 strings
408
  // ripped from https://github.com/coolaj86/TextEncoderLite
409
  // credit: AJ ONeal (coolaj86) && Feross Aboukhadijeh (feross)
410
  static fromUTF8Array (arr) {
411
    let utf16 = '', tmp = '';
412
    for (let i = 0, ij = arr.length; i < ij; i++) {
413
      if (arr[i] <= 0x7F) {
414
        utf16 += RXCipher.decodeUtf8Char(tmp) + String.fromCharCode(arr[i]);
415
        tmp = '';
416
      } else {
417
        tmp += '%' + arr[i].toString(16);
418
      }
419
    }
420
421
    return utf16 + RXCipher.decodeUtf8Char(tmp);
422
  }
423
424
  static decodeUtf8Char (str) {
425
    try {
426
      return decodeURIComponent(str);
427
    } catch (err) {
428
      return String.fromCharCode(0xFFFD); // UTF 8 invalid char
429
    }
430
  }
431
432
  static toHex (raw) {
433
    let t = [];
434
    for (let i=0, il=raw.length; i<il; ++i) {
435
        t[i] = raw[i].toString(16); t[i] = t[i].length%2 ? '0'+t[i] : t[i];
436
    }
437
    return t.join('');
438
  }
439
440
  static fromHex (hex) {
441
    let t = new Uint8Array(hex.length/2);
442
    for (let i=0, j=0, il=hex.length; i<il; ++i, ++j) {
443
        t[j] = parseInt(hex[i] + hex[++i], 16);
444
    }
445
    return t;
446
  }
447
}
448