@time-file/browser-file-crypto 1.0.3 → 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 +78 -4
- package/README.md +78 -4
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +398 -7
- package/dist/index.mjs +486 -200
- 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
|
@@ -44,9 +44,10 @@
|
|
|
44
44
|
- **Zero-Dependency** - 네이티브 Web Crypto API만 사용합니다.
|
|
45
45
|
- **AES-256-GCM** - 업계 표준 인증 암호화 방식입니다.
|
|
46
46
|
- **비밀번호 & 키파일** - 용도에 따라 두 가지 모드를 지원합니다.
|
|
47
|
+
- **스트리밍 지원** - 메모리 효율적인 대용량 파일 처리 (v1.1.0+)
|
|
47
48
|
- **진행률 콜백** - 암호화/복호화 진행 상황을 추적할 수 있습니다.
|
|
48
49
|
- **TypeScript** - 완전한 타입 정의가 포함되어 있습니다.
|
|
49
|
-
- **초경량** - gzip 압축 시
|
|
50
|
+
- **초경량** - gzip 압축 시 5KB 미만의 용량을 자랑합니다.
|
|
50
51
|
|
|
51
52
|
## 왜 사용해야 하나요?
|
|
52
53
|
|
|
@@ -162,6 +163,55 @@ const decrypted = await decryptFile(encrypted, {
|
|
|
162
163
|
});
|
|
163
164
|
```
|
|
164
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
|
+
|
|
186
|
+
### 스트리밍 암호화 (v1.1.0+)
|
|
187
|
+
|
|
188
|
+
메모리에 맞지 않는 대용량 파일을 처리할 때:
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
import { encryptFileStream, decryptFileStream } from '@time-file/browser-file-crypto';
|
|
192
|
+
|
|
193
|
+
// 대용량 파일 암호화 (메모리 효율적)
|
|
194
|
+
const encryptedStream = await encryptFileStream(largeFile, {
|
|
195
|
+
password: 'secret',
|
|
196
|
+
chunkSize: 1024 * 1024, // 1MB 청크 (기본값: 64KB)
|
|
197
|
+
onProgress: ({ processedBytes, totalBytes }) => {
|
|
198
|
+
console.log(`${Math.round(processedBytes / totalBytes * 100)}%`);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// 스트림을 Blob으로 변환
|
|
203
|
+
const response = new Response(encryptedStream);
|
|
204
|
+
const encryptedBlob = await response.blob();
|
|
205
|
+
|
|
206
|
+
// 복호화 - decryptFile이 스트리밍 포맷을 자동 감지합니다!
|
|
207
|
+
const decrypted = await decryptFile(encryptedBlob, { password: 'secret' });
|
|
208
|
+
|
|
209
|
+
// 또는 스트리밍 복호화를 직접 사용
|
|
210
|
+
const decryptedStream = decryptFileStream(encryptedBlob, { password: 'secret' });
|
|
211
|
+
const decryptResponse = new Response(decryptedStream);
|
|
212
|
+
const decryptedBlob = await decryptResponse.blob();
|
|
213
|
+
```
|
|
214
|
+
|
|
165
215
|
### 키파일 모드
|
|
166
216
|
|
|
167
217
|
비밀번호를 기억할 필요가 없습니다:
|
|
@@ -190,7 +240,7 @@ if (loaded) {
|
|
|
190
240
|
```typescript
|
|
191
241
|
import { getEncryptionType, isEncryptedFile, generateRandomPassword } from '@time-file/browser-file-crypto';
|
|
192
242
|
|
|
193
|
-
await getEncryptionType(blob); // 'password' | 'keyfile' | 'unknown'
|
|
243
|
+
await getEncryptionType(blob); // 'password' | 'keyfile' | 'password-stream' | 'keyfile-stream' | 'unknown'
|
|
194
244
|
await isEncryptedFile(blob); // true | false
|
|
195
245
|
generateRandomPassword(24); // 'Kx9#mP2$vL5@nQ8!...'
|
|
196
246
|
```
|
|
@@ -198,13 +248,21 @@ generateRandomPassword(24); // 'Kx9#mP2$vL5@nQ8!...'
|
|
|
198
248
|
### 다운로드 & 복호화
|
|
199
249
|
|
|
200
250
|
```typescript
|
|
201
|
-
import { downloadAndDecrypt } from '@time-file/browser-file-crypto';
|
|
251
|
+
import { downloadAndDecrypt, downloadAndDecryptStream } from '@time-file/browser-file-crypto';
|
|
202
252
|
|
|
253
|
+
// 일반 다운로드 & 복호화
|
|
203
254
|
await downloadAndDecrypt('https://example.com/secret.enc', {
|
|
204
255
|
password: 'secret',
|
|
205
256
|
fileName: 'document.pdf',
|
|
206
257
|
onProgress: ({ phase, progress }) => console.log(`${phase}: ${progress}%`)
|
|
207
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
|
+
});
|
|
208
266
|
```
|
|
209
267
|
|
|
210
268
|
### 에러 처리
|
|
@@ -249,6 +307,15 @@ try {
|
|
|
249
307
|
|
|
250
308
|
키파일 암호화:
|
|
251
309
|
=> [0x02] + [iv:12] + [ciphertext + auth_tag:16]
|
|
310
|
+
|
|
311
|
+
비밀번호 암호화 (스트리밍):
|
|
312
|
+
=> [0x11] + [version:1] + [chunkSize:4] + [salt:16] + [baseIV:12] + [chunks...]
|
|
313
|
+
|
|
314
|
+
키파일 암호화 (스트리밍):
|
|
315
|
+
=> [0x12] + [version:1] + [chunkSize:4] + [baseIV:12] + [chunks...]
|
|
316
|
+
|
|
317
|
+
각 스트리밍 청크:
|
|
318
|
+
=> [chunkLength:4] + [ciphertext + auth_tag:16]
|
|
252
319
|
```
|
|
253
320
|
|
|
254
321
|
### 참고 사항
|
|
@@ -278,7 +345,14 @@ import type {
|
|
|
278
345
|
Progress,
|
|
279
346
|
KeyFile,
|
|
280
347
|
EncryptionType,
|
|
281
|
-
CryptoErrorCode
|
|
348
|
+
CryptoErrorCode,
|
|
349
|
+
// 스트리밍 타입 (v1.1.0+)
|
|
350
|
+
StreamEncryptOptions,
|
|
351
|
+
StreamDecryptOptions,
|
|
352
|
+
StreamProgress,
|
|
353
|
+
// v1.1.1+
|
|
354
|
+
AutoEncryptOptions,
|
|
355
|
+
DownloadDecryptStreamOptions
|
|
282
356
|
} from '@time-file/browser-file-crypto';
|
|
283
357
|
```
|
|
284
358
|
|
package/README.md
CHANGED
|
@@ -44,9 +44,10 @@
|
|
|
44
44
|
- **Zero-Dependency** - Native Web Crypto API only
|
|
45
45
|
- **AES-256-GCM** - Industry-standard authenticated encryption
|
|
46
46
|
- **Password & Keyfile** - Two modes for different use cases
|
|
47
|
+
- **Streaming Support** - Memory-efficient large file handling (v1.1.0+)
|
|
47
48
|
- **Progress Callbacks** - Track encryption/decryption progress
|
|
48
49
|
- **TypeScript** - Full type definitions
|
|
49
|
-
- **Tiny** - <
|
|
50
|
+
- **Tiny** - < 5KB gzipped
|
|
50
51
|
|
|
51
52
|
## Why?
|
|
52
53
|
|
|
@@ -163,6 +164,55 @@ const decrypted = await decryptFile(encrypted, {
|
|
|
163
164
|
});
|
|
164
165
|
```
|
|
165
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
|
+
|
|
187
|
+
### Streaming Encryption (v1.1.0+)
|
|
188
|
+
|
|
189
|
+
For large files that don't fit in memory:
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
import { encryptFileStream, decryptFileStream } from '@time-file/browser-file-crypto';
|
|
193
|
+
|
|
194
|
+
// Encrypt large file (memory-efficient)
|
|
195
|
+
const encryptedStream = await encryptFileStream(largeFile, {
|
|
196
|
+
password: 'secret',
|
|
197
|
+
chunkSize: 1024 * 1024, // 1MB chunks (default: 64KB)
|
|
198
|
+
onProgress: ({ processedBytes, totalBytes }) => {
|
|
199
|
+
console.log(`${Math.round(processedBytes / totalBytes * 100)}%`);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Convert stream to Blob
|
|
204
|
+
const response = new Response(encryptedStream);
|
|
205
|
+
const encryptedBlob = await response.blob();
|
|
206
|
+
|
|
207
|
+
// Decrypt - decryptFile auto-detects streaming format!
|
|
208
|
+
const decrypted = await decryptFile(encryptedBlob, { password: 'secret' });
|
|
209
|
+
|
|
210
|
+
// Or use streaming decryption directly
|
|
211
|
+
const decryptedStream = decryptFileStream(encryptedBlob, { password: 'secret' });
|
|
212
|
+
const decryptResponse = new Response(decryptedStream);
|
|
213
|
+
const decryptedBlob = await decryptResponse.blob();
|
|
214
|
+
```
|
|
215
|
+
|
|
166
216
|
### Keyfile Mode
|
|
167
217
|
|
|
168
218
|
No password to remember:
|
|
@@ -191,7 +241,7 @@ if (loaded) {
|
|
|
191
241
|
```typescript
|
|
192
242
|
import { getEncryptionType, isEncryptedFile, generateRandomPassword } from '@time-file/browser-file-crypto';
|
|
193
243
|
|
|
194
|
-
await getEncryptionType(blob); // 'password' | 'keyfile' | 'unknown'
|
|
244
|
+
await getEncryptionType(blob); // 'password' | 'keyfile' | 'password-stream' | 'keyfile-stream' | 'unknown'
|
|
195
245
|
await isEncryptedFile(blob); // true | false
|
|
196
246
|
generateRandomPassword(24); // 'Kx9#mP2$vL5@nQ8!...'
|
|
197
247
|
```
|
|
@@ -199,13 +249,21 @@ generateRandomPassword(24); // 'Kx9#mP2$vL5@nQ8!...'
|
|
|
199
249
|
### Download & Decrypt
|
|
200
250
|
|
|
201
251
|
```typescript
|
|
202
|
-
import { downloadAndDecrypt } from '@time-file/browser-file-crypto';
|
|
252
|
+
import { downloadAndDecrypt, downloadAndDecryptStream } from '@time-file/browser-file-crypto';
|
|
203
253
|
|
|
254
|
+
// Standard download & decrypt
|
|
204
255
|
await downloadAndDecrypt('https://example.com/secret.enc', {
|
|
205
256
|
password: 'secret',
|
|
206
257
|
fileName: 'document.pdf',
|
|
207
258
|
onProgress: ({ phase, progress }) => console.log(`${phase}: ${progress}%`)
|
|
208
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
|
+
});
|
|
209
267
|
```
|
|
210
268
|
|
|
211
269
|
### Error Handling
|
|
@@ -250,6 +308,15 @@ Password-encrypted
|
|
|
250
308
|
|
|
251
309
|
Keyfile-encrypted
|
|
252
310
|
=> [0x02] + [iv:12] + [ciphertext + auth_tag:16]
|
|
311
|
+
|
|
312
|
+
Password-encrypted (streaming)
|
|
313
|
+
=> [0x11] + [version:1] + [chunkSize:4] + [salt:16] + [baseIV:12] + [chunks...]
|
|
314
|
+
|
|
315
|
+
Keyfile-encrypted (streaming)
|
|
316
|
+
=> [0x12] + [version:1] + [chunkSize:4] + [baseIV:12] + [chunks...]
|
|
317
|
+
|
|
318
|
+
Each streaming chunk:
|
|
319
|
+
=> [chunkLength:4] + [ciphertext + auth_tag:16]
|
|
253
320
|
```
|
|
254
321
|
|
|
255
322
|
### Notes
|
|
@@ -279,7 +346,14 @@ import type {
|
|
|
279
346
|
Progress,
|
|
280
347
|
KeyFile,
|
|
281
348
|
EncryptionType,
|
|
282
|
-
CryptoErrorCode
|
|
349
|
+
CryptoErrorCode,
|
|
350
|
+
// Streaming types (v1.1.0+)
|
|
351
|
+
StreamEncryptOptions,
|
|
352
|
+
StreamDecryptOptions,
|
|
353
|
+
StreamProgress,
|
|
354
|
+
// v1.1.1+
|
|
355
|
+
AutoEncryptOptions,
|
|
356
|
+
DownloadDecryptStreamOptions
|
|
283
357
|
} from '@time-file/browser-file-crypto';
|
|
284
358
|
```
|
|
285
359
|
|
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const s=16,p=12,_=256,N=1e5,m=32,u=1,d=2,D=16,R=1+s+p+D,L=1+p+D,f="AES-GCM",S="SHA-256",U={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 y extends Error{constructor(a,t){super(t??U[a]),this.name="CryptoError",this.code=a,Error.captureStackTrace&&Error.captureStackTrace(this,y)}}function C(n){return n instanceof y}async function w(n){if(n instanceof ArrayBuffer)return n;if(n instanceof Blob)return n.arrayBuffer();throw new y("INVALID_INPUT")}function h(n){return n.buffer.slice(n.byteOffset,n.byteOffset+n.byteLength)}function Y(n){const a=new Uint8Array(n);let t="";for(let e=0;e<a.byteLength;e++)t+=String.fromCharCode(a[e]);return btoa(t)}function b(n){const a=atob(n),t=new Uint8Array(a.length);for(let e=0;e<a.length;e++)t[e]=a.charCodeAt(e);return t.buffer}async function O(n,a){const t=new TextEncoder,e=await crypto.subtle.importKey("raw",t.encode(n),"PBKDF2",!1,["deriveKey"]);return crypto.subtle.deriveKey({name:"PBKDF2",salt:h(a),iterations:N,hash:S},e,{name:f,length:_},!1,["encrypt","decrypt"])}async function F(n){try{const a=b(n);return await crypto.subtle.importKey("raw",a,{name:f,length:_},!1,["encrypt","decrypt"])}catch{throw new y("INVALID_KEYFILE")}}function A(n){return crypto.getRandomValues(new Uint8Array(n))}function v(){return A(s)}function K(){return A(p)}const T="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*",H=16;function M(n=H){const a=Math.max(8,Math.min(128,n)),t=A(a);let e="";for(let i=0;i<a;i++){const c=t[i]%T.length;e+=T[c]}return e}async function B(n,a){const{password:t,keyData:e,onProgress:i}=a;if(!t&&!e)throw new y("PASSWORD_REQUIRED");try{i==null||i({phase:"deriving_key",progress:0});const c=await w(n);return i==null||i({phase:"deriving_key",progress:10}),e?await W(c,e,i):await G(c,t,i)}catch(c){throw c instanceof y?c:new y("ENCRYPTION_FAILED")}}async function G(n,a,t){const e=v(),i=K();t==null||t({phase:"deriving_key",progress:20});const c=await O(a,e);t==null||t({phase:"encrypting",progress:30});const r=await crypto.subtle.encrypt({name:f,iv:h(i)},c,n);t==null||t({phase:"encrypting",progress:90});const o=new Uint8Array(1+s+p+r.byteLength);return o[0]=u,o.set(e,1),o.set(i,1+s),o.set(new Uint8Array(r),1+s+p),t==null||t({phase:"complete",progress:100}),new Blob([o],{type:"application/octet-stream"})}async function W(n,a,t){const e=K();t==null||t({phase:"deriving_key",progress:20});const i=await F(a);t==null||t({phase:"encrypting",progress:30});const c=await crypto.subtle.encrypt({name:f,iv:h(e)},i,n);t==null||t({phase:"encrypting",progress:90});const r=new Uint8Array(1+p+c.byteLength);return r[0]=d,r.set(e,1),r.set(new Uint8Array(c),1+p),t==null||t({phase:"complete",progress:100}),new Blob([r],{type:"application/octet-stream"})}async function k(n,a){const{password:t,keyData:e,onProgress:i}=a;try{i==null||i({phase:"decrypting",progress:0});const c=new Uint8Array(await w(n));if(i==null||i({phase:"decrypting",progress:5}),c.length<1)throw new y("INVALID_ENCRYPTED_DATA");const r=c[0];if(r===u){if(!t)throw new y("PASSWORD_REQUIRED");return await V(c,t,i)}else if(r===d){if(!e)throw new y("KEYFILE_REQUIRED");return await x(c,e,i)}else throw new y("UNSUPPORTED_FORMAT")}catch(c){throw c instanceof y?c:new y("DECRYPTION_FAILED")}}async function V(n,a,t){if(n.length<R)throw new y("INVALID_ENCRYPTED_DATA");t==null||t({phase:"deriving_key",progress:10});const e=n.slice(1,1+s),i=n.slice(1+s,1+s+p),c=n.slice(1+s+p),r=await O(a,e);t==null||t({phase:"decrypting",progress:30});try{const o=await crypto.subtle.decrypt({name:f,iv:h(i)},r,c);return t==null||t({phase:"complete",progress:100}),new Blob([o])}catch{throw new y("INVALID_PASSWORD")}}async function x(n,a,t){if(n.length<L)throw new y("INVALID_ENCRYPTED_DATA");t==null||t({phase:"decrypting",progress:10});const e=n.slice(1,1+p),i=n.slice(1+p),c=await F(a);t==null||t({phase:"decrypting",progress:30});try{const r=await crypto.subtle.decrypt({name:f,iv:h(e)},c,i);return t==null||t({phase:"complete",progress:100}),new Blob([r])}catch{throw new y("INVALID_KEYFILE")}}async function g(n){const a=await w(n),t=new Uint8Array(a);if(t.length<1)return"unknown";const e=t[0];return e===u?"password":e===d?"keyfile":"unknown"}async function j(n){const a=await w(n),t=new Uint8Array(a);if(t.length<1)return!1;const e=t[0];return e===u&&t.length>=R||e===d&&t.length>=L}function Q(){const n=A(m);return{version:1,algorithm:"AES-256-GCM",key:Y(n.buffer),createdAt:new Date().toISOString()}}function Z(n){try{const a=JSON.parse(n);return $(a)?a:null}catch{return null}}function $(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 q(n,a,t="key"){const e={version:1,algorithm:"AES-256-GCM",key:n,createdAt:new Date().toISOString()},i=JSON.stringify(e,null,2),c=new Blob([i],{type:"application/json"}),r=URL.createObjectURL(c),o=document.createElement("a");o.href=r,o.download=`${a}.${t}`,o.style.display="none",document.body.appendChild(o),o.click(),document.body.removeChild(o),URL.revokeObjectURL(r)}async function J(n){const a=b(n),t=await crypto.subtle.digest("SHA-256",a),e=new Uint8Array(t);return Array.from(e).map(i=>i.toString(16).padStart(2,"0")).join("")}async function z(n,a){const{fileName:t,onProgress:e,...i}=a;try{e==null||e({phase:"downloading",progress:0});const c=await fetch(n);if(!c.ok)throw new y("DOWNLOAD_FAILED",`Download failed: ${c.status} ${c.statusText}`);const r=c.headers.get("content-length");let o;r&&c.body?o=await X(c.body,parseInt(r,10),l=>{e==null||e({phase:"downloading",progress:Math.round(l*50)})}):(e==null||e({phase:"downloading",progress:25}),o=await c.arrayBuffer()),e==null||e({phase:"downloading",progress:50});const E=await k(o,{...i,onProgress:l=>{const I=50+Math.round(l.progress*.45);e==null||e({phase:l.phase==="complete"?"complete":l.phase,progress:l.phase==="complete"?100:I})}});P(E,t),e==null||e({phase:"complete",progress:100})}catch(c){throw c instanceof y?c:new y("DOWNLOAD_FAILED")}}async function X(n,a,t){const e=n.getReader(),i=[];let c=0,r=!1;for(;!r;){const l=await e.read();if(r=l.done,l.value){i.push(l.value),c+=l.value.length;const I=c/a;t(Math.min(I,1))}}const o=new Uint8Array(c);let E=0;for(const l of i)o.set(l,E),E+=l.length;return o.buffer}function P(n,a){const t=URL.createObjectURL(n),e=document.createElement("a");e.href=t,e.download=a,e.style.display="none",document.body.appendChild(e),e.click(),document.body.removeChild(e),URL.revokeObjectURL(t)}exports.ALGORITHM=f;exports.AUTH_TAG_LENGTH=D;exports.CryptoError=y;exports.ENCRYPTION_MARKER_KEYFILE=d;exports.ENCRYPTION_MARKER_PASSWORD=u;exports.HASH_ALGORITHM=S;exports.IV_LENGTH=p;exports.KEYFILE_KEY_LENGTH=m;exports.KEY_LENGTH=_;exports.MIN_ENCRYPTED_SIZE_KEYFILE=L;exports.MIN_ENCRYPTED_SIZE_PASSWORD=R;exports.PBKDF2_ITERATIONS=N;exports.SALT_LENGTH=s;exports.computeKeyFileHash=J;exports.decryptFile=k;exports.downloadAndDecrypt=z;exports.downloadKeyFile=q;exports.encryptFile=B;exports.generateKeyFile=Q;exports.generateRandomPassword=M;exports.getEncryptionType=g;exports.isCryptoError=C;exports.isEncryptedFile=j;exports.parseKeyFile=Z;
|
|
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
|