@secrecy/lib 1.86.3 → 1.86.4-fix-crypto.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -700,7 +700,10 @@ export class SecrecyCloudClient {
700
700
  : this.#client.currentIdentity.identityPubKey, this.#client.currentIdentityPrivateKey)
701
701
  : null;
702
702
  const src = key
703
- ? await decrypt(key, encryptedContent, progressDecrypt, signal)
703
+ ? // Legacy streams may lack the final tag: the md5 check below covers them.
704
+ await decrypt(key, encryptedContent, progressDecrypt, signal, {
705
+ allowMissingFinalTag: true,
706
+ })
704
707
  : encryptedContent;
705
708
  const md5Content = await md5(src);
706
709
  if (md5Content !== dataContent.md5) {
@@ -1,8 +1,7 @@
1
1
  import z from 'zod';
2
2
  import { buildBytesFromDataLink } from '../crypto/domain';
3
3
  import { sodium } from '../sodium';
4
- import { derivePassword } from '../crypto/helpers';
5
- import { decryptSecretBox } from '../crypto';
4
+ import { decryptKeyWithPassword } from '../crypto/helpers';
6
5
  import { decrypt } from '../worker/sodium';
7
6
  import { md5 } from '../worker/md5';
8
7
  import { decompress } from '../minify';
@@ -69,10 +68,10 @@ export async function downloadDataFromLink(opts) {
69
68
  throw new Error('Unable to read encrypted data without password!');
70
69
  }
71
70
  try {
72
- const salt = sodium.crypto_generichash(sodium.crypto_pwhash_SALTBYTES, opts.crypto.password + dataLink.md5);
73
- const derivedPassword = derivePassword(opts.crypto.password, salt);
74
- const decryptedSharingDataKey = decryptSecretBox(sodium.from_hex(opts.crypto.key), derivedPassword);
75
- const decryptedBytes = await decrypt(decryptedSharingDataKey, bytes, opts.decryptProgress, opts.signal);
71
+ const decryptedSharingDataKey = decryptKeyWithPassword(sodium.from_hex(opts.crypto.key), opts.crypto.password, dataLink.md5);
72
+ const decryptedBytes = await decrypt(decryptedSharingDataKey, bytes, opts.decryptProgress, opts.signal,
73
+ // Legacy streams may lack the final tag: the md5 check below covers them.
74
+ { allowMissingFinalTag: true });
76
75
  const md5Content = await md5(decryptedBytes);
77
76
  if (md5Content !== dataLink.md5) {
78
77
  throw new Error(`Content does not match`);
@@ -21,7 +21,9 @@ export function parseInfos() {
21
21
  const hash = window.location.hash.slice(1);
22
22
  try {
23
23
  const res = JSON.parse(atob(hash));
24
- history.replaceState(null, '', window.location.pathname + window.location.search);
24
+ // Replace (not push) the entry: the fragment holds the private keys and
25
+ // must not stay in the browser history.
26
+ window.history.replaceState(window.history.state, '', window.location.pathname + window.location.search);
25
27
  const data = secrecyUserApp.safeParse(res);
26
28
  return data.success ? data.data : null;
27
29
  }
@@ -8,8 +8,8 @@ import { chunks, enumerate } from '../utils/array.js';
8
8
  import { getTrpcGuestClient, } from '../client.js';
9
9
  import axios from 'axios';
10
10
  import { encryptDataAndKey } from '../crypto/domain.js';
11
- import { derivePassword, generatePassword } from '../crypto/helpers.js';
12
- import { decryptCryptoBox, encryptSecretBox } from '../crypto/index.js';
11
+ import { encryptKeyWithPassword, generatePassword } from '../crypto/helpers.js';
12
+ import { decryptCryptoBox } from '../crypto/index.js';
13
13
  export async function uploadData({ storageType, data, password, forcePassword = false, encrypted = true, encryptProgress, uploadProgress, signal, meta, currentIdentity, keyPairs, apiClient, }) {
14
14
  if (!encrypted && (password || forcePassword)) {
15
15
  throw new Error('Cannot share unencrypted data with a password!');
@@ -63,8 +63,6 @@ export async function uploadData({ storageType, data, password, forcePassword =
63
63
  const createSharing = ({ data, }) => {
64
64
  if (dataKey && (password || forcePassword)) {
65
65
  password ??= generatePassword();
66
- const salt = sodium.crypto_generichash(sodium.crypto_pwhash_SALTBYTES, password + md5Data);
67
- const derivedPassword = derivePassword(password, salt);
68
66
  let key;
69
67
  if (data.type === 'guest' ||
70
68
  (data.type === 'authed' &&
@@ -88,7 +86,7 @@ export async function uploadData({ storageType, data, password, forcePassword =
88
86
  // NOTE: Process to create a sharing for a auth client (todo: endpoint)
89
87
  return {
90
88
  password,
91
- encryptedDataKey: sodium.to_hex(encryptSecretBox(key, derivedPassword)),
89
+ encryptedDataKey: sodium.to_hex(encryptKeyWithPassword(key, password)),
92
90
  };
93
91
  }
94
92
  return null;
@@ -1,6 +1,5 @@
1
1
  import { setup, sodium } from '../sodium.js';
2
2
  import SparkMD5 from 'spark-md5';
3
- import { chunks } from '../utils/array.js';
4
3
  function assert(c, message) {
5
4
  if (!c) {
6
5
  throw new Error(message);
@@ -9,7 +8,19 @@ function assert(c, message) {
9
8
  export function secretStreamKeygen() {
10
9
  return sodium.crypto_secretstream_xchacha20poly1305_keygen();
11
10
  }
11
+ function assertKey(key) {
12
+ assert(key.byteLength === sodium.crypto_secretstream_xchacha20poly1305_KEYBYTES, `key must be KEYBYTES (${sodium.crypto_secretstream_xchacha20poly1305_KEYBYTES}) long`);
13
+ }
14
+ // The wrappers return the secretstream state as a raw address in the wasm
15
+ // heap and never release it: wipe it (it holds the stream key) and free it.
16
+ function wipeState(state) {
17
+ const lib = sodium.libsodium;
18
+ const size = lib._crypto_secretstream_xchacha20poly1305_statebytes();
19
+ lib.HEAPU8.fill(0, state, state + size);
20
+ lib._free(state);
21
+ }
12
22
  function encrypt(key) {
23
+ assertKey(key);
13
24
  let destroyed = false;
14
25
  const { state, header } = sodium.crypto_secretstream_xchacha20poly1305_init_push(key);
15
26
  const encrypt = (tag, plaintext) => {
@@ -19,6 +30,7 @@ function encrypt(key) {
19
30
  function destroy() {
20
31
  assert(!destroyed, 'state already destroyed');
21
32
  destroyed = true;
33
+ wipeState(state);
22
34
  }
23
35
  return {
24
36
  encrypt,
@@ -27,18 +39,19 @@ function encrypt(key) {
27
39
  };
28
40
  }
29
41
  function decrypt(header, key) {
30
- assert(header.byteLength >=
31
- sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES, `header must be at least HEADERBYTES (${sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES}) long`);
32
- assert(key.byteLength >= sodium.crypto_secretstream_xchacha20poly1305_KEYBYTES, `key must be at least KEYBYTES (${sodium.crypto_secretstream_xchacha20poly1305_KEYBYTES}) long`);
42
+ assert(header.byteLength ===
43
+ sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES, `header must be HEADERBYTES (${sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES}) long`);
44
+ assertKey(key);
33
45
  let destroyed = false;
34
46
  const state = sodium.crypto_secretstream_xchacha20poly1305_init_pull(header, key);
35
47
  const decrypt = (ciphertext) => {
36
48
  assert(!destroyed, 'state already destroyed');
37
- return sodium.crypto_secretstream_xchacha20poly1305_pull(state, ciphertext);
49
+ return sodium.crypto_secretstream_xchacha20poly1305_pull(state, ciphertext, null);
38
50
  };
39
51
  function destroy() {
40
52
  assert(!destroyed, 'state already destroyed');
41
53
  destroyed = true;
54
+ wipeState(state);
42
55
  }
43
56
  return {
44
57
  decrypt,
@@ -46,104 +59,149 @@ function decrypt(header, key) {
46
59
  };
47
60
  }
48
61
  export const CHUNK_SIZE = 8192;
62
+ /**
63
+ * Encrypt `data` as a secretstream: the header followed by one authenticated
64
+ * chunk per CHUNK_SIZE bytes of plaintext. The last chunk (an empty one for
65
+ * empty data) is always tagged `TAG_FINAL` so that truncation is detected.
66
+ */
49
67
  export async function encryptSecretStream(key, data, progress, signal) {
50
68
  await setup();
51
69
  const { encrypt: crypt, destroy, header } = encrypt(key);
52
- const encryptedChunk = CHUNK_SIZE + sodium.crypto_secretstream_xchacha20poly1305_ABYTES;
53
- const max = Math.ceil(data.byteLength / CHUNK_SIZE) * encryptedChunk + header.byteLength;
54
- void progress?.({
55
- percent: 0,
56
- total: max,
57
- current: 0,
58
- });
59
- const final = new Uint8Array(max);
60
- const sparkEncrypted = new SparkMD5.ArrayBuffer();
61
- const spark = new SparkMD5.ArrayBuffer();
62
- final.set(header);
63
- sparkEncrypted.append(header.buffer);
64
- let total = header.byteLength;
65
- void progress?.({
66
- percent: total / max,
67
- total: max,
68
- current: total,
69
- });
70
- let lastPercent = total / max;
71
- for (const chunk of chunks(data, CHUNK_SIZE)) {
72
- if (signal?.aborted === true) {
73
- throw new Error(`Encrypt aborted`);
74
- }
75
- spark.append(chunk.buffer);
76
- const tag = chunk.length < CHUNK_SIZE
77
- ? sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL
78
- : sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE;
79
- const encrypted = crypt(tag, chunk);
80
- sparkEncrypted.append(encrypted.buffer);
81
- final.set(encrypted, total);
82
- total += encrypted.byteLength;
83
- const percent = total / max;
84
- if (percent > lastPercent + 0.01) {
85
- void progress?.({
86
- percent,
87
- total: max,
88
- current: total,
89
- });
90
- lastPercent = percent;
70
+ try {
71
+ const chunkCount = Math.max(1, Math.ceil(data.byteLength / CHUNK_SIZE));
72
+ const max = header.byteLength +
73
+ data.byteLength +
74
+ chunkCount * sodium.crypto_secretstream_xchacha20poly1305_ABYTES;
75
+ void progress?.({
76
+ percent: 0,
77
+ total: max,
78
+ current: 0,
79
+ });
80
+ const final = new Uint8Array(max);
81
+ const sparkEncrypted = new SparkMD5.ArrayBuffer();
82
+ const spark = new SparkMD5.ArrayBuffer();
83
+ final.set(header);
84
+ sparkEncrypted.append(header.buffer);
85
+ let total = header.byteLength;
86
+ void progress?.({
87
+ percent: total / max,
88
+ total: max,
89
+ current: total,
90
+ });
91
+ let lastPercent = total / max;
92
+ for (let i = 0; i < chunkCount; i++) {
93
+ if (signal?.aborted === true) {
94
+ throw new Error(`Encrypt aborted`);
95
+ }
96
+ const chunk = data.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE);
97
+ spark.append(chunk.buffer);
98
+ const tag = i === chunkCount - 1
99
+ ? sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL
100
+ : sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE;
101
+ const encrypted = crypt(tag, chunk);
102
+ sparkEncrypted.append(encrypted.buffer);
103
+ final.set(encrypted, total);
104
+ total += encrypted.byteLength;
105
+ const percent = total / max;
106
+ if (percent > lastPercent + 0.01) {
107
+ void progress?.({
108
+ percent,
109
+ total: max,
110
+ current: total,
111
+ });
112
+ lastPercent = percent;
113
+ }
91
114
  }
115
+ void progress?.({
116
+ percent: 1,
117
+ total,
118
+ current: total,
119
+ });
120
+ return {
121
+ data: final,
122
+ md5Encrypted: sparkEncrypted.end(),
123
+ md5: spark.end(),
124
+ };
125
+ }
126
+ finally {
127
+ destroy();
92
128
  }
93
- destroy();
94
- void progress?.({
95
- percent: 1,
96
- total,
97
- current: total,
98
- });
99
- return {
100
- data: final.slice(0, total),
101
- md5Encrypted: sparkEncrypted.end(),
102
- md5: spark.end(),
103
- };
104
129
  }
105
- export async function decryptSecretStream(key, data, progress, signal) {
130
+ /**
131
+ * Decrypt a secretstream produced by `encryptSecretStream`. Throws if any
132
+ * chunk fails authentication, if data follows the `TAG_FINAL` chunk, or if the
133
+ * stream ends without it (truncation) unless `allowMissingFinalTag` is set.
134
+ */
135
+ export async function decryptSecretStream(key, data, progress, signal, options) {
106
136
  await setup();
107
- const header = data.slice(0, sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES);
108
- data = data.slice(sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES);
109
- const { decrypt: decryptFn, destroy } = decrypt(header, key);
137
+ const headerBytes = sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES;
110
138
  const chunkSize = CHUNK_SIZE + sodium.crypto_secretstream_xchacha20poly1305_ABYTES;
111
- const max = Math.ceil(data.byteLength / chunkSize) * CHUNK_SIZE;
112
- void progress?.({
113
- percent: 0,
114
- total: max,
115
- current: 0,
116
- });
117
- const final = new Uint8Array(max);
118
- let total = 0;
119
- let lastPercent = total / max;
120
- for (const chunk of chunks(data, chunkSize)) {
121
- if (signal?.aborted === true) {
122
- throw new Error(`Decrypt aborted`);
123
- }
124
- const messageTag = decryptFn(chunk);
125
- if (typeof messageTag === 'boolean') {
126
- throw new Error('Unable to decrypt the data!');
139
+ // A valid stream always holds at least one (possibly empty) chunk.
140
+ if (data.byteLength <
141
+ headerBytes + sodium.crypto_secretstream_xchacha20poly1305_ABYTES) {
142
+ throw new Error('Unable to decrypt the data: the data is too short!');
143
+ }
144
+ const header = data.slice(0, headerBytes);
145
+ const body = data.subarray(headerBytes);
146
+ const chunkCount = Math.ceil(body.byteLength / chunkSize);
147
+ const max = body.byteLength -
148
+ chunkCount * sodium.crypto_secretstream_xchacha20poly1305_ABYTES;
149
+ const { decrypt: decryptFn, destroy } = decrypt(header, key);
150
+ try {
151
+ void progress?.({
152
+ percent: 0,
153
+ total: max,
154
+ current: 0,
155
+ });
156
+ const final = new Uint8Array(max);
157
+ let total = 0;
158
+ let finalTagSeen = false;
159
+ let lastPercent = total / max;
160
+ for (let i = 0; i < chunkCount; i++) {
161
+ if (signal?.aborted === true) {
162
+ throw new Error(`Decrypt aborted`);
163
+ }
164
+ const chunk = body.subarray(i * chunkSize, (i + 1) * chunkSize);
165
+ if (finalTagSeen) {
166
+ throw new Error('Unable to decrypt the data: unexpected data after the final chunk!');
167
+ }
168
+ const messageTag = decryptFn(chunk);
169
+ if (typeof messageTag === 'boolean') {
170
+ throw new Error('Unable to decrypt the data!');
171
+ }
172
+ if (messageTag.tag ===
173
+ sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL) {
174
+ finalTagSeen = true;
175
+ }
176
+ else if (messageTag.tag !==
177
+ sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE) {
178
+ throw new Error('Unable to decrypt the data: unexpected chunk tag!');
179
+ }
180
+ final.set(messageTag.message, total);
181
+ total += messageTag.message.byteLength;
182
+ const percent = total / max;
183
+ if (percent > lastPercent + 0.01) {
184
+ void progress?.({
185
+ percent,
186
+ total: max,
187
+ current: total,
188
+ });
189
+ lastPercent = percent;
190
+ }
127
191
  }
128
- final.set(messageTag.message, total);
129
- total += messageTag.message.byteLength;
130
- const percent = total / max;
131
- if (percent > lastPercent + 0.01) {
132
- void progress?.({
133
- percent,
134
- total: max,
135
- current: total,
136
- });
137
- lastPercent = percent;
192
+ if (!finalTagSeen && options?.allowMissingFinalTag !== true) {
193
+ throw new Error('Unable to decrypt the data: the data is truncated!');
138
194
  }
195
+ void progress?.({
196
+ percent: 1,
197
+ total,
198
+ current: total,
199
+ });
200
+ return final;
201
+ }
202
+ finally {
203
+ destroy();
139
204
  }
140
- destroy();
141
- void progress?.({
142
- percent: 1,
143
- total,
144
- current: total,
145
- });
146
- return final.slice(0, total);
147
205
  }
148
206
  // async function mainSecretStream(random: Uint8Array): Promise<vo id> {
149
207
  // const key = secretstreamKeygen();
@@ -1,5 +1,6 @@
1
- import { encryptCryptoBox } from '.';
1
+ import { decryptSecretBox, encryptCryptoBox, encryptSecretBox } from '.';
2
2
  import { sodium } from '../sodium';
3
+ import { concatenate } from '../utils/array.js';
3
4
  import { encryptSecretStream, secretStreamKeygen } from './data';
4
5
  export function generatePassword() {
5
6
  return sodium.randombytes_buf(16, 'hex');
@@ -7,6 +8,35 @@ export function generatePassword() {
7
8
  export function derivePassword(password, salt) {
8
9
  return sodium.crypto_pwhash(sodium.crypto_secretbox_KEYBYTES, password, salt, sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE, sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE, sodium.crypto_pwhash_ALG_ARGON2ID13, 'hex');
9
10
  }
11
+ const PASSWORD_KEY_VERSION = 1;
12
+ /**
13
+ * Wrap a data key under a password, with a random salt:
14
+ * `version (1 byte) || salt || nonce || secretbox(key)`.
15
+ */
16
+ export function encryptKeyWithPassword(key, password) {
17
+ const salt = sodium.randombytes_buf(sodium.crypto_pwhash_SALTBYTES);
18
+ return concatenate(new Uint8Array([PASSWORD_KEY_VERSION]), salt, encryptSecretBox(key, derivePassword(password, salt)));
19
+ }
20
+ /**
21
+ * Unwrap a data key produced by `encryptKeyWithPassword`. Also reads the
22
+ * legacy format (`nonce || secretbox(key)`) whose salt was derived from the
23
+ * password and the plaintext md5, hence `legacyMd5`.
24
+ */
25
+ export function decryptKeyWithPassword(wrapped, password, legacyMd5) {
26
+ const boxBytes = sodium.crypto_secretbox_NONCEBYTES +
27
+ sodium.crypto_secretbox_MACBYTES +
28
+ sodium.crypto_secretstream_xchacha20poly1305_KEYBYTES;
29
+ if (wrapped.byteLength === boxBytes) {
30
+ const salt = sodium.crypto_generichash(sodium.crypto_pwhash_SALTBYTES, password + legacyMd5, null);
31
+ return decryptSecretBox(wrapped, derivePassword(password, salt));
32
+ }
33
+ if (wrapped.byteLength === 1 + sodium.crypto_pwhash_SALTBYTES + boxBytes &&
34
+ wrapped[0] === PASSWORD_KEY_VERSION) {
35
+ const salt = wrapped.slice(1, 1 + sodium.crypto_pwhash_SALTBYTES);
36
+ return decryptSecretBox(wrapped.slice(1 + sodium.crypto_pwhash_SALTBYTES), derivePassword(password, salt));
37
+ }
38
+ throw new Error('Unsupported password protected key format');
39
+ }
10
40
  export async function encryptName(name, nameKey) {
11
41
  const { data } = await encryptSecretStream(sodium.from_hex(nameKey), sodium.from_string(name));
12
42
  return sodium.to_hex(data);
@@ -98,26 +98,30 @@ function popupExecute(execute, url, name, options, callback) {
98
98
  //
99
99
  };
100
100
  const optionsString = optionsToString(popupOptions);
101
- const win = execute(url, popupName, optionsString);
101
+ // Only the page we opened may answer: the popup name is predictable, so
102
+ // without these checks any window could inject a response (e.g. an
103
+ // attacker's session and keys on login).
102
104
  const expectedOrigin = new URL(url, window.location.href).origin;
105
+ const win = execute(url, popupName, optionsString);
103
106
  let isMessageSent = false;
104
107
  let interval;
105
108
  function popupCallbackOnce(err, data) {
106
109
  if (!isMessageSent) {
107
110
  isMessageSent = true;
111
+ window.removeEventListener('message', onMessage);
112
+ clearInterval(interval);
108
113
  popupCallback(err, data);
109
114
  }
110
115
  }
111
116
  function onMessage(message) {
112
- if (message === undefined || message.origin !== expectedOrigin) {
113
- console.error('Invalid message origin', message?.origin, expectedOrigin);
117
+ if (win === null ||
118
+ message.source !== win ||
119
+ message.origin !== expectedOrigin) {
114
120
  return;
115
121
  }
116
122
  const data = message.data;
117
- if (data !== undefined && data.id === popupName) {
123
+ if (data !== undefined && data !== null && data.id === popupName) {
118
124
  popupCallbackOnce(undefined, data.data);
119
- window.removeEventListener('message', onMessage);
120
- clearInterval(interval);
121
125
  }
122
126
  }
123
127
  window.addEventListener('message', onMessage, false);
@@ -181,8 +185,14 @@ export function popupWithPost(url, postData, name, options, callback) {
181
185
  }
182
186
  /**
183
187
  * Return html that when executed, will trigger the popup to callback with a response
188
+ *
189
+ * @param {Object}
190
+ * @param {String} targetOrigin origin of the opener allowed to receive `data`
191
+ * @return {String}
184
192
  */
185
193
  export function popupResponse(id, data, targetOrigin) {
186
- const jsonData = JSON.stringify({ id, data });
187
- return `<script>window.opener.postMessage(${jsonData}, ${JSON.stringify(targetOrigin)});setTimeout(function() { window.close(); }, 50);</script>`;
194
+ // Escape "<" so the payload cannot close the script tag.
195
+ const jsonData = JSON.stringify({ id, data }).replaceAll('<', '\\u003c');
196
+ const origin = JSON.stringify(targetOrigin).replaceAll('<', '\\u003c');
197
+ return `<script>window.opener.postMessage(${jsonData}, ${origin});setTimeout(function() { window.close(); }, 50);</script>`;
188
198
  }
@@ -1 +1 @@
1
- export const SECRECY_LIB_VERSION = '1.86.3';
1
+ export const SECRECY_LIB_VERSION = '1.86.4-fix-crypto.1';
@@ -0,0 +1,2 @@
1
+ // Generated by scripts/build-workers.ts, do not edit.
2
+ export const workerMd5Script = "(()=>{var D=Object.create;var{getPrototypeOf:E,defineProperty:F,getOwnPropertyNames:G}=Object;var v=Object.prototype.hasOwnProperty;function H(o){return this[o]}var K,N,R=(o,u,s)=>{var _=o!=null&&typeof o===\"object\";if(_){var y=u?K??=new WeakMap:N??=new WeakMap,b=y.get(o);if(b)return b}s=o!=null?D(E(o)):{};let l=u||!o||!o.__esModule||!v.call(o,\"default\")?F(s,\"default\",{value:o,enumerable:!0}):s;if(o&&typeof o===\"object\"||typeof o===\"function\"){for(let g of G(o))if(!v.call(l,g))F(l,g,{get:H.bind(o,g),enumerable:!0})}if(_)y.set(o,l);return l};var Z=(o,u)=>()=>(u||o((u={exports:{}}).exports,u),u.exports);var S=Z(function(w,U){(function(o){if(typeof w===\"object\")U.exports=o();else if(typeof define===\"function\"&&define.amd)define(o);else{var u;try{u=window}catch(s){u=self}u.SparkMD5=o()}})(function(o){var u=function(i,f){return i+f&4294967295},s=[\"0\",\"1\",\"2\",\"3\",\"4\",\"5\",\"6\",\"7\",\"8\",\"9\",\"a\",\"b\",\"c\",\"d\",\"e\",\"f\"];function _(i,f,r,t,n,e){return f=u(u(f,i),u(t,e)),u(f<<n|f>>>32-n,r)}function y(i,f){var r=i[0],t=i[1],n=i[2],e=i[3];r+=(t&n|~t&e)+f[0]-680876936|0,r=(r<<7|r>>>25)+t|0,e+=(r&t|~r&n)+f[1]-389564586|0,e=(e<<12|e>>>20)+r|0,n+=(e&r|~e&t)+f[2]+606105819|0,n=(n<<17|n>>>15)+e|0,t+=(n&e|~n&r)+f[3]-1044525330|0,t=(t<<22|t>>>10)+n|0,r+=(t&n|~t&e)+f[4]-176418897|0,r=(r<<7|r>>>25)+t|0,e+=(r&t|~r&n)+f[5]+1200080426|0,e=(e<<12|e>>>20)+r|0,n+=(e&r|~e&t)+f[6]-1473231341|0,n=(n<<17|n>>>15)+e|0,t+=(n&e|~n&r)+f[7]-45705983|0,t=(t<<22|t>>>10)+n|0,r+=(t&n|~t&e)+f[8]+1770035416|0,r=(r<<7|r>>>25)+t|0,e+=(r&t|~r&n)+f[9]-1958414417|0,e=(e<<12|e>>>20)+r|0,n+=(e&r|~e&t)+f[10]-42063|0,n=(n<<17|n>>>15)+e|0,t+=(n&e|~n&r)+f[11]-1990404162|0,t=(t<<22|t>>>10)+n|0,r+=(t&n|~t&e)+f[12]+1804603682|0,r=(r<<7|r>>>25)+t|0,e+=(r&t|~r&n)+f[13]-40341101|0,e=(e<<12|e>>>20)+r|0,n+=(e&r|~e&t)+f[14]-1502002290|0,n=(n<<17|n>>>15)+e|0,t+=(n&e|~n&r)+f[15]+1236535329|0,t=(t<<22|t>>>10)+n|0,r+=(t&e|n&~e)+f[1]-165796510|0,r=(r<<5|r>>>27)+t|0,e+=(r&n|t&~n)+f[6]-1069501632|0,e=(e<<9|e>>>23)+r|0,n+=(e&t|r&~t)+f[11]+643717713|0,n=(n<<14|n>>>18)+e|0,t+=(n&r|e&~r)+f[0]-373897302|0,t=(t<<20|t>>>12)+n|0,r+=(t&e|n&~e)+f[5]-701558691|0,r=(r<<5|r>>>27)+t|0,e+=(r&n|t&~n)+f[10]+38016083|0,e=(e<<9|e>>>23)+r|0,n+=(e&t|r&~t)+f[15]-660478335|0,n=(n<<14|n>>>18)+e|0,t+=(n&r|e&~r)+f[4]-405537848|0,t=(t<<20|t>>>12)+n|0,r+=(t&e|n&~e)+f[9]+568446438|0,r=(r<<5|r>>>27)+t|0,e+=(r&n|t&~n)+f[14]-1019803690|0,e=(e<<9|e>>>23)+r|0,n+=(e&t|r&~t)+f[3]-187363961|0,n=(n<<14|n>>>18)+e|0,t+=(n&r|e&~r)+f[8]+1163531501|0,t=(t<<20|t>>>12)+n|0,r+=(t&e|n&~e)+f[13]-1444681467|0,r=(r<<5|r>>>27)+t|0,e+=(r&n|t&~n)+f[2]-51403784|0,e=(e<<9|e>>>23)+r|0,n+=(e&t|r&~t)+f[7]+1735328473|0,n=(n<<14|n>>>18)+e|0,t+=(n&r|e&~r)+f[12]-1926607734|0,t=(t<<20|t>>>12)+n|0,r+=(t^n^e)+f[5]-378558|0,r=(r<<4|r>>>28)+t|0,e+=(r^t^n)+f[8]-2022574463|0,e=(e<<11|e>>>21)+r|0,n+=(e^r^t)+f[11]+1839030562|0,n=(n<<16|n>>>16)+e|0,t+=(n^e^r)+f[14]-35309556|0,t=(t<<23|t>>>9)+n|0,r+=(t^n^e)+f[1]-1530992060|0,r=(r<<4|r>>>28)+t|0,e+=(r^t^n)+f[4]+1272893353|0,e=(e<<11|e>>>21)+r|0,n+=(e^r^t)+f[7]-155497632|0,n=(n<<16|n>>>16)+e|0,t+=(n^e^r)+f[10]-1094730640|0,t=(t<<23|t>>>9)+n|0,r+=(t^n^e)+f[13]+681279174|0,r=(r<<4|r>>>28)+t|0,e+=(r^t^n)+f[0]-358537222|0,e=(e<<11|e>>>21)+r|0,n+=(e^r^t)+f[3]-722521979|0,n=(n<<16|n>>>16)+e|0,t+=(n^e^r)+f[6]+76029189|0,t=(t<<23|t>>>9)+n|0,r+=(t^n^e)+f[9]-640364487|0,r=(r<<4|r>>>28)+t|0,e+=(r^t^n)+f[12]-421815835|0,e=(e<<11|e>>>21)+r|0,n+=(e^r^t)+f[15]+530742520|0,n=(n<<16|n>>>16)+e|0,t+=(n^e^r)+f[2]-995338651|0,t=(t<<23|t>>>9)+n|0,r+=(n^(t|~e))+f[0]-198630844|0,r=(r<<6|r>>>26)+t|0,e+=(t^(r|~n))+f[7]+1126891415|0,e=(e<<10|e>>>22)+r|0,n+=(r^(e|~t))+f[14]-1416354905|0,n=(n<<15|n>>>17)+e|0,t+=(e^(n|~r))+f[5]-57434055|0,t=(t<<21|t>>>11)+n|0,r+=(n^(t|~e))+f[12]+1700485571|0,r=(r<<6|r>>>26)+t|0,e+=(t^(r|~n))+f[3]-1894986606|0,e=(e<<10|e>>>22)+r|0,n+=(r^(e|~t))+f[10]-1051523|0,n=(n<<15|n>>>17)+e|0,t+=(e^(n|~r))+f[1]-2054922799|0,t=(t<<21|t>>>11)+n|0,r+=(n^(t|~e))+f[8]+1873313359|0,r=(r<<6|r>>>26)+t|0,e+=(t^(r|~n))+f[15]-30611744|0,e=(e<<10|e>>>22)+r|0,n+=(r^(e|~t))+f[6]-1560198380|0,n=(n<<15|n>>>17)+e|0,t+=(e^(n|~r))+f[13]+1309151649|0,t=(t<<21|t>>>11)+n|0,r+=(n^(t|~e))+f[4]-145523070|0,r=(r<<6|r>>>26)+t|0,e+=(t^(r|~n))+f[11]-1120210379|0,e=(e<<10|e>>>22)+r|0,n+=(r^(e|~t))+f[2]+718787259|0,n=(n<<15|n>>>17)+e|0,t+=(e^(n|~r))+f[9]-343485551|0,t=(t<<21|t>>>11)+n|0,i[0]=r+i[0]|0,i[1]=t+i[1]|0,i[2]=n+i[2]|0,i[3]=e+i[3]|0}function b(i){var f=[],r;for(r=0;r<64;r+=4)f[r>>2]=i.charCodeAt(r)+(i.charCodeAt(r+1)<<8)+(i.charCodeAt(r+2)<<16)+(i.charCodeAt(r+3)<<24);return f}function l(i){var f=[],r;for(r=0;r<64;r+=4)f[r>>2]=i[r]+(i[r+1]<<8)+(i[r+2]<<16)+(i[r+3]<<24);return f}function g(i){var f=i.length,r=[1732584193,-271733879,-1732584194,271733878],t,n,e,a,c,p;for(t=64;t<=f;t+=64)y(r,b(i.substring(t-64,t)));i=i.substring(t-64),n=i.length,e=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(t=0;t<n;t+=1)e[t>>2]|=i.charCodeAt(t)<<(t%4<<3);if(e[t>>2]|=128<<(t%4<<3),t>55){y(r,e);for(t=0;t<16;t+=1)e[t]=0}return a=f*8,a=a.toString(16).match(/(.*?)(.{0,8})$/),c=parseInt(a[2],16),p=parseInt(a[1],16)||0,e[14]=c,e[15]=p,y(r,e),r}function T(i){var f=i.length,r=[1732584193,-271733879,-1732584194,271733878],t,n,e,a,c,p;for(t=64;t<=f;t+=64)y(r,l(i.subarray(t-64,t)));i=t-64<f?i.subarray(t-64):new Uint8Array(0),n=i.length,e=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(t=0;t<n;t+=1)e[t>>2]|=i[t]<<(t%4<<3);if(e[t>>2]|=128<<(t%4<<3),t>55){y(r,e);for(t=0;t<16;t+=1)e[t]=0}return a=f*8,a=a.toString(16).match(/(.*?)(.{0,8})$/),c=parseInt(a[2],16),p=parseInt(a[1],16)||0,e[14]=c,e[15]=p,y(r,e),r}function L(i){var f=\"\",r;for(r=0;r<4;r+=1)f+=s[i>>r*8+4&15]+s[i>>r*8&15];return f}function d(i){var f;for(f=0;f<i.length;f+=1)i[f]=L(i[f]);return i.join(\"\")}if(d(g(\"hello\"))!==\"5d41402abc4b2a76b9719d911017c592\")u=function(i,f){var r=(i&65535)+(f&65535),t=(i>>16)+(f>>16)+(r>>16);return t<<16|r&65535};if(typeof ArrayBuffer<\"u\"&&!ArrayBuffer.prototype.slice)(function(){function i(f,r){if(f=f|0||0,f<0)return Math.max(f+r,0);return Math.min(f,r)}ArrayBuffer.prototype.slice=function(f,r){var t=this.byteLength,n=i(f,t),e=t,a,c,p,B;if(r!==o)e=i(r,t);if(n>e)return new ArrayBuffer(0);return a=e-n,c=new ArrayBuffer(a),p=new Uint8Array(c),B=new Uint8Array(this,n,a),p.set(B),c}})();function m(i){if(/[\\u0080-\\uFFFF]/.test(i))i=unescape(encodeURIComponent(i));return i}function M(i,f){var r=i.length,t=new ArrayBuffer(r),n=new Uint8Array(t),e;for(e=0;e<r;e+=1)n[e]=i.charCodeAt(e);return f?n:t}function x(i){return String.fromCharCode.apply(null,new Uint8Array(i))}function j(i,f,r){var t=new Uint8Array(i.byteLength+f.byteLength);return t.set(new Uint8Array(i)),t.set(new Uint8Array(f),i.byteLength),r?t:t.buffer}function A(i){var f=[],r=i.length,t;for(t=0;t<r-1;t+=2)f.push(parseInt(i.substr(t,2),16));return String.fromCharCode.apply(String,f)}function h(){this.reset()}return h.prototype.append=function(i){return this.appendBinary(m(i)),this},h.prototype.appendBinary=function(i){this._buff+=i,this._length+=i.length;var f=this._buff.length,r;for(r=64;r<=f;r+=64)y(this._hash,b(this._buff.substring(r-64,r)));return this._buff=this._buff.substring(r-64),this},h.prototype.end=function(i){var f=this._buff,r=f.length,t,n=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],e;for(t=0;t<r;t+=1)n[t>>2]|=f.charCodeAt(t)<<(t%4<<3);if(this._finish(n,r),e=d(this._hash),i)e=A(e);return this.reset(),e},h.prototype.reset=function(){return this._buff=\"\",this._length=0,this._hash=[1732584193,-271733879,-1732584194,271733878],this},h.prototype.getState=function(){return{buff:this._buff,length:this._length,hash:this._hash.slice()}},h.prototype.setState=function(i){return this._buff=i.buff,this._length=i.length,this._hash=i.hash,this},h.prototype.destroy=function(){delete this._hash,delete this._buff,delete this._length},h.prototype._finish=function(i,f){var r=f,t,n,e;if(i[r>>2]|=128<<(r%4<<3),r>55){y(this._hash,i);for(r=0;r<16;r+=1)i[r]=0}t=this._length*8,t=t.toString(16).match(/(.*?)(.{0,8})$/),n=parseInt(t[2],16),e=parseInt(t[1],16)||0,i[14]=n,i[15]=e,y(this._hash,i)},h.hash=function(i,f){return h.hashBinary(m(i),f)},h.hashBinary=function(i,f){var r=g(i),t=d(r);return f?A(t):t},h.ArrayBuffer=function(){this.reset()},h.ArrayBuffer.prototype.append=function(i){var f=j(this._buff.buffer,i,!0),r=f.length,t;this._length+=i.byteLength;for(t=64;t<=r;t+=64)y(this._hash,l(f.subarray(t-64,t)));return this._buff=t-64<r?new Uint8Array(f.buffer.slice(t-64)):new Uint8Array(0),this},h.ArrayBuffer.prototype.end=function(i){var f=this._buff,r=f.length,t=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],n,e;for(n=0;n<r;n+=1)t[n>>2]|=f[n]<<(n%4<<3);if(this._finish(t,r),e=d(this._hash),i)e=A(e);return this.reset(),e},h.ArrayBuffer.prototype.reset=function(){return this._buff=new Uint8Array(0),this._length=0,this._hash=[1732584193,-271733879,-1732584194,271733878],this},h.ArrayBuffer.prototype.getState=function(){var i=h.prototype.getState.call(this);return i.buff=x(i.buff),i},h.ArrayBuffer.prototype.setState=function(i){return i.buff=M(i.buff,!0),h.prototype.setState.call(this,i)},h.ArrayBuffer.prototype.destroy=h.prototype.destroy,h.ArrayBuffer.prototype._finish=h.prototype._finish,h.ArrayBuffer.hash=function(i,f){var r=T(new Uint8Array(i)),t=d(r);return f?A(t):t},h})});var I=R(S(),1);function*C(o,u){for(let s=0;s<o.length;s+=u)yield o.slice(s,s+u)}var q=8192;self.onmessage=({data:o})=>{if(o.event===\"md5\"){let u=new I.default.ArrayBuffer;for(let s of C(o.data,q))u.append(s.buffer);postMessage({event:\"md5-result\",data:u.end()})}};})();\n";