@time-file/browser-file-crypto 1.1.0 → 1.1.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.
- package/README.ko.md +37 -3
- package/README.md +37 -3
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +135 -1
- package/dist/index.mjs +411 -319
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/index.umd.js.map +1 -1
- package/package.json +1 -1
package/README.ko.md
CHANGED
|
@@ -163,6 +163,26 @@ const decrypted = await decryptFile(encrypted, {
|
|
|
163
163
|
});
|
|
164
164
|
```
|
|
165
165
|
|
|
166
|
+
### 하이브리드 암호화 (v1.1.1+)
|
|
167
|
+
|
|
168
|
+
파일 크기에 따라 자동으로 일반/스트리밍 암호화를 전환합니다:
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
import { encryptFileAuto } from '@time-file/browser-file-crypto';
|
|
172
|
+
|
|
173
|
+
// 100MB 이상 파일은 자동으로 스트리밍 사용
|
|
174
|
+
const encrypted = await encryptFileAuto(file, {
|
|
175
|
+
password: 'secret',
|
|
176
|
+
autoStreaming: true,
|
|
177
|
+
streamingThreshold: 100 * 1024 * 1024, // 100MB (기본값)
|
|
178
|
+
chunkSize: 1024 * 1024, // 스트리밍 시 1MB 청크
|
|
179
|
+
onProgress: ({ phase, progress }) => console.log(`${phase}: ${progress}%`)
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// decryptFile이 자동으로 두 포맷 모두 처리합니다!
|
|
183
|
+
const decrypted = await decryptFile(encrypted, { password: 'secret' });
|
|
184
|
+
```
|
|
185
|
+
|
|
166
186
|
### 스트리밍 암호화 (v1.1.0+)
|
|
167
187
|
|
|
168
188
|
메모리에 맞지 않는 대용량 파일을 처리할 때:
|
|
@@ -183,7 +203,10 @@ const encryptedStream = await encryptFileStream(largeFile, {
|
|
|
183
203
|
const response = new Response(encryptedStream);
|
|
184
204
|
const encryptedBlob = await response.blob();
|
|
185
205
|
|
|
186
|
-
// 복호화
|
|
206
|
+
// 복호화 - decryptFile이 스트리밍 포맷을 자동 감지합니다!
|
|
207
|
+
const decrypted = await decryptFile(encryptedBlob, { password: 'secret' });
|
|
208
|
+
|
|
209
|
+
// 또는 스트리밍 복호화를 직접 사용
|
|
187
210
|
const decryptedStream = decryptFileStream(encryptedBlob, { password: 'secret' });
|
|
188
211
|
const decryptResponse = new Response(decryptedStream);
|
|
189
212
|
const decryptedBlob = await decryptResponse.blob();
|
|
@@ -225,13 +248,21 @@ generateRandomPassword(24); // 'Kx9#mP2$vL5@nQ8!...'
|
|
|
225
248
|
### 다운로드 & 복호화
|
|
226
249
|
|
|
227
250
|
```typescript
|
|
228
|
-
import { downloadAndDecrypt } from '@time-file/browser-file-crypto';
|
|
251
|
+
import { downloadAndDecrypt, downloadAndDecryptStream } from '@time-file/browser-file-crypto';
|
|
229
252
|
|
|
253
|
+
// 일반 다운로드 & 복호화
|
|
230
254
|
await downloadAndDecrypt('https://example.com/secret.enc', {
|
|
231
255
|
password: 'secret',
|
|
232
256
|
fileName: 'document.pdf',
|
|
233
257
|
onProgress: ({ phase, progress }) => console.log(`${phase}: ${progress}%`)
|
|
234
258
|
});
|
|
259
|
+
|
|
260
|
+
// 스트리밍 다운로드 & 복호화 (v1.1.1+) - 대용량 파일용
|
|
261
|
+
await downloadAndDecryptStream('https://example.com/large-file.enc', {
|
|
262
|
+
password: 'secret',
|
|
263
|
+
fileName: 'video.mp4',
|
|
264
|
+
onProgress: ({ phase, processedBytes }) => console.log(`${phase}: ${processedBytes} 바이트`)
|
|
265
|
+
});
|
|
235
266
|
```
|
|
236
267
|
|
|
237
268
|
### 에러 처리
|
|
@@ -318,7 +349,10 @@ import type {
|
|
|
318
349
|
// 스트리밍 타입 (v1.1.0+)
|
|
319
350
|
StreamEncryptOptions,
|
|
320
351
|
StreamDecryptOptions,
|
|
321
|
-
StreamProgress
|
|
352
|
+
StreamProgress,
|
|
353
|
+
// v1.1.1+
|
|
354
|
+
AutoEncryptOptions,
|
|
355
|
+
DownloadDecryptStreamOptions
|
|
322
356
|
} from '@time-file/browser-file-crypto';
|
|
323
357
|
```
|
|
324
358
|
|
package/README.md
CHANGED
|
@@ -164,6 +164,26 @@ const decrypted = await decryptFile(encrypted, {
|
|
|
164
164
|
});
|
|
165
165
|
```
|
|
166
166
|
|
|
167
|
+
### Hybrid Encryption (v1.1.1+)
|
|
168
|
+
|
|
169
|
+
Auto-switch between standard and streaming encryption based on file size:
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
import { encryptFileAuto } from '@time-file/browser-file-crypto';
|
|
173
|
+
|
|
174
|
+
// Automatically uses streaming for files > 100MB
|
|
175
|
+
const encrypted = await encryptFileAuto(file, {
|
|
176
|
+
password: 'secret',
|
|
177
|
+
autoStreaming: true,
|
|
178
|
+
streamingThreshold: 100 * 1024 * 1024, // 100MB (default)
|
|
179
|
+
chunkSize: 1024 * 1024, // 1MB chunks for streaming
|
|
180
|
+
onProgress: ({ phase, progress }) => console.log(`${phase}: ${progress}%`)
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// decryptFile automatically handles both formats!
|
|
184
|
+
const decrypted = await decryptFile(encrypted, { password: 'secret' });
|
|
185
|
+
```
|
|
186
|
+
|
|
167
187
|
### Streaming Encryption (v1.1.0+)
|
|
168
188
|
|
|
169
189
|
For large files that don't fit in memory:
|
|
@@ -184,7 +204,10 @@ const encryptedStream = await encryptFileStream(largeFile, {
|
|
|
184
204
|
const response = new Response(encryptedStream);
|
|
185
205
|
const encryptedBlob = await response.blob();
|
|
186
206
|
|
|
187
|
-
// Decrypt
|
|
207
|
+
// Decrypt - decryptFile auto-detects streaming format!
|
|
208
|
+
const decrypted = await decryptFile(encryptedBlob, { password: 'secret' });
|
|
209
|
+
|
|
210
|
+
// Or use streaming decryption directly
|
|
188
211
|
const decryptedStream = decryptFileStream(encryptedBlob, { password: 'secret' });
|
|
189
212
|
const decryptResponse = new Response(decryptedStream);
|
|
190
213
|
const decryptedBlob = await decryptResponse.blob();
|
|
@@ -226,13 +249,21 @@ generateRandomPassword(24); // 'Kx9#mP2$vL5@nQ8!...'
|
|
|
226
249
|
### Download & Decrypt
|
|
227
250
|
|
|
228
251
|
```typescript
|
|
229
|
-
import { downloadAndDecrypt } from '@time-file/browser-file-crypto';
|
|
252
|
+
import { downloadAndDecrypt, downloadAndDecryptStream } from '@time-file/browser-file-crypto';
|
|
230
253
|
|
|
254
|
+
// Standard download & decrypt
|
|
231
255
|
await downloadAndDecrypt('https://example.com/secret.enc', {
|
|
232
256
|
password: 'secret',
|
|
233
257
|
fileName: 'document.pdf',
|
|
234
258
|
onProgress: ({ phase, progress }) => console.log(`${phase}: ${progress}%`)
|
|
235
259
|
});
|
|
260
|
+
|
|
261
|
+
// Streaming download & decrypt (v1.1.1+) - for large files
|
|
262
|
+
await downloadAndDecryptStream('https://example.com/large-file.enc', {
|
|
263
|
+
password: 'secret',
|
|
264
|
+
fileName: 'video.mp4',
|
|
265
|
+
onProgress: ({ phase, processedBytes }) => console.log(`${phase}: ${processedBytes} bytes`)
|
|
266
|
+
});
|
|
236
267
|
```
|
|
237
268
|
|
|
238
269
|
### Error Handling
|
|
@@ -319,7 +350,10 @@ import type {
|
|
|
319
350
|
// Streaming types (v1.1.0+)
|
|
320
351
|
StreamEncryptOptions,
|
|
321
352
|
StreamDecryptOptions,
|
|
322
|
-
StreamProgress
|
|
353
|
+
StreamProgress,
|
|
354
|
+
// v1.1.1+
|
|
355
|
+
AutoEncryptOptions,
|
|
356
|
+
DownloadDecryptStreamOptions
|
|
323
357
|
} from '@time-file/browser-file-crypto';
|
|
324
358
|
```
|
|
325
359
|
|
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const h=16,f=12,U=256,H=1e5,B=32,D=1,m=2,S=17,T=18,V=64*1024,N=1,F=16,C=1+h+f+F,K=1+f+F,A="AES-GCM",W="SHA-256",$={INVALID_INPUT:"Input must be a File, Blob, or ArrayBuffer.",PASSWORD_REQUIRED:"Password or keyfile is required for encryption.",KEYFILE_REQUIRED:"Keyfile is required to decrypt this file.",INVALID_PASSWORD:"Decryption failed: incorrect password.",INVALID_KEYFILE:"Decryption failed: incorrect keyfile.",INVALID_ENCRYPTED_DATA:"Invalid encrypted data: file may be corrupted.",ENCRYPTION_FAILED:"Encryption failed.",DECRYPTION_FAILED:"Decryption failed.",DOWNLOAD_FAILED:"File download failed.",UNSUPPORTED_FORMAT:"Unsupported encryption format."};class o extends Error{constructor(a,e){super(e??$[a]),this.name="CryptoError",this.code=a,Error.captureStackTrace&&Error.captureStackTrace(this,o)}}function J(n){return n instanceof o}async function L(n){if(n instanceof ArrayBuffer)return n;if(n instanceof Blob)return n.arrayBuffer();throw new o("INVALID_INPUT")}function E(n){return n.buffer.slice(n.byteOffset,n.byteOffset+n.byteLength)}function X(n){const a=new Uint8Array(n);let e="";for(let t=0;t<a.byteLength;t++)e+=String.fromCharCode(a[t]);return btoa(e)}function G(n){const a=atob(n),e=new Uint8Array(a.length);for(let t=0;t<a.length;t++)e[t]=a.charCodeAt(t);return e.buffer}async function b(n,a){const e=new TextEncoder,t=await crypto.subtle.importKey("raw",e.encode(n),"PBKDF2",!1,["deriveKey"]);return crypto.subtle.deriveKey({name:"PBKDF2",salt:E(a),iterations:H,hash:W},t,{name:A,length:U},!1,["encrypt","decrypt"])}async function k(n){try{const a=G(n);return await crypto.subtle.importKey("raw",a,{name:A,length:U},!1,["encrypt","decrypt"])}catch{throw new o("INVALID_KEYFILE")}}function O(n){return crypto.getRandomValues(new Uint8Array(n))}function x(){return O(h)}function M(){return O(f)}const Y="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*",P=16;function ee(n=P){const a=Math.max(8,Math.min(128,n)),e=O(a);let t="";for(let r=0;r<a;r++){const c=e[r]%Y.length;t+=Y[c]}return t}async function te(n,a){const{password:e,keyData:t,onProgress:r}=a;if(!e&&!t)throw new o("PASSWORD_REQUIRED");try{r==null||r({phase:"deriving_key",progress:0});const c=await L(n);return r==null||r({phase:"deriving_key",progress:10}),t?await re(c,t,r):await ne(c,e,r)}catch(c){throw c instanceof o?c:new o("ENCRYPTION_FAILED")}}async function ne(n,a,e){const t=x(),r=M();e==null||e({phase:"deriving_key",progress:20});const c=await b(a,t);e==null||e({phase:"encrypting",progress:30});const i=await crypto.subtle.encrypt({name:A,iv:E(r)},c,n);e==null||e({phase:"encrypting",progress:90});const s=new Uint8Array(1+h+f+i.byteLength);return s[0]=D,s.set(t,1),s.set(r,1+h),s.set(new Uint8Array(i),1+h+f),e==null||e({phase:"complete",progress:100}),new Blob([s],{type:"application/octet-stream"})}async function re(n,a,e){const t=M();e==null||e({phase:"deriving_key",progress:20});const r=await k(a);e==null||e({phase:"encrypting",progress:30});const c=await crypto.subtle.encrypt({name:A,iv:E(t)},r,n);e==null||e({phase:"encrypting",progress:90});const i=new Uint8Array(1+f+c.byteLength);return i[0]=m,i.set(t,1),i.set(new Uint8Array(c),1+f),e==null||e({phase:"complete",progress:100}),new Blob([i],{type:"application/octet-stream"})}async function z(n,a){const{password:e,keyData:t,onProgress:r}=a;try{r==null||r({phase:"decrypting",progress:0});const c=new Uint8Array(await L(n));if(r==null||r({phase:"decrypting",progress:5}),c.length<1)throw new o("INVALID_ENCRYPTED_DATA");const i=c[0];if(i===D){if(!e)throw new o("PASSWORD_REQUIRED");return await ae(c,e,r)}else if(i===m){if(!t)throw new o("KEYFILE_REQUIRED");return await ce(c,t,r)}else throw new o("UNSUPPORTED_FORMAT")}catch(c){throw c instanceof o?c:new o("DECRYPTION_FAILED")}}async function ae(n,a,e){if(n.length<C)throw new o("INVALID_ENCRYPTED_DATA");e==null||e({phase:"deriving_key",progress:10});const t=n.slice(1,1+h),r=n.slice(1+h,1+h+f),c=n.slice(1+h+f),i=await b(a,t);e==null||e({phase:"decrypting",progress:30});try{const s=await crypto.subtle.decrypt({name:A,iv:E(r)},i,c);return e==null||e({phase:"complete",progress:100}),new Blob([s])}catch{throw new o("INVALID_PASSWORD")}}async function ce(n,a,e){if(n.length<K)throw new o("INVALID_ENCRYPTED_DATA");e==null||e({phase:"decrypting",progress:10});const t=n.slice(1,1+f),r=n.slice(1+f),c=await k(a);e==null||e({phase:"decrypting",progress:30});try{const i=await crypto.subtle.decrypt({name:A,iv:E(t)},c,r);return e==null||e({phase:"complete",progress:100}),new Blob([i])}catch{throw new o("INVALID_KEYFILE")}}function Q(n,a){const e=new Uint8Array(12);e.set(n);const t=new DataView(e.buffer),r=t.getUint32(8,!0);return t.setUint32(8,r^a,!0),e}function j(n,a){const e=new Uint8Array(n.length+a.length);return e.set(n,0),e.set(a,n.length),e}async function g(n,a,e,t){const r=Q(e,t),c=await crypto.subtle.encrypt({name:A,iv:E(r)},a,E(n)),i=new Uint8Array(4+c.byteLength);return new DataView(i.buffer).setUint32(0,c.byteLength,!0),i.set(new Uint8Array(c),4),i}async function ie(n,a,e,t){const r=Q(e,t);try{const c=await crypto.subtle.decrypt({name:A,iv:E(r)},a,E(n));return new Uint8Array(c)}catch{throw new o("DECRYPTION_FAILED")}}function se(n,a,e,t){if(n&&e){const r=new Uint8Array(34);return r[0]=S,r[1]=N,new DataView(r.buffer).setUint32(2,a,!0),r.set(e,6),r.set(t,22),r}else{const r=new Uint8Array(18);return r[0]=T,r[1]=N,new DataView(r.buffer).setUint32(2,a,!0),r.set(t,6),r}}function oe(n){const a=n[0],e=a===S;if(a!==S&&a!==T)throw new o("UNSUPPORTED_FORMAT");const t=n[1];if(t!==N)throw new o("UNSUPPORTED_FORMAT");const r=new DataView(n.buffer,n.byteOffset).getUint32(2,!0);if(e){const c=n.slice(6,22),i=n.slice(22,34);return{isPassword:e,version:t,chunkSize:r,salt:c,baseIV:i,headerSize:34}}else{const c=n.slice(6,18);return{isPassword:e,version:t,chunkSize:r,salt:null,baseIV:c,headerSize:18}}}async function q(n){const{password:a,keyData:e,chunkSize:t=V,onProgress:r}=n;if(!a&&!e)throw new o("PASSWORD_REQUIRED");const c=!!a,i=c?x():null,s=M();r==null||r({phase:"deriving_key",processedBytes:0,processedChunks:0});const p=c?await b(a,i):await k(e),y=se(c,t,i,s);let l=new Uint8Array(0),u=0,R=0;return{stream:new TransformStream({async transform(I,d){for(l=j(l,I);l.length>=t;){const _=l.slice(0,t);l=l.slice(t);const w=await g(_,p,s,u);d.enqueue(w),u++,R+=_.length,r==null||r({phase:"processing",processedBytes:R,processedChunks:u})}},async flush(I){if(l.length>0){const d=await g(l,p,s,u);I.enqueue(d),R+=l.length,u++}r==null||r({phase:"complete",processedBytes:R,processedChunks:u,progress:100})}}),header:y}}function Z(n){const{password:a,keyData:e,onProgress:t}=n;let r=new Uint8Array(0),c=!1,i=null,s=null,p=0,y=0,l=!1,u=0;return new TransformStream({async transform(R,v){if(r=j(r,R),!c){if(r.length<2)return;const d=r[0]===S?34:18;if(r.length<d)return;const _=r.slice(0,d),w=oe(_);if(l=w.isPassword,s=w.baseIV,u=w.headerSize,l&&!a)throw new o("PASSWORD_REQUIRED");if(!l&&!e)throw new o("KEYFILE_REQUIRED");t==null||t({phase:"deriving_key",processedBytes:0,processedChunks:0}),i=l?await b(a,w.salt):await k(e),r=r.slice(u),c=!0}for(;r.length>=4;){const d=4+new DataView(r.buffer,r.byteOffset).getUint32(0,!0);if(r.length<d)break;const _=r.slice(4,d);r=r.slice(d);const w=await ie(_,i,s,p);v.enqueue(w),y+=w.length,p++,t==null||t({phase:"processing",processedBytes:y,processedChunks:p})}},async flush(){if(r.length>0)throw new o("INVALID_ENCRYPTED_DATA");t==null||t({phase:"complete",processedBytes:y,processedChunks:p,progress:100})}})}async function le(n,a){const e=n.size,t=a.onProgress?l=>{a.onProgress({...l,totalBytes:e,progress:l.phase==="complete"?100:Math.round(l.processedBytes/e*100)})}:void 0,{stream:r,header:c}=await q({...a,onProgress:t}),s=n.stream().pipeThrough(r);let p=!1;const y=s.getReader();return new ReadableStream({async pull(l){if(!p){l.enqueue(c),p=!0;return}try{const{done:u,value:R}=await y.read();u?l.close():l.enqueue(R)}catch(u){l.error(u)}},cancel(){y.releaseLock()}})}function ye(n,a){const e=Z(a);return n instanceof ReadableStream?n.pipeThrough(e):n.stream().pipeThrough(e)}async function pe(n){const a=await L(n),e=new Uint8Array(a);if(e.length<1)return"unknown";const t=e[0];return t===D?"password":t===m?"keyfile":t===S?"password-stream":t===T?"keyfile-stream":"unknown"}const ue=34,fe=18;async function de(n){const a=await L(n),e=new Uint8Array(a);if(e.length<1)return!1;const t=e[0];return t===D&&e.length>=C||t===m&&e.length>=K||t===S&&e.length>=ue||t===T&&e.length>=fe}function he(n){return n==="password-stream"||n==="keyfile-stream"}function we(){const n=O(B);return{version:1,algorithm:"AES-256-GCM",key:X(n.buffer),createdAt:new Date().toISOString()}}function Ee(n){try{const a=JSON.parse(n);return Ae(a)?a:null}catch{return null}}function Ae(n){if(typeof n!="object"||n===null)return!1;const a=n;return a.version===1&&a.algorithm==="AES-256-GCM"&&typeof a.key=="string"&&a.key.length>0&&typeof a.createdAt=="string"}function Re(n,a,e="key"){const t={version:1,algorithm:"AES-256-GCM",key:n,createdAt:new Date().toISOString()},r=JSON.stringify(t,null,2),c=new Blob([r],{type:"application/json"}),i=URL.createObjectURL(c),s=document.createElement("a");s.href=i,s.download=`${a}.${e}`,s.style.display="none",document.body.appendChild(s),s.click(),document.body.removeChild(s),URL.revokeObjectURL(i)}async function Se(n){const a=G(n),e=await crypto.subtle.digest("SHA-256",a),t=new Uint8Array(e);return Array.from(t).map(r=>r.toString(16).padStart(2,"0")).join("")}async function _e(n,a){const{fileName:e,onProgress:t,...r}=a;try{t==null||t({phase:"downloading",progress:0});const c=await fetch(n);if(!c.ok)throw new o("DOWNLOAD_FAILED",`Download failed: ${c.status} ${c.statusText}`);const i=c.headers.get("content-length");let s;i&&c.body?s=await Ie(c.body,parseInt(i,10),y=>{t==null||t({phase:"downloading",progress:Math.round(y*50)})}):(t==null||t({phase:"downloading",progress:25}),s=await c.arrayBuffer()),t==null||t({phase:"downloading",progress:50});const p=await z(s,{...r,onProgress:y=>{const l=50+Math.round(y.progress*.45);t==null||t({phase:y.phase==="complete"?"complete":y.phase,progress:y.phase==="complete"?100:l})}});De(p,e),t==null||t({phase:"complete",progress:100})}catch(c){throw c instanceof o?c:new o("DOWNLOAD_FAILED")}}async function Ie(n,a,e){const t=n.getReader(),r=[];let c=0,i=!1;for(;!i;){const y=await t.read();if(i=y.done,y.value){r.push(y.value),c+=y.value.length;const l=c/a;e(Math.min(l,1))}}const s=new Uint8Array(c);let p=0;for(const y of r)s.set(y,p),p+=y.length;return s.buffer}function De(n,a){const e=URL.createObjectURL(n),t=document.createElement("a");t.href=e,t.download=a,t.style.display="none",document.body.appendChild(t),t.click(),document.body.removeChild(t),URL.revokeObjectURL(e)}exports.ALGORITHM=A;exports.AUTH_TAG_LENGTH=F;exports.CryptoError=o;exports.DEFAULT_CHUNK_SIZE=V;exports.ENCRYPTION_MARKER_KEYFILE=m;exports.ENCRYPTION_MARKER_KEYFILE_STREAM=T;exports.ENCRYPTION_MARKER_PASSWORD=D;exports.ENCRYPTION_MARKER_PASSWORD_STREAM=S;exports.HASH_ALGORITHM=W;exports.IV_LENGTH=f;exports.KEYFILE_KEY_LENGTH=B;exports.KEY_LENGTH=U;exports.MIN_ENCRYPTED_SIZE_KEYFILE=K;exports.MIN_ENCRYPTED_SIZE_PASSWORD=C;exports.PBKDF2_ITERATIONS=H;exports.SALT_LENGTH=h;exports.STREAM_FORMAT_VERSION=N;exports.computeKeyFileHash=Se;exports.createDecryptStream=Z;exports.createEncryptStream=q;exports.decryptFile=z;exports.decryptFileStream=ye;exports.downloadAndDecrypt=_e;exports.downloadKeyFile=Re;exports.encryptFile=te;exports.encryptFileStream=le;exports.generateKeyFile=we;exports.generateRandomPassword=ee;exports.getEncryptionType=pe;exports.isCryptoError=J;exports.isEncryptedFile=de;exports.isStreamingEncryption=he;exports.parseKeyFile=Ee;
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const E=16,f=12,U=256,V=1e5,W=32,_=1,T=2,D=17,I=18,C=64*1024,b=1,K=16,M=1+E+f+K,g=1+f+K,S="AES-GCM",G="SHA-256",ee={INVALID_INPUT:"Input must be a File, Blob, or ArrayBuffer.",PASSWORD_REQUIRED:"Password or keyfile is required for encryption.",KEYFILE_REQUIRED:"Keyfile is required to decrypt this file.",INVALID_PASSWORD:"Decryption failed: incorrect password.",INVALID_KEYFILE:"Decryption failed: incorrect keyfile.",INVALID_ENCRYPTED_DATA:"Invalid encrypted data: file may be corrupted.",ENCRYPTION_FAILED:"Encryption failed.",DECRYPTION_FAILED:"Decryption failed.",DOWNLOAD_FAILED:"File download failed.",UNSUPPORTED_FORMAT:"Unsupported encryption format."};class o extends Error{constructor(a,e){super(e??ee[a]),this.name="CryptoError",this.code=a,Error.captureStackTrace&&Error.captureStackTrace(this,o)}}function te(n){return n instanceof o}async function N(n){if(n instanceof ArrayBuffer)return n;if(n instanceof Blob)return n.arrayBuffer();throw new o("INVALID_INPUT")}function R(n){return n.buffer.slice(n.byteOffset,n.byteOffset+n.byteLength)}function ne(n){const a=new Uint8Array(n);let e="";for(let t=0;t<a.byteLength;t++)e+=String.fromCharCode(a[t]);return btoa(e)}function x(n){const a=atob(n),e=new Uint8Array(a.length);for(let t=0;t<a.length;t++)e[t]=a.charCodeAt(t);return e.buffer}async function O(n,a){const e=new TextEncoder,t=await crypto.subtle.importKey("raw",e.encode(n),"PBKDF2",!1,["deriveKey"]);return crypto.subtle.deriveKey({name:"PBKDF2",salt:R(a),iterations:V,hash:G},t,{name:S,length:U},!1,["encrypt","decrypt"])}async function k(n){try{const a=x(n);return await crypto.subtle.importKey("raw",a,{name:S,length:U},!1,["encrypt","decrypt"])}catch{throw new o("INVALID_KEYFILE")}}function F(n){return crypto.getRandomValues(new Uint8Array(n))}function z(){return F(E)}function v(){return F(f)}const Y="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*",re=16;function ae(n=re){const a=Math.max(8,Math.min(128,n)),e=F(a);let t="";for(let r=0;r<a;r++){const c=e[r]%Y.length;t+=Y[c]}return t}function Q(n,a){const e=new Uint8Array(12);e.set(n);const t=new DataView(e.buffer),r=t.getUint32(8,!0);return t.setUint32(8,r^a,!0),e}function j(n,a){const e=new Uint8Array(n.length+a.length);return e.set(n,0),e.set(a,n.length),e}async function H(n,a,e,t){const r=Q(e,t),c=await crypto.subtle.encrypt({name:S,iv:R(r)},a,R(n)),s=new Uint8Array(4+c.byteLength);return new DataView(s.buffer).setUint32(0,c.byteLength,!0),s.set(new Uint8Array(c),4),s}async function ce(n,a,e,t,r){const c=Q(e,t);try{const s=await crypto.subtle.decrypt({name:S,iv:R(c)},a,R(n));return new Uint8Array(s)}catch{throw t===0?new o(r?"INVALID_PASSWORD":"INVALID_KEYFILE"):new o("DECRYPTION_FAILED")}}function se(n,a,e,t){if(n&&e){const r=new Uint8Array(34);return r[0]=D,r[1]=b,new DataView(r.buffer).setUint32(2,a,!0),r.set(e,6),r.set(t,22),r}else{const r=new Uint8Array(18);return r[0]=I,r[1]=b,new DataView(r.buffer).setUint32(2,a,!0),r.set(t,6),r}}function ie(n){const a=n[0],e=a===D;if(a!==D&&a!==I)throw new o("UNSUPPORTED_FORMAT");const t=n[1];if(t!==b)throw new o("UNSUPPORTED_FORMAT");const r=new DataView(n.buffer,n.byteOffset).getUint32(2,!0);if(e){const c=n.slice(6,22),s=n.slice(22,34);return{isPassword:e,version:t,chunkSize:r,salt:c,baseIV:s,headerSize:34}}else{const c=n.slice(6,18);return{isPassword:e,version:t,chunkSize:r,salt:null,baseIV:c,headerSize:18}}}async function q(n){const{password:a,keyData:e,chunkSize:t=C,onProgress:r}=n;if(!a&&!e)throw new o("PASSWORD_REQUIRED");const c=!!a,s=c?z():null,i=v();r==null||r({phase:"deriving_key",processedBytes:0,processedChunks:0});const y=c?await O(a,s):await k(e),p=se(c,t,s,i);let l=new Uint8Array(0),u=0,d=0;return{stream:new TransformStream({async transform(h,w){for(l=j(l,h);l.length>=t;){const m=l.slice(0,t);l=l.slice(t);const A=await H(m,y,i,u);w.enqueue(A),u++,d+=m.length,r==null||r({phase:"encrypting",processedBytes:d,processedChunks:u})}},async flush(h){if(l.length>0){const w=await H(l,y,i,u);h.enqueue(w),d+=l.length,u++}r==null||r({phase:"complete",processedBytes:d,processedChunks:u,progress:100})}}),header:p}}function Z(n){const{password:a,keyData:e,onProgress:t}=n;let r=new Uint8Array(0),c=!1,s=null,i=null,y=0,p=0,l=!1,u=0;return new TransformStream({async transform(d,L){if(r=j(r,d),!c){if(r.length<2)return;const w=r[0]===D?34:18;if(r.length<w)return;const m=r.slice(0,w),A=ie(m);if(l=A.isPassword,i=A.baseIV,u=A.headerSize,l&&!a)throw new o("PASSWORD_REQUIRED");if(!l&&!e)throw new o("KEYFILE_REQUIRED");t==null||t({phase:"deriving_key",processedBytes:0,processedChunks:0}),s=l?await O(a,A.salt):await k(e),r=r.slice(u),c=!0}for(;r.length>=4;){const w=4+new DataView(r.buffer,r.byteOffset).getUint32(0,!0);if(r.length<w)break;const m=r.slice(4,w);r=r.slice(w);const A=await ce(m,s,i,y,l);L.enqueue(A),p+=A.length,y++,t==null||t({phase:"decrypting",processedBytes:p,processedChunks:y})}},async flush(){if(r.length>0)throw new o("INVALID_ENCRYPTED_DATA");t==null||t({phase:"complete",processedBytes:p,processedChunks:y,progress:100})}})}async function $(n,a){const e=n.size,t=a.onProgress?l=>{a.onProgress({...l,totalBytes:e,progress:l.phase==="complete"?100:Math.round(l.processedBytes/e*100)})}:void 0,{stream:r,header:c}=await q({...a,onProgress:t}),i=n.stream().pipeThrough(r);let y=!1;const p=i.getReader();return new ReadableStream({async pull(l){if(!y){l.enqueue(c),y=!0;return}try{const{done:u,value:d}=await p.read();u?l.close():l.enqueue(d)}catch(u){l.error(u)}},cancel(){p.releaseLock()}})}function B(n,a){const e=Z(a);return n instanceof ReadableStream?n.pipeThrough(e):n.stream().pipeThrough(e)}const oe=100*1024*1024;async function J(n,a){const{password:e,keyData:t,onProgress:r}=a;if(!e&&!t)throw new o("PASSWORD_REQUIRED");try{r==null||r({phase:"deriving_key",progress:0});const c=await N(n);return r==null||r({phase:"deriving_key",progress:10}),t?await pe(c,t,r):await le(c,e,r)}catch(c){throw c instanceof o?c:new o("ENCRYPTION_FAILED")}}async function le(n,a,e){const t=z(),r=v();e==null||e({phase:"deriving_key",progress:20});const c=await O(a,t);e==null||e({phase:"encrypting",progress:30});const s=await crypto.subtle.encrypt({name:S,iv:R(r)},c,n);e==null||e({phase:"encrypting",progress:90});const i=new Uint8Array(1+E+f+s.byteLength);return i[0]=_,i.set(t,1),i.set(r,1+E),i.set(new Uint8Array(s),1+E+f),e==null||e({phase:"complete",progress:100}),new Blob([i],{type:"application/octet-stream"})}async function pe(n,a,e){const t=v();e==null||e({phase:"deriving_key",progress:20});const r=await k(a);e==null||e({phase:"encrypting",progress:30});const c=await crypto.subtle.encrypt({name:S,iv:R(t)},r,n);e==null||e({phase:"encrypting",progress:90});const s=new Uint8Array(1+f+c.byteLength);return s[0]=T,s.set(t,1),s.set(new Uint8Array(c),1+f),e==null||e({phase:"complete",progress:100}),new Blob([s],{type:"application/octet-stream"})}async function ye(n,a){const{password:e,keyData:t,onProgress:r,autoStreaming:c=!1,streamingThreshold:s=oe,chunkSize:i=C}=a;if(!e&&!t)throw new o("PASSWORD_REQUIRED");if(c&&n.size>s){const l=await $(n,{password:e,keyData:t,chunkSize:i,onProgress:r?d=>{r({phase:d.phase,progress:d.progress??Math.round(d.processedBytes/n.size*100)})}:void 0});return await new Response(l).blob()}else return await J(n,{password:e,keyData:t,onProgress:r})}async function X(n,a){const{password:e,keyData:t,onProgress:r}=a;try{r==null||r({phase:"decrypting",progress:0});const c=new Uint8Array(await N(n));if(r==null||r({phase:"decrypting",progress:5}),c.length<1)throw new o("INVALID_ENCRYPTED_DATA");const s=c[0];if(s===_){if(!e)throw new o("PASSWORD_REQUIRED");return await ue(c,e,r)}else if(s===T){if(!t)throw new o("KEYFILE_REQUIRED");return await de(c,t,r)}else{if(s===D||s===I)return await fe(n,a);throw new o("UNSUPPORTED_FORMAT")}}catch(c){throw c instanceof o?c:new o("DECRYPTION_FAILED")}}async function ue(n,a,e){if(n.length<M)throw new o("INVALID_ENCRYPTED_DATA");e==null||e({phase:"deriving_key",progress:10});const t=n.slice(1,1+E),r=n.slice(1+E,1+E+f),c=n.slice(1+E+f),s=await O(a,t);e==null||e({phase:"decrypting",progress:30});try{const i=await crypto.subtle.decrypt({name:S,iv:R(r)},s,c);return e==null||e({phase:"complete",progress:100}),new Blob([i])}catch{throw new o("INVALID_PASSWORD")}}async function de(n,a,e){if(n.length<g)throw new o("INVALID_ENCRYPTED_DATA");e==null||e({phase:"decrypting",progress:10});const t=n.slice(1,1+f),r=n.slice(1+f),c=await k(a);e==null||e({phase:"decrypting",progress:30});try{const s=await crypto.subtle.decrypt({name:S,iv:R(t)},c,r);return e==null||e({phase:"complete",progress:100}),new Blob([s])}catch{throw new o("INVALID_KEYFILE")}}async function fe(n,a){const{password:e,keyData:t,onProgress:r}=a,c=n instanceof Blob?n:new Blob([n]),i=B(c,{password:e,keyData:t,onProgress:r?p=>{r({phase:p.phase,progress:p.progress??Math.round(p.processedBytes/(p.totalBytes||1)*100)})}:void 0});return await new Response(i).blob()}async function he(n){const a=await N(n),e=new Uint8Array(a);if(e.length<1)return"unknown";const t=e[0];return t===_?"password":t===T?"keyfile":t===D?"password-stream":t===I?"keyfile-stream":"unknown"}const we=34,Ee=18;async function Ae(n){const a=await N(n),e=new Uint8Array(a);if(e.length<1)return!1;const t=e[0];return t===_&&e.length>=M||t===T&&e.length>=g||t===D&&e.length>=we||t===I&&e.length>=Ee}function Re(n){return n==="password-stream"||n==="keyfile-stream"}function Se(){const n=F(W);return{version:1,algorithm:"AES-256-GCM",key:ne(n.buffer),createdAt:new Date().toISOString()}}function De(n){try{const a=JSON.parse(n);return me(a)?a:null}catch{return null}}function me(n){if(typeof n!="object"||n===null)return!1;const a=n;return a.version===1&&a.algorithm==="AES-256-GCM"&&typeof a.key=="string"&&a.key.length>0&&typeof a.createdAt=="string"}function Ie(n,a,e="key"){const t={version:1,algorithm:"AES-256-GCM",key:n,createdAt:new Date().toISOString()},r=JSON.stringify(t,null,2),c=new Blob([r],{type:"application/json"}),s=URL.createObjectURL(c),i=document.createElement("a");i.href=s,i.download=`${a}.${e}`,i.style.display="none",document.body.appendChild(i),i.click(),document.body.removeChild(i),URL.revokeObjectURL(s)}async function _e(n){const a=x(n),e=await crypto.subtle.digest("SHA-256",a),t=new Uint8Array(e);return Array.from(t).map(r=>r.toString(16).padStart(2,"0")).join("")}async function Te(n,a){const{fileName:e,onProgress:t,...r}=a;try{t==null||t({phase:"downloading",progress:0});const c=await fetch(n);if(!c.ok)throw new o("DOWNLOAD_FAILED",`Download failed: ${c.status} ${c.statusText}`);const s=c.headers.get("content-length");let i;s&&c.body?i=await Le(c.body,parseInt(s,10),p=>{t==null||t({phase:"downloading",progress:Math.round(p*50)})}):(t==null||t({phase:"downloading",progress:25}),i=await c.arrayBuffer()),t==null||t({phase:"downloading",progress:50});const y=await X(i,{...r,onProgress:p=>{const l=50+Math.round(p.progress*.45);t==null||t({phase:p.phase==="complete"?"complete":p.phase,progress:p.phase==="complete"?100:l})}});P(y,e),t==null||t({phase:"complete",progress:100})}catch(c){throw c instanceof o?c:new o("DOWNLOAD_FAILED")}}async function Le(n,a,e){const t=n.getReader(),r=[];let c=0,s=!1;for(;!s;){const p=await t.read();if(s=p.done,p.value){r.push(p.value),c+=p.value.length;const l=c/a;e(Math.min(l,1))}}const i=new Uint8Array(c);let y=0;for(const p of r)i.set(p,y),y+=p.length;return i.buffer}function P(n,a){const e=URL.createObjectURL(n),t=document.createElement("a");t.href=e,t.download=a,t.style.display="none",document.body.appendChild(t),t.click(),document.body.removeChild(t),URL.revokeObjectURL(e)}async function be(n,a){const{fileName:e,password:t,keyData:r,onProgress:c}=a;try{c==null||c({phase:"downloading",processedBytes:0,processedChunks:0,progress:0});const s=await fetch(n);if(!s.ok)throw new o("DOWNLOAD_FAILED",`Download failed: ${s.status} ${s.statusText}`);const i=s.headers.get("content-length"),y=i?parseInt(i,10):void 0;c==null||c({phase:"downloading",processedBytes:0,processedChunks:0,totalBytes:y,progress:25});const p=await s.blob();let l=0;const u=B(p,{password:t,keyData:r,onProgress:h=>{l=h.processedBytes,c==null||c({...h,progress:h.phase==="complete"?100:50+Math.round((h.progress??0)*.45)})}}),L=await new Response(u).blob();P(L,e),c==null||c({phase:"complete",processedBytes:l,processedChunks:0,progress:100})}catch(s){throw s instanceof o?s:new o("DOWNLOAD_FAILED")}}exports.ALGORITHM=S;exports.AUTH_TAG_LENGTH=K;exports.CryptoError=o;exports.DEFAULT_CHUNK_SIZE=C;exports.ENCRYPTION_MARKER_KEYFILE=T;exports.ENCRYPTION_MARKER_KEYFILE_STREAM=I;exports.ENCRYPTION_MARKER_PASSWORD=_;exports.ENCRYPTION_MARKER_PASSWORD_STREAM=D;exports.HASH_ALGORITHM=G;exports.IV_LENGTH=f;exports.KEYFILE_KEY_LENGTH=W;exports.KEY_LENGTH=U;exports.MIN_ENCRYPTED_SIZE_KEYFILE=g;exports.MIN_ENCRYPTED_SIZE_PASSWORD=M;exports.PBKDF2_ITERATIONS=V;exports.SALT_LENGTH=E;exports.STREAM_FORMAT_VERSION=b;exports.computeKeyFileHash=_e;exports.createDecryptStream=Z;exports.createEncryptStream=q;exports.decryptFile=X;exports.decryptFileStream=B;exports.downloadAndDecrypt=Te;exports.downloadAndDecryptStream=be;exports.downloadKeyFile=Ie;exports.encryptFile=J;exports.encryptFileAuto=ye;exports.encryptFileStream=$;exports.generateKeyFile=Se;exports.generateRandomPassword=ae;exports.getEncryptionType=he;exports.isCryptoError=te;exports.isEncryptedFile=Ae;exports.isStreamingEncryption=Re;exports.parseKeyFile=De;
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|