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 |