SHOW:
|
|
- or go back to the newest paste.
1 | /** | |
2 | - | * Title: RXCipher |
2 | + | * XORShiftPlus "non-linear" PRNG |
3 | - | * Description: Simple and relatively quick stream cipher algorithm - JavaScript implementation |
3 | + | * Author: k98kurz (@gmail) |
4 | - | * Author: Jonathan Voss |
4 | + | * License: MIT |
5 | - | * Date: 8/7/2012 (v1.0); 12/16/2014 (v2.0); 11/19/2015 (v3.0) |
5 | + | * Date: 2020-09-07 |
6 | - | * Version: 3.2 |
6 | + | * Description: Based upon the xorshift128+ generator, one of the fastest generators passing BigCrush. |
7 | - | * Namespace: github.com/k98kurz |
7 | + | * Methods: |
8 | - | * License: MIT |
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 | - | * function enc (t, k) { return (t+k>255 ? t+k-256 : t+k)^k; } |
12 | + | * hex(n) returns hexidecimal string of n bytes |
13 | - | * function dec (c, k) { return (c^k)-k<0 ? (c^k)-k+256 : (c^k)-k; } |
13 | + | * Note: This generates with higher periodicity and better random distribution than the linear XORShift. |
14 | */ | |
15 | class XORShiftPlus { | |
16 | #booted = false; | |
17 | - | * Methods: |
17 | + | #x; |
18 | - | * encrypt ( plaintext ) |
18 | + | #y; |
19 | - | * encrypts plaintext with key stream derived from skey |
19 | + | #z; |
20 | - | * decrypt ( ciphertext) |
20 | + | #w; |
21 | - | * decrypts ciphertext with key stream derived from skey |
21 | + | |
22 | - | * getKeyStream () |
22 | + | constructor (seed) { |
23 | - | * returns a KeyStream for PRNG generation |
23 | + | this.#x = new Uint32Array(1); |
24 | - | * reset () |
24 | + | this.#y = new Uint32Array(1); |
25 | - | * resets the internal KeyStream |
25 | + | this.#z = new Uint32Array(1); |
26 | this.#w = new Uint32Array(1); | |
27 | this.#x[0] = seed ? seed|0 : 317973455; | |
28 | - | * 2.0 (12/16/2014): added pseudo-random key stream; dropped automatic hex conversion of ciphertext |
28 | + | this.#y[0] = this.#x[0]<<362436069; |
29 | - | * 2.1 (12/29/2014): changed default seed value for XORShiftPlus PRNG |
29 | + | this.#z[0] = this.#y[0]+this.#x[0]; |
30 | - | * 2.2 (11/18/2015): converts to/from UTF-8 encoding; new keystream uses multiple csprngs and XORs the key with csprng values |
30 | + | this.#w[0] = this.#z[0]^this.#x[0]+this.#y[0]; |
31 | - | * 2.3 (11/18/2015): added explicit IV support; made base RXEncrypt & RXDecrypt use a Uint8 byte |
31 | + | } |
32 | - | * 3.0 (11/19/2015): changed KeyStream to use an internally mixed 64 byte state; improved IV handling; improved performance |
32 | + | |
33 | - | * 3.1 (02/01/2016): updated KeyStream to use improved/fixed XORShiftPlus class; encrypt/decrypt use hex; encryptRaw/decryptRaw use Uint8Array |
33 | + | #next () { |
34 | - | * 3.2 (03/08/2016): bug fix: getKeyStream now returns actual KeyStream; reset method returns this |
34 | + | let t = new Uint32Array(1); |
35 | t[0] = this.#x[0]^(this.#x[0]<<11); | |
36 | this.#x[0] = this.#y[0]; | |
37 | - | function RXCipher (key, iv) { |
37 | + | this.#y[0] = this.#z[0]; |
38 | - | var skey, siv = iv, stream; |
38 | + | this.#z[0] = this.#w[0]; |
39 | - | skey = (typeof key == 'object' && key instanceof Uint8Array) ? fromUTF8Array(key) : key; |
39 | + | this.#w[0] = this.#w[0]^(this.#w[0]>>19)^(t[0]^(t[0]>>8)); |
40 | - | siv = (typeof iv == 'object' && iv instanceof Uint8Array) ? fromUTF8Array(iv) : iv; |
40 | + | return this.#w[0]+this.#y[0]; |
41 | - | stream = new KeyStream(skey, siv); |
41 | + | } |
42 | ||
43 | - | function encrypt (plaintext) { |
43 | + | #boot () { |
44 | - | var ciphertext = new Uint8Array(plaintext.length), g, t, k, key; |
44 | + | // if not booted, discard the first numbers as they are somewhat predictable |
45 | - | key = stream.get(plaintext.length); |
45 | + | if (this.#booted) |
46 | - | for (var i = 0, ij = plaintext.length; i < ij; ++i) { |
46 | + | return; |
47 | - | ciphertext[i] = plaintext[i] ^ key[i]; |
47 | + | for (let i=0, n=this.#next(), j=this.#next()%256; i<=j; this.#next(), ++i); |
48 | - | } |
48 | + | this.#booted = true; |
49 | - | return ciphertext; |
49 | + | } |
50 | - | } |
50 | + | |
51 | next (n, option) { | |
52 | - | function decrypt (ciphertext) { |
52 | + | this.#boot(); |
53 | - | var plaintext = new Uint8Array(ciphertext.length), g, c, k, key; |
53 | + | n = (n === undefined || typeof n !== 'number') ? 1 : n; |
54 | - | key = stream.get(ciphertext.length); |
54 | + | let t; n |= 0; n = n>0 ? n : 1; |
55 | - | for (var i = 0, ij = ciphertext.length; i < ij; ++i) { |
55 | + | option = (option === undefined || typeof n !== 'number') ? 0 : option; |
56 | - | plaintext[i] = ciphertext[i] ^ key[i]; |
56 | + | |
57 | - | } |
57 | + | // redundant code, but improved performance |
58 | - | return plaintext; |
58 | + | switch (option) { |
59 | - | } |
59 | + | // nextHex |
60 | case 1: | |
61 | - | this.encrypt = function (plaintext) { |
61 | + | t = []; |
62 | - | var ciphertext = encrypt(toUTF8Array(plaintext)); |
62 | + | for (let i=0, il=n; i<il; t[i++] = this.#next().toString(16)); |
63 | - | return toHex(ciphertext); |
63 | + | break; |
64 | - | }; |
64 | + | // bytes |
65 | case 2: | |
66 | - | this.decrypt = function (ciphertext) { |
66 | + | t = new Uint8Array(n); |
67 | - | var plaintext = decrypt(fromHex(ciphertext)); |
67 | + | for (let i=0, il=n; i<il; t[i++] = this.#next()); |
68 | - | return fromUTF8Array(plaintext); |
68 | + | break; |
69 | - | }; |
69 | + | // bytesHex |
70 | case 3: | |
71 | - | this.encryptRaw = function (plaintext) { |
71 | + | t = []; |
72 | - | return encrypt(toUTF8Array(plaintext)); |
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 | - | }; |
73 | + | break; |
74 | // hex | |
75 | - | this.decryptRaw = function (ciphertext) { |
75 | + | case 4: |
76 | - | return fromUTF8Array(decrypt(ciphertext)); |
76 | + | t = []; |
77 | - | }; |
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 | - | this.getKeyStream = function () { |
79 | + | break; |
80 | - | return new KeyStream(skey, siv); |
80 | + | // next |
81 | - | }; |
81 | + | default: |
82 | t = new Uint32Array(n); | |
83 | - | this.reset = function () { |
83 | + | for (let i=0, il=n; i<il; t[i++] = this.#next()); |
84 | - | stream = new KeyStream (skey, siv); |
84 | + | } |
85 | - | return this; |
85 | + | return t; |
86 | - | }; |
86 | + | }; |
87 | ||
88 | - | function RXEncrypt (t, k, workingByte) { |
88 | + | nextHex (n) { return this.next(n, 1); }; |
89 | - | workingByte[0] = (t+k); |
89 | + | |
90 | - | return workingByte[0]^k; |
90 | + | bytes (n) { return this.next(n, 2); }; |
91 | - | } |
91 | + | |
92 | bytesHex (n) { return this.next(n, 3); }; | |
93 | - | function RXDecrypt (c, k, workingByte) { |
93 | + | |
94 | - | workingByte[0] = (c^k) - k; |
94 | + | hex (n) { return this.next(n, 4); }; |
95 | - | return workingByte[0]; |
95 | + | } |
96 | - | } |
96 | + | |
97 | /** | |
98 | - | /** |
98 | + | * KeyStream |
99 | - | * Class: XORShiftPlus: "non-linear" PRNG |
99 | + | * Author: k98kurz |
100 | - | * Description: Based upon the xorshift128+ generator, one of the fastest generators passing BigCrush. |
100 | + | * License: MIT |
101 | - | * Methods: |
101 | + | * Date: 2020-09-07 |
102 | - | * next(n) returns Uint32Array of n random numbers |
102 | + | * Description: Takes a key and iv, mixes them, and uses the result to seed several XORShiftPlus PRNGs. |
103 | - | * nextHex(n) returns Array of n hexidecimal numbers |
103 | + | * Dependency: XORShiftPlus |
104 | - | * bytes(n) returns Uint8Array of n random bytes |
104 | + | * Methods: |
105 | - | * bytesHex(n) returns Array of n random bytes in hexidecimal |
105 | + | |
106 | - | * hex(n) returns hexidecimal string of n bytes |
106 | + | */ |
107 | - | * Note: This generates with higher periodicity and better random distribution than the linear XORShift |
107 | + | class KeyStream { |
108 | - | */ |
108 | + | #csprngs; |
109 | - | function XORShiftPlus (seed) { |
109 | + | #state; |
110 | - | var x = new Uint32Array(1), y = new Uint32Array(1), z = new Uint32Array(1), w = new Uint32Array(1); |
110 | + | #keyLength; |
111 | - | x[0] = seed ? seed|0 : 317973455; |
111 | + | #keyIndex; |
112 | - | y[0] = x[0]<<362436069; |
112 | + | #stateIndex; |
113 | - | z[0] = y[0]+x[0]; |
113 | + | #workingByte = new Uint8Array(1); |
114 | - | w[0] = z[0]^x[0]+y[0]; |
114 | + | #key; |
115 | #iv; | |
116 | - | function next () { |
116 | + | |
117 | - | var t = new Uint32Array(1); |
117 | + | constructor (key, iv) { |
118 | - | t[0] = x[0]^(x<<11); |
118 | + | if (typeof key == 'undefined') |
119 | - | x[0] = y[0]; y[0] = z[0]; z[0] = w[0]; |
119 | + | throw new Error('KeyStream: key required'); |
120 | - | w[0] = w[0]^(w[0]>>19)^(t[0]^(t[0]>>8)); |
120 | + | if (typeof key == 'object' && key instanceof Array) |
121 | - | return w[0]+y[0]; |
121 | + | key = (typeof key[0] == 'number') ? new Uint8Array(key) : key.join(''); |
122 | - | } |
122 | + | if (typeof key == 'string') |
123 | key = KeyStream.toUTF8Array(key); | |
124 | - | // discard the first numbers as they are somewhat predictable |
124 | + | if (typeof key !== 'object' && !(key instanceof Uint8Array)) |
125 | - | for (var i=0, n=next(), j=next()%256; i<=j; next(), ++i); |
125 | + | throw new Error('KeyStream: expected key of type String, Array, or Uint8Array'); |
126 | ||
127 | - | this.next = function (n, option) { |
127 | + | if (typeof iv == 'undefined') |
128 | - | if (typeof n == 'undefined' || typeof n !== 'number') |
128 | + | throw new Error('KeyStream: iv required'); |
129 | - | var n = 1; |
129 | + | if (typeof iv == 'object' && iv instanceof Array) |
130 | - | var t; n |= 0; n = n>0 ? n : 1; |
130 | + | iv = (typeof iv[0] == 'number') ? new Uint8Array(iv) : iv.join(''); |
131 | - | if (typeof option == 'undefined' || typeof n !== 'number') |
131 | + | if (typeof iv == 'string') |
132 | - | var option = 0; |
132 | + | iv = KeyStream.toUTF8Array(iv); |
133 | if (!(iv instanceof Uint8Array) || iv.length == 0) | |
134 | - | // redundant code, but improved performance |
134 | + | throw new Error('KeyStream: expected iv of type String, Array or Uint8Array'); |
135 | - | switch (option) { |
135 | + | |
136 | - | // nextHex |
136 | + | // set the key and iv private properties |
137 | - | case 1: |
137 | + | this.#key = key; |
138 | - | t = []; |
138 | + | this.#iv = iv; |
139 | - | for (var i=0, il=n; i<il; t[i++] = next().toString(16)); |
139 | + | |
140 | - | break; |
140 | + | // initialize some internal state |
141 | - | // bytes |
141 | + | this.#csprngs = []; this.#keyLength = this.#key.length; this.#keyIndex = 0; this.#state = new Uint8Array(64); this.#stateIndex = 0; |
142 | - | case 2: |
142 | + | for (let i = 0; i < this.#keyLength; i++) { |
143 | - | t = new Uint8Array(n); |
143 | + | this.#csprngs[i] = new XORShiftPlus(this.#key[i]); |
144 | - | for (var i=0, il=n; i<il; t[i++] = next()); |
144 | + | } |
145 | - | break; |
145 | + | for (let i = 0, j = 0, c = 0; c < this.#keyLength * 4; ++i, ++j, ++c) { |
146 | - | // bytesHex |
146 | + | j = (j == this.#keyLength) ? 0 : j; |
147 | - | case 3: |
147 | + | i = (i == this.#iv.length) ? 0 : i; |
148 | - | t = []; |
148 | + | this.#key[j] = this.#key[j] + this.#iv[i]; |
149 | - | 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); |
149 | + | } |
150 | - | break; |
150 | + | for (let i = 0, j = 0; i < 64; ++i, ++j) { |
151 | - | // hex |
151 | + | j = (j == this.#keyLength) ? 0 : j; |
152 | - | case 4: |
152 | + | this.#state[i] = this.#csprngs[j].next()[0]; |
153 | - | t = []; |
153 | + | } |
154 | - | 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); |
154 | + | for (let i = 63, j = 0, t, m; i >= 0; --i, ++j) { |
155 | - | t = t.join(''); |
155 | + | j = (j == this.#keyLength) ? 0 : j; |
156 | - | break; |
156 | + | t = (this.#csprngs[j].next()[0] + this.#key[j]) % 64; |
157 | - | // next |
157 | + | m = this.#state[t]; |
158 | - | default: |
158 | + | this.#state[t] = KeyStream.RXEncrypt(this.#state[i], this.#key[j], this.#workingByte); |
159 | - | t = new Uint32Array(n); |
159 | + | this.#state[i] = KeyStream.RXDecrypt(m, t, this.#workingByte); |
160 | - | for (var i=0, il=n; i<il; t[i++] = next()); |
160 | + | } |
161 | - | } |
161 | + | } |
162 | - | return t; |
162 | + | |
163 | - | }; |
163 | + | #next () { |
164 | this.#keyIndex = (++this.#keyIndex == this.#keyLength) ? 0 : this.#keyIndex; | |
165 | - | this.nextHex = function (n) { return this.next(n, 1); }; |
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.bytes = function (n) { return this.next(n, 2); }; |
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 | - | this.bytesHex = function (n) { return this.next(n, 3); }; |
169 | + | } |
170 | ||
171 | - | this.hex = function (n) { return this.next(n, 4); }; |
171 | + | getBytes (nBytes) { |
172 | - | } |
172 | + | nBytes = (nBytes|0 ? nBytes|0 : (parseInt(nBytes) ? parseInt(nBytes) : 1)); |
173 | let k = new Uint8Array(nBytes); | |
174 | - | function KeyStream (key, iv) { |
174 | + | for (let o=0; o<nBytes; ++o) { |
175 | - | var csprngs, state, keyLength, keyIndex, stateIndex, workingByte = new Uint8Array(1), t, j; |
175 | + | k[o] = this.#next(); |
176 | } | |
177 | - | if (typeof key == 'undefined') |
177 | + | return k; |
178 | - | throw new Error('RXCipher@KeyStream: key required'); |
178 | + | } |
179 | - | if (typeof key == 'object' && key instanceof Array) |
179 | + | |
180 | - | key = (typeof key[0] == 'number') ? new Uint8Array(key) : key.join(''); |
180 | + | static RXEncrypt (t, k, workingByte) { |
181 | - | if (typeof key == 'string') |
181 | + | workingByte[0] = (t+k); |
182 | - | key = toUTF8Array(key); |
182 | + | return workingByte[0]^k; |
183 | - | if (typeof key !== 'object' && !(key instanceof Uint8Array)) |
183 | + | } |
184 | - | throw new Error('RXCipher@KeyStream: expected key of type String, Array, or Uint8Array'); |
184 | + | |
185 | static RXDecrypt (c, k, workingByte) { | |
186 | - | if (typeof iv == 'undefined') |
186 | + | workingByte[0] = (c^k) - k; |
187 | - | iv = '0123456'; |
187 | + | return workingByte[0]; |
188 | - | if (typeof iv == 'object' && iv instanceof Array) |
188 | + | } |
189 | - | iv = (typeof iv[0] == 'number') ? new Uint8Array(iv) : iv.join(''); |
189 | + | |
190 | - | if (typeof iv == 'string') |
190 | + | // convert from JavaScript's internal UTF-16 strings to UTF-8 |
191 | - | iv = toUTF8Array(iv); |
191 | + | // ripped from http://stackoverflow.com/a/18729931 |
192 | - | if (!(iv instanceof Uint8Array) || iv.length == 0) |
192 | + | // credit: Joni Salonen |
193 | - | throw new Error('RXCipher@KeyStream: expected iv of type String, Array or Uint8Array, or undefined'); |
193 | + | static toUTF8Array(str) { |
194 | let utf8 = [], charcode; | |
195 | - | // initialize some internal state |
195 | + | for (let i=0; i < str.length; i++) { |
196 | - | csprngs = []; keyLength = key.length; keyIndex = 0; state = new Uint8Array(64); stateIndex = 0; |
196 | + | charcode = str.charCodeAt(i); |
197 | - | for (var i = 0; i < keyLength; i++) { |
197 | + | if (charcode < 0x80) utf8.push(charcode); |
198 | - | csprngs[i] = new XORShiftPlus(key[i]); |
198 | + | else if (charcode < 0x800) { |
199 | - | } |
199 | + | utf8.push(0xc0 | (charcode >> 6), |
200 | - | for (var i = 0, j = 0, c = 0; c < keyLength * 4; ++i, ++j, ++c) { |
200 | + | 0x80 | (charcode & 0x3f)); |
201 | - | j = (j == keyLength) ? 0 : j; |
201 | + | } else if (charcode < 0xd800 || charcode >= 0xe000) { |
202 | - | i = (i == iv.length) ? 0 : i; |
202 | + | utf8.push(0xe0 | (charcode >> 12), |
203 | - | key[j] = key[j] + iv[i]; |
203 | + | 0x80 | ((charcode>>6) & 0x3f), |
204 | - | } |
204 | + | 0x80 | (charcode & 0x3f)); |
205 | - | for (var i = 0, j = 0; i < 64; ++i, ++j) { |
205 | + | } else { // surrogate pair |
206 | - | j = (j == keyLength) ? 0 : j; |
206 | + | i++; |
207 | - | state[i] = csprngs[j].next()[0]; |
207 | + | // UTF-16 encodes 0x10000-0x10FFFF by |
208 | - | } |
208 | + | // subtracting 0x10000 and splitting the |
209 | - | for (var i = 63, j = 0, t, m; i >= 0; --i, ++j) { |
209 | + | // 20 bits of 0x0-0xFFFFF into two halves |
210 | - | j = (j == keyLength) ? 0 : j; |
210 | + | charcode = 0x10000 + (((charcode & 0x3ff)<<10) |
211 | - | t = (csprngs[j].next()[0]+key[j])%64; |
211 | + | | (str.charCodeAt(i) & 0x3ff)); |
212 | - | m = state[t]; |
212 | + | utf8.push(0xf0 | (charcode >>18), |
213 | - | state[t] = RXEncrypt(state[i], key[j], workingByte); |
213 | + | 0x80 | ((charcode>>12) & 0x3f), |
214 | - | state[i] = RXDecrypt(m, t, workingByte); |
214 | + | 0x80 | ((charcode>>6) & 0x3f), |
215 | - | } |
215 | + | 0x80 | (charcode & 0x3f)); |
216 | } | |
217 | - | function next () { |
217 | + | } |
218 | - | keyIndex = (++keyIndex == keyLength) ? 0 : keyIndex; |
218 | + | return new Uint8Array(utf8); |
219 | - | stateIndex = (++stateIndex == 64) ? 0 : stateIndex; |
219 | + | } |
220 | - | state[stateIndex] = RXEncrypt(state[stateIndex], key[keyIndex], workingByte); |
220 | + | } |
221 | - | state[63 - stateIndex] = RXEncrypt(state[63 - stateIndex], state[stateIndex], workingByte); |
221 | + | |
222 | - | return state[stateIndex]; |
222 | + | |
223 | - | } |
223 | + | * Title: RXCipher |
224 | * Description: Simple and relatively quick stream cipher algorithm - JavaScript implementation | |
225 | - | this.get = function (nBytes) { |
225 | + | * Author: Jonathan Voss |
226 | - | nBytes = (nBytes|0 ? nBytes|0 : (parseInt(nBytes) ? parseInt(nBytes) : 1)); |
226 | + | * Date: 8/7/2012 (v1.0); 12/16/2014 (v2.0); 11/19/2015 (v3.0); 09/07/2020 (4.0) |
227 | - | var k = new Uint8Array(nBytes); |
227 | + | * Version: 4.0 |
228 | - | for (var o=0; o<nBytes; ++o) { |
228 | + | * Namespace: pastebin.com/u/k98kurz; github.com/k98kurz |
229 | - | k[o] = next(); |
229 | + | * License: ISC |
230 | - | } |
230 | + | |
231 | - | return k; |
231 | + | |
232 | - | }; |
232 | + | |
233 | - | } |
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 | - | // convert from JavaScript's internal UTF-16 strings to UTF-8 |
235 | + | |
236 | - | // ripped from http://stackoverflow.com/a/18729931 |
236 | + | |
237 | - | // credit: Joni Salonen |
237 | + | |
238 | - | function toUTF8Array(str) { |
238 | + | * Instance Methods: |
239 | - | var utf8 = [], charcode; |
239 | + | * encrypt ( plaintext ) |
240 | - | for (var i=0; i < str.length; i++) { |
240 | + | * encrypts plaintext with key stream derived from skey |
241 | - | charcode = str.charCodeAt(i); |
241 | + | * decrypt ( ciphertext) |
242 | - | if (charcode < 0x80) utf8.push(charcode); |
242 | + | * decrypts ciphertext with key stream derived from skey |
243 | - | else if (charcode < 0x800) { |
243 | + | * getKeyStream () |
244 | - | utf8.push(0xc0 | (charcode >> 6), |
244 | + | * returns a KeyStream for PRNG generation |
245 | - | 0x80 | (charcode & 0x3f)); |
245 | + | * reset () |
246 | - | } else if (charcode < 0xd800 || charcode >= 0xe000) { |
246 | + | * resets the internal KeyStream |
247 | - | utf8.push(0xe0 | (charcode >> 12), |
247 | + | * Static Methods: |
248 | - | 0x80 | ((charcode>>6) & 0x3f), |
248 | + | * rxencrypt ( key, plaintext, optional iv ) |
249 | - | 0x80 | (charcode & 0x3f)); |
249 | + | * creates ephemeral RXCipher instance, calls encrypt, and returns {iv}.{ciphertext} |
250 | - | } else { // surrogate pair |
250 | + | * rxdecrypt ( key, ciphertextString ) |
251 | - | i++; |
251 | + | * decrypts rxencrypt output |
252 | - | // UTF-16 encodes 0x10000-0x10FFFF by |
252 | + | |
253 | - | // subtracting 0x10000 and splitting the |
253 | + | |
254 | - | // 20 bits of 0x0-0xFFFFF into two halves |
254 | + | * 2.0 (12/16/2014): added pseudo-random key stream; dropped automatic hex conversion of ciphertext |
255 | - | charcode = 0x10000 + (((charcode & 0x3ff)<<10) |
255 | + | * 2.1 (12/29/2014): changed default seed value for XORShiftPlus PRNG |
256 | - | | (str.charCodeAt(i) & 0x3ff)); |
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 | - | utf8.push(0xf0 | (charcode >>18), |
257 | + | * 2.3 (11/18/2015): added explicit IV support; made base RXEncrypt & RXDecrypt use a Uint8 byte |
258 | - | 0x80 | ((charcode>>12) & 0x3f), |
258 | + | * 3.0 (11/19/2015): changed KeyStream to use an internally mixed 64 byte state; improved IV handling; improved performance |
259 | - | 0x80 | ((charcode>>6) & 0x3f), |
259 | + | * 3.1 (02/01/2016): updated KeyStream to use improved/fixed XORShiftPlus class; encrypt/decrypt use hex; encryptRaw/decryptRaw use Uint8Array |
260 | - | 0x80 | (charcode & 0x3f)); |
260 | + | * 3.2 (03/08/2016): bug fix: getKeyStream now returns actual KeyStream; reset method returns this |
261 | - | } |
261 | + | * 4.0 (09/07/2020): converted to es6 class; added static helper methods |
262 | - | } |
262 | + | |
263 | - | return new Uint8Array(utf8); |
263 | + | class RXCipher { |
264 | - | } |
264 | + | #key; |
265 | #iv; | |
266 | - | // convert from UTF-8 to JavaScript's internal UTF-16 strings |
266 | + | #stream; |
267 | - | // ripped from https://github.com/coolaj86/TextEncoderLite |
267 | + | |
268 | - | // credit: AJ ONeal (coolaj86) && Feross Aboukhadijeh (feross) |
268 | + | constructor (key, iv) { |
269 | - | function fromUTF8Array (arr) { |
269 | + | this.#key = (typeof key == 'object' && key instanceof Uint8Array) ? RXCipher.fromUTF8Array(key) : key; |
270 | - | var utf16 = '', tmp = ''; |
270 | + | this.#iv = (typeof iv == 'object' && iv instanceof Uint8Array) ? RXCipher.fromUTF8Array(iv) : iv; |
271 | - | for (var i = 0, ij = arr.length; i < ij; i++) { |
271 | + | this.#stream = new KeyStream(this.#key, this.#iv); |
272 | - | if (arr[i] <= 0x7F) { |
272 | + | } |
273 | - | utf16 += decodeUtf8Char(tmp) + String.fromCharCode(arr[i]); |
273 | + | |
274 | - | tmp = ''; |
274 | + | #encrypt (plaintext) { |
275 | - | } else { |
275 | + | let ciphertext = new Uint8Array(plaintext.length); |
276 | - | tmp += '%' + arr[i].toString(16); |
276 | + | let key = this.#stream.getBytes(plaintext.length); |
277 | - | } |
277 | + | for (let i = 0, ij = plaintext.length; i < ij; ++i) { |
278 | - | }; |
278 | + | ciphertext[i] = plaintext[i] ^ key[i]; |
279 | } | |
280 | - | return utf16 + decodeUtf8Char(tmp); |
280 | + | return ciphertext; |
281 | - | } |
281 | + | } |
282 | ||
283 | - | function decodeUtf8Char (str) { |
283 | + | #decrypt (ciphertext) { |
284 | - | try { |
284 | + | let plaintext = new Uint8Array(ciphertext.length); |
285 | - | return decodeURIComponent(str); |
285 | + | let key = this.#stream.getBytes(ciphertext.length); |
286 | - | } catch (err) { |
286 | + | for (let i = 0, ij = ciphertext.length; i < ij; ++i) { |
287 | - | return String.fromCharCode(0xFFFD); // UTF 8 invalid char |
287 | + | plaintext[i] = ciphertext[i] ^ key[i]; |
288 | - | } |
288 | + | } |
289 | - | } |
289 | + | return plaintext; |
290 | } | |
291 | - | function toHex (raw) { |
291 | + | |
292 | - | var t = []; |
292 | + | encrypt (plaintext) { |
293 | - | for (var i=0, il=raw.length; i<il; ++i) { |
293 | + | let ciphertext = this.#encrypt(RXCipher.toUTF8Array(plaintext)); |
294 | - | t[i] = raw[i].toString(16); t[i] = t[i].length%2 ? '0'+t[i] : t[i]; |
294 | + | return RXCipher.toHex(ciphertext); |
295 | - | } |
295 | + | } |
296 | - | return t.join(''); |
296 | + | |
297 | - | } |
297 | + | decrypt (ciphertext) { |
298 | let plaintext = this.#decrypt(RXCipher.fromHex(ciphertext)); | |
299 | - | function fromHex (hex) { |
299 | + | return RXCipher.fromUTF8Array(plaintext); |
300 | - | var t = new Uint8Array(hex.length/2); |
300 | + | } |
301 | - | for (var i=0, j=0, il=hex.length; i<il; ++i, ++j) { |
301 | + | |
302 | - | t[j] = parseInt(hex[i] + hex[++i], 16); |
302 | + | encryptRaw (plaintext) { |
303 | - | } |
303 | + | return this.#encrypt(RXCipher.toUTF8Array(plaintext)); |
304 | - | return t; |
304 | + | } |
305 | - | } |
305 | + | |
306 | - | }; |
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 |