@neoware_inc/neozipkit 0.5.0

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.
Files changed (171) hide show
  1. package/README.md +134 -0
  2. package/dist/browser/ZipkitBrowser.d.ts +27 -0
  3. package/dist/browser/ZipkitBrowser.d.ts.map +1 -0
  4. package/dist/browser/ZipkitBrowser.js +303 -0
  5. package/dist/browser/ZipkitBrowser.js.map +1 -0
  6. package/dist/browser/index.d.ts +9 -0
  7. package/dist/browser/index.d.ts.map +1 -0
  8. package/dist/browser/index.esm.d.ts +12 -0
  9. package/dist/browser/index.esm.d.ts.map +1 -0
  10. package/dist/browser/index.esm.js +46 -0
  11. package/dist/browser/index.esm.js.map +1 -0
  12. package/dist/browser/index.js +38 -0
  13. package/dist/browser/index.js.map +1 -0
  14. package/dist/browser-esm/index.d.ts +9 -0
  15. package/dist/browser-esm/index.js +50211 -0
  16. package/dist/browser-esm/index.js.map +7 -0
  17. package/dist/browser-umd/index.d.ts +9 -0
  18. package/dist/browser-umd/index.js +50221 -0
  19. package/dist/browser-umd/index.js.map +7 -0
  20. package/dist/browser-umd/index.min.js +39 -0
  21. package/dist/browser.d.ts +9 -0
  22. package/dist/browser.js +38 -0
  23. package/dist/core/ZipCompress.d.ts +99 -0
  24. package/dist/core/ZipCompress.d.ts.map +1 -0
  25. package/dist/core/ZipCompress.js +287 -0
  26. package/dist/core/ZipCompress.js.map +1 -0
  27. package/dist/core/ZipCopy.d.ts +175 -0
  28. package/dist/core/ZipCopy.d.ts.map +1 -0
  29. package/dist/core/ZipCopy.js +310 -0
  30. package/dist/core/ZipCopy.js.map +1 -0
  31. package/dist/core/ZipDecompress.d.ts +57 -0
  32. package/dist/core/ZipDecompress.d.ts.map +1 -0
  33. package/dist/core/ZipDecompress.js +155 -0
  34. package/dist/core/ZipDecompress.js.map +1 -0
  35. package/dist/core/ZipEntry.d.ts +138 -0
  36. package/dist/core/ZipEntry.d.ts.map +1 -0
  37. package/dist/core/ZipEntry.js +829 -0
  38. package/dist/core/ZipEntry.js.map +1 -0
  39. package/dist/core/Zipkit.d.ts +315 -0
  40. package/dist/core/Zipkit.d.ts.map +1 -0
  41. package/dist/core/Zipkit.js +647 -0
  42. package/dist/core/Zipkit.js.map +1 -0
  43. package/dist/core/ZstdManager.d.ts +56 -0
  44. package/dist/core/ZstdManager.d.ts.map +1 -0
  45. package/dist/core/ZstdManager.js +144 -0
  46. package/dist/core/ZstdManager.js.map +1 -0
  47. package/dist/core/components/HashCalculator.d.ts +138 -0
  48. package/dist/core/components/HashCalculator.d.ts.map +1 -0
  49. package/dist/core/components/HashCalculator.js +360 -0
  50. package/dist/core/components/HashCalculator.js.map +1 -0
  51. package/dist/core/components/Logger.d.ts +73 -0
  52. package/dist/core/components/Logger.d.ts.map +1 -0
  53. package/dist/core/components/Logger.js +156 -0
  54. package/dist/core/components/Logger.js.map +1 -0
  55. package/dist/core/components/ProgressTracker.d.ts +43 -0
  56. package/dist/core/components/ProgressTracker.d.ts.map +1 -0
  57. package/dist/core/components/ProgressTracker.js +112 -0
  58. package/dist/core/components/ProgressTracker.js.map +1 -0
  59. package/dist/core/components/Support.d.ts +64 -0
  60. package/dist/core/components/Support.d.ts.map +1 -0
  61. package/dist/core/components/Support.js +71 -0
  62. package/dist/core/components/Support.js.map +1 -0
  63. package/dist/core/components/Util.d.ts +26 -0
  64. package/dist/core/components/Util.d.ts.map +1 -0
  65. package/dist/core/components/Util.js +95 -0
  66. package/dist/core/components/Util.js.map +1 -0
  67. package/dist/core/constants/Errors.d.ts +52 -0
  68. package/dist/core/constants/Errors.d.ts.map +1 -0
  69. package/dist/core/constants/Errors.js +67 -0
  70. package/dist/core/constants/Errors.js.map +1 -0
  71. package/dist/core/constants/Headers.d.ts +170 -0
  72. package/dist/core/constants/Headers.d.ts.map +1 -0
  73. package/dist/core/constants/Headers.js +194 -0
  74. package/dist/core/constants/Headers.js.map +1 -0
  75. package/dist/core/encryption/Manager.d.ts +58 -0
  76. package/dist/core/encryption/Manager.d.ts.map +1 -0
  77. package/dist/core/encryption/Manager.js +121 -0
  78. package/dist/core/encryption/Manager.js.map +1 -0
  79. package/dist/core/encryption/ZipCrypto.d.ts +172 -0
  80. package/dist/core/encryption/ZipCrypto.d.ts.map +1 -0
  81. package/dist/core/encryption/ZipCrypto.js +554 -0
  82. package/dist/core/encryption/ZipCrypto.js.map +1 -0
  83. package/dist/core/encryption/index.d.ts +9 -0
  84. package/dist/core/encryption/index.d.ts.map +1 -0
  85. package/dist/core/encryption/index.js +17 -0
  86. package/dist/core/encryption/index.js.map +1 -0
  87. package/dist/core/encryption/types.d.ts +29 -0
  88. package/dist/core/encryption/types.d.ts.map +1 -0
  89. package/dist/core/encryption/types.js +12 -0
  90. package/dist/core/encryption/types.js.map +1 -0
  91. package/dist/core/index.d.ts +27 -0
  92. package/dist/core/index.d.ts.map +1 -0
  93. package/dist/core/index.js +59 -0
  94. package/dist/core/index.js.map +1 -0
  95. package/dist/core/version.d.ts +5 -0
  96. package/dist/core/version.d.ts.map +1 -0
  97. package/dist/core/version.js +31 -0
  98. package/dist/core/version.js.map +1 -0
  99. package/dist/index.d.ts +9 -0
  100. package/dist/index.d.ts.map +1 -0
  101. package/dist/index.js +38 -0
  102. package/dist/index.js.map +1 -0
  103. package/dist/node/ZipCompressNode.d.ts +123 -0
  104. package/dist/node/ZipCompressNode.d.ts.map +1 -0
  105. package/dist/node/ZipCompressNode.js +565 -0
  106. package/dist/node/ZipCompressNode.js.map +1 -0
  107. package/dist/node/ZipCopyNode.d.ts +165 -0
  108. package/dist/node/ZipCopyNode.d.ts.map +1 -0
  109. package/dist/node/ZipCopyNode.js +347 -0
  110. package/dist/node/ZipCopyNode.js.map +1 -0
  111. package/dist/node/ZipDecompressNode.d.ts +197 -0
  112. package/dist/node/ZipDecompressNode.d.ts.map +1 -0
  113. package/dist/node/ZipDecompressNode.js +678 -0
  114. package/dist/node/ZipDecompressNode.js.map +1 -0
  115. package/dist/node/ZipkitNode.d.ts +466 -0
  116. package/dist/node/ZipkitNode.d.ts.map +1 -0
  117. package/dist/node/ZipkitNode.js +1426 -0
  118. package/dist/node/ZipkitNode.js.map +1 -0
  119. package/dist/node/index.d.ts +25 -0
  120. package/dist/node/index.d.ts.map +1 -0
  121. package/dist/node/index.js +54 -0
  122. package/dist/node/index.js.map +1 -0
  123. package/dist/types/index.d.ts +45 -0
  124. package/dist/types/index.d.ts.map +1 -0
  125. package/dist/types/index.js +11 -0
  126. package/dist/types/index.js.map +1 -0
  127. package/examples/README.md +261 -0
  128. package/examples/append-data.json +44 -0
  129. package/examples/copy-zip-append.ts +139 -0
  130. package/examples/copy-zip.ts +152 -0
  131. package/examples/create-zip.ts +172 -0
  132. package/examples/extract-zip.ts +118 -0
  133. package/examples/list-zip.ts +161 -0
  134. package/examples/test-files/data.json +116 -0
  135. package/examples/test-files/document.md +80 -0
  136. package/examples/test-files/document.txt +6 -0
  137. package/examples/test-files/file1.txt +48 -0
  138. package/examples/test-files/file2.txt +80 -0
  139. package/examples/tsconfig.json +44 -0
  140. package/package.json +167 -0
  141. package/src/browser/ZipkitBrowser.ts +305 -0
  142. package/src/browser/index.esm.ts +32 -0
  143. package/src/browser/index.ts +19 -0
  144. package/src/core/ZipCompress.ts +370 -0
  145. package/src/core/ZipCopy.ts +434 -0
  146. package/src/core/ZipDecompress.ts +191 -0
  147. package/src/core/ZipEntry.ts +917 -0
  148. package/src/core/Zipkit.ts +794 -0
  149. package/src/core/ZstdManager.ts +165 -0
  150. package/src/core/components/HashCalculator.ts +384 -0
  151. package/src/core/components/Logger.ts +180 -0
  152. package/src/core/components/ProgressTracker.ts +134 -0
  153. package/src/core/components/Support.ts +77 -0
  154. package/src/core/components/Util.ts +91 -0
  155. package/src/core/constants/Errors.ts +78 -0
  156. package/src/core/constants/Headers.ts +205 -0
  157. package/src/core/encryption/Manager.ts +137 -0
  158. package/src/core/encryption/ZipCrypto.ts +650 -0
  159. package/src/core/encryption/index.ts +15 -0
  160. package/src/core/encryption/types.ts +33 -0
  161. package/src/core/index.ts +42 -0
  162. package/src/core/version.ts +33 -0
  163. package/src/index.ts +19 -0
  164. package/src/node/ZipCompressNode.ts +618 -0
  165. package/src/node/ZipCopyNode.ts +437 -0
  166. package/src/node/ZipDecompressNode.ts +793 -0
  167. package/src/node/ZipkitNode.ts +1706 -0
  168. package/src/node/index.ts +40 -0
  169. package/src/types/index.ts +68 -0
  170. package/src/types/modules.d.ts +22 -0
  171. package/src/types/opentimestamps.d.ts +1 -0
@@ -0,0 +1,650 @@
1
+ /**
2
+ * Traditional ZIP Crypto encryption implementation
3
+ * Compatible with standard ZIP 2.0 encryption (ZipCrypto)
4
+ */
5
+
6
+ import { EncryptionProvider, EncryptionOptions, EncryptionResult, DecryptionResult, EncryptionMethod } from './types';
7
+ import ZipEntry from '../ZipEntry';
8
+ import { Logger } from '../components/Logger';
9
+ import { GP_FLAG, ENCRYPT_HDR_SIZE } from '../constants/Headers';
10
+ import { randomBytes } from 'crypto';
11
+
12
+ export class ZipCrypto implements EncryptionProvider {
13
+ private static readonly KEY_LENGTH = 3; // 24 bits for traditional ZIP crypto
14
+
15
+ canHandle(method: EncryptionMethod): boolean {
16
+ return method === EncryptionMethod.ZIP_CRYPTO;
17
+ }
18
+
19
+ getMethodName(): string {
20
+ return 'ZIP-Crypto';
21
+ }
22
+
23
+ getKeyLength(): number {
24
+ return ZipCrypto.KEY_LENGTH;
25
+ }
26
+
27
+ async encrypt(data: Buffer, options: EncryptionOptions): Promise<EncryptionResult> {
28
+ try {
29
+ if (options.method !== EncryptionMethod.ZIP_CRYPTO) {
30
+ return {
31
+ success: false,
32
+ error: 'ZIP-Crypto encryption method required'
33
+ };
34
+ }
35
+
36
+ // Initialize keys from password
37
+ const keys = this.initKeys(options.password);
38
+
39
+ // Encrypt data using traditional ZIP crypto
40
+ const encryptedData = Buffer.alloc(data.length);
41
+
42
+ for (let i = 0; i < data.length; i++) {
43
+ const byte = data[i];
44
+ const encryptedByte = this.encryptByte(keys, byte);
45
+ encryptedData[i] = encryptedByte;
46
+ this.updateKeys(keys, byte);
47
+ }
48
+
49
+ return {
50
+ success: true,
51
+ encryptedData: encryptedData
52
+ };
53
+ } catch (error) {
54
+ return {
55
+ success: false,
56
+ error: error instanceof Error ? error.message : 'Unknown encryption error'
57
+ };
58
+ }
59
+ }
60
+
61
+ async decrypt(data: Buffer, options: EncryptionOptions): Promise<DecryptionResult> {
62
+ try {
63
+ if (options.method !== EncryptionMethod.ZIP_CRYPTO) {
64
+ return {
65
+ success: false,
66
+ error: 'ZIP-Crypto decryption method required'
67
+ };
68
+ }
69
+
70
+ // Initialize keys from password
71
+ const keys = this.initKeys(options.password);
72
+
73
+ // Decrypt data using traditional ZIP crypto
74
+ const decryptedData = Buffer.alloc(data.length);
75
+
76
+ for (let i = 0; i < data.length; i++) {
77
+ const encryptedByte = data[i];
78
+ const decryptedByte = this.decryptByte(keys, encryptedByte);
79
+ decryptedData[i] = decryptedByte;
80
+ this.updateKeys(keys, decryptedByte);
81
+ }
82
+
83
+ return {
84
+ success: true,
85
+ decryptedData: decryptedData
86
+ };
87
+ } catch (error) {
88
+ return {
89
+ success: false,
90
+ error: error instanceof Error ? error.message : 'Unknown decryption error'
91
+ };
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Verify password using check byte from decrypted header
97
+ * @param decryptedHeader - 12-byte decrypted encryption header
98
+ * @param checkByte - Expected check byte value
99
+ * @throws Error if password verification fails
100
+ */
101
+ verifyPassword(decryptedHeader: Buffer, checkByte: number): void {
102
+ if (decryptedHeader.length < 12) {
103
+ throw new Error('Decrypted header must be at least 12 bytes');
104
+ }
105
+
106
+ // Verify check byte (11th byte, 0-indexed)
107
+ if (decryptedHeader[11] !== checkByte) {
108
+ throw new Error('Password verification FAILED');
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Parse local header to extract information needed for decryption
114
+ * @param localHeaderBuffer - 30-byte local header buffer
115
+ * @param entry - ZIP entry to update with extracted information
116
+ * @returns Object with bitFlags, lastModTimeDate, localCrc, and checkByte
117
+ */
118
+ parseLocalHeaderForDecryption(localHeaderBuffer: Buffer, entry: ZipEntry): {
119
+ bitFlags: number;
120
+ lastModTimeDate: number;
121
+ localCrc: number;
122
+ checkByte: number;
123
+ } {
124
+ // Verify signature
125
+ if (localHeaderBuffer.readUInt32LE(0) !== 0x04034b50) {
126
+ throw new Error('Invalid local header signature');
127
+ }
128
+
129
+ // Read bit flags, lastModTimeDate, and CRC from local header
130
+ const bitFlags = localHeaderBuffer.readUInt16LE(6);
131
+ const lastModTimeDate = localHeaderBuffer.readUInt32LE(10);
132
+ const localCrc = localHeaderBuffer.readUInt32LE(14);
133
+
134
+ // Store lastModTimeDate in entry if DATA_DESC flag is set
135
+ if ((bitFlags & GP_FLAG.DATA_DESC) && (!(entry as any).lastModTimeDate || (entry as any).lastModTimeDate === 0)) {
136
+ (entry as any).lastModTimeDate = lastModTimeDate;
137
+ }
138
+
139
+ // Store local CRC for check byte calculation
140
+ (entry as any).localCrc = localCrc;
141
+
142
+ // Calculate check byte
143
+ let checkByte: number;
144
+ if (bitFlags & GP_FLAG.DATA_DESC) {
145
+ const timeDate = (entry as any).lastModTimeDate || entry.timeDateDOS || 0;
146
+ checkByte = (timeDate >> 8) & 0xff;
147
+ } else {
148
+ const crc = localCrc && localCrc !== 0 ? localCrc : entry.crc;
149
+ checkByte = (crc >> 24) & 0xff;
150
+ }
151
+
152
+ return { bitFlags, lastModTimeDate, localCrc, checkByte };
153
+ }
154
+
155
+ /**
156
+ * Create encryption header (12 bytes) for PKZIP encryption
157
+ * Generates 11 random bytes + 1 check byte
158
+ * @param entry - ZIP entry to create header for
159
+ * @param password - Password for encryption (used to calculate check byte)
160
+ * @returns 12-byte encryption header buffer
161
+ */
162
+ createEncryptionHeader(entry: ZipEntry, password: string): Buffer {
163
+ const header = Buffer.alloc(ENCRYPT_HDR_SIZE);
164
+
165
+ // Generate 11 random bytes (bytes 0-10)
166
+ const random = randomBytes(11);
167
+ random.copy(header, 0);
168
+
169
+ // Calculate check byte (byte 11)
170
+ // For DATA_DESC files: use lastModTimeDate >> 8
171
+ // For regular files: use CRC >> 24
172
+ let checkByte: number;
173
+ if ((entry.bitFlags & GP_FLAG.DATA_DESC) !== 0) {
174
+ const timeDate = entry.lastModTimeDate || entry.timeDateDOS || 0;
175
+ checkByte = (timeDate >> 8) & 0xff;
176
+ } else {
177
+ // Use CRC from entry (should be calculated before encryption)
178
+ checkByte = (entry.crc >> 24) & 0xff;
179
+ }
180
+
181
+ // Set check byte (11th byte, 0-indexed)
182
+ header[11] = checkByte;
183
+
184
+ Logger.debug(`[ZipCrypto] Created encryption header: checkByte=0x${checkByte.toString(16).padStart(2, '0')}, DATA_DESC=${(entry.bitFlags & GP_FLAG.DATA_DESC) !== 0}`);
185
+
186
+ return header;
187
+ }
188
+
189
+ /**
190
+ * Encrypt buffer (header + compressed data) for PKZIP encryption
191
+ * Creates encryption header, concatenates with compressed data, and encrypts everything
192
+ * @param entry - ZIP entry to encrypt
193
+ * @param compressedData - Compressed data to encrypt
194
+ * @param password - Password for encryption
195
+ * @returns Encrypted buffer (encrypted header + encrypted compressed data)
196
+ */
197
+ encryptBuffer(entry: ZipEntry, compressedData: Buffer, password: string): Buffer {
198
+ // Create 12-byte encryption header
199
+ const header = this.createEncryptionHeader(entry, password);
200
+
201
+ // Concatenate header + compressed data
202
+ const dataToEncrypt = Buffer.concat([header, compressedData]);
203
+
204
+ // Initialize encryption keys from password
205
+ const keys = this.initKeys(password);
206
+
207
+ // Encrypt the entire buffer (header + compressed data) maintaining key state
208
+ // This ensures keys are updated correctly throughout encryption
209
+ const encryptedData = Buffer.alloc(dataToEncrypt.length);
210
+
211
+ for (let i = 0; i < dataToEncrypt.length; i++) {
212
+ const byte = dataToEncrypt[i];
213
+ const encryptedByte = this.encryptByte(keys, byte);
214
+ encryptedData[i] = encryptedByte;
215
+ // Update keys after each byte - state must be maintained across the entire buffer
216
+ this.updateKeys(keys, byte);
217
+ }
218
+
219
+ Logger.debug(`[ZipCrypto] Encrypted buffer: ${compressedData.length} bytes compressed data + ${ENCRYPT_HDR_SIZE} bytes header = ${encryptedData.length} bytes total`);
220
+
221
+ return encryptedData;
222
+ }
223
+
224
+ /**
225
+ * Decrypt buffer-based encrypted data (for in-memory extraction)
226
+ * Handles local header parsing, encryption header extraction, and decryption
227
+ * @param entry - ZIP entry with encryption information
228
+ * @param buffer - Full ZIP file buffer
229
+ * @param encryptedData - Encrypted data without header (from parseLocalHeader)
230
+ * @param password - Password for decryption
231
+ * @returns Decrypted compressed data (without header)
232
+ */
233
+ decryptBuffer(entry: ZipEntry, buffer: Buffer, encryptedData: Buffer, password: string): Buffer {
234
+ // Read local header to get lastModTimeDate for DATA_DESC files (needed for check byte)
235
+ const localData = buffer.subarray(entry.localHdrOffset);
236
+ const localHeaderBuffer = localData.subarray(0, 30);
237
+
238
+ // Parse local header to extract decryption info
239
+ const { bitFlags, localCrc, checkByte } = this.parseLocalHeaderForDecryption(localHeaderBuffer, entry);
240
+
241
+ // Extract the 12-byte encryption header
242
+ // parseLocalHeader returns data WITHOUT the 12-byte encryption header
243
+ // We need to prepend the header before decrypting so keys are updated correctly
244
+ const fnameLen = localHeaderBuffer.readUInt16LE(26);
245
+ const extraLen = localHeaderBuffer.readUInt16LE(28);
246
+ const localSize = 30 + fnameLen + extraLen;
247
+ const ENCRYPT_HDR_SIZE = 12;
248
+ const encryptHeader = localData.subarray(localSize, localSize + ENCRYPT_HDR_SIZE);
249
+
250
+ // Prepend the header to the encrypted data
251
+ const encryptedDataWithHeader = Buffer.concat([encryptHeader, encryptedData]);
252
+
253
+ // Decrypt the full data (header + compressed data) in one pass
254
+ // This ensures keys are updated correctly throughout the decryption
255
+ // MEMORY EFFICIENCY: Decrypt in-place into encryptedDataWithHeader (no new allocation)
256
+ const keys = this.initKeys(password);
257
+
258
+ for (let i = 0; i < encryptedDataWithHeader.length; i++) {
259
+ const encryptedByte = encryptedDataWithHeader[i];
260
+ const decryptedByte = this.decryptByte(keys, encryptedByte);
261
+ encryptedDataWithHeader[i] = decryptedByte;
262
+ this.updateKeys(keys, decryptedByte);
263
+ }
264
+
265
+ // Extract the decrypted header (first 12 bytes) for password verification
266
+ // encryptedDataWithHeader now contains decrypted data
267
+ const decryptedHeader = encryptedDataWithHeader.subarray(0, ENCRYPT_HDR_SIZE);
268
+
269
+ // Verify password using check byte
270
+ this.verifyPassword(decryptedHeader, checkByte);
271
+
272
+ // Skip the decrypted header (first 12 bytes) and return only the decrypted compressed data
273
+ return encryptedDataWithHeader.subarray(ENCRYPT_HDR_SIZE);
274
+ }
275
+
276
+ /**
277
+ * Create a streaming decryptor for chunked data processing
278
+ * Handles 12-byte header decryption and verification, then yields decrypted chunks
279
+ *
280
+ * MEMORY EFFICIENCY: This method processes encrypted data one block at a time.
281
+ * - Decryption state (keys) is maintained across blocks via updateKeys()
282
+ * - Only the minimum 12-byte header is accumulated before password verification
283
+ * - Each decrypted block is yielded immediately without accumulation
284
+ *
285
+ * @param password - Password for decryption
286
+ * @param checkByte - Expected check byte value for password verification
287
+ * @param encryptedStream - Async generator of encrypted data chunks (one block at a time)
288
+ * @returns Async generator of decrypted data chunks (one block at a time)
289
+ */
290
+ async *createStreamDecryptor(
291
+ password: string,
292
+ checkByte: number,
293
+ encryptedStream: AsyncGenerator<Buffer>
294
+ ): AsyncGenerator<Buffer> {
295
+ // Initialize keys from password - state maintained across all blocks
296
+ const keys = this.initKeys(password);
297
+
298
+ // Process 12-byte encryption header incrementally (minimum accumulation)
299
+ const ENCRYPT_HDR_SIZE = 12;
300
+ const headerBuffer = Buffer.alloc(ENCRYPT_HDR_SIZE);
301
+ let headerBytesCollected = 0;
302
+ let currentChunk: Buffer | null = null;
303
+ let currentChunkOffset = 0;
304
+
305
+ // Read chunks incrementally until we have exactly 12 bytes for header
306
+ // This minimizes memory usage by only accumulating the necessary header bytes
307
+ while (headerBytesCollected < ENCRYPT_HDR_SIZE) {
308
+ // Get next chunk if current chunk is exhausted
309
+ if (!currentChunk || currentChunkOffset >= currentChunk.length) {
310
+ const result = await encryptedStream.next();
311
+
312
+ if (result.done || !result.value) {
313
+ throw new Error('ZIP-Crypto: insufficient encrypted data (missing encryption header)');
314
+ }
315
+
316
+ currentChunk = result.value;
317
+ currentChunkOffset = 0;
318
+ }
319
+
320
+ // Copy bytes from current chunk to header buffer
321
+ const bytesNeeded = ENCRYPT_HDR_SIZE - headerBytesCollected;
322
+ const bytesToCopy = Math.min(bytesNeeded, currentChunk.length - currentChunkOffset);
323
+
324
+ currentChunk.copy(headerBuffer, headerBytesCollected, currentChunkOffset, currentChunkOffset + bytesToCopy);
325
+ headerBytesCollected += bytesToCopy;
326
+ currentChunkOffset += bytesToCopy;
327
+ }
328
+
329
+ // Decrypt the 12-byte encryption header byte-by-byte
330
+ // This maintains proper key state for subsequent decryption
331
+ // MEMORY EFFICIENCY: Decrypt in-place into headerBuffer (no new allocation)
332
+ for (let i = 0; i < ENCRYPT_HDR_SIZE; i++) {
333
+ const encryptedByte = headerBuffer[i];
334
+ const decryptedByte = this.decryptByte(keys, encryptedByte) & 0xff;
335
+ headerBuffer[i] = decryptedByte;
336
+ // Update keys after each byte - state must be maintained across blocks
337
+ this.updateKeys(keys, decryptedByte);
338
+ }
339
+
340
+ // Verify password using check byte (11th byte of decrypted header)
341
+ // headerBuffer now contains decrypted header data
342
+ this.verifyPassword(headerBuffer, checkByte);
343
+
344
+ // Process remaining data from the first chunk (after header) if any
345
+ // This ensures we decrypt and yield data immediately without accumulating
346
+ // MEMORY EFFICIENCY: Decrypt in-place into currentChunk buffer (no new allocation)
347
+ if (currentChunk && currentChunkOffset < currentChunk.length) {
348
+ const remainingInChunk = currentChunk.length - currentChunkOffset;
349
+
350
+ for (let i = 0; i < remainingInChunk; i++) {
351
+ const pos = currentChunkOffset + i;
352
+ const encryptedByte = currentChunk[pos];
353
+ const decryptedByte = this.decryptByte(keys, encryptedByte);
354
+ currentChunk[pos] = decryptedByte;
355
+ // Maintain state across bytes - keys updated for next block
356
+ this.updateKeys(keys, decryptedByte);
357
+ }
358
+
359
+ // Yield subarray of currentChunk (already decrypted in-place)
360
+ yield currentChunk.subarray(currentChunkOffset);
361
+ }
362
+
363
+ // Process remaining chunks one at a time - decrypt and yield immediately
364
+ // Decryption state (keys) continues across blocks via updateKeys()
365
+ // MEMORY EFFICIENCY: Decrypt in-place into encryptedChunk buffer (no new allocation)
366
+ for await (const encryptedChunk of encryptedStream) {
367
+
368
+ // Decrypt each byte in-place, maintaining state across bytes
369
+ for (let i = 0; i < encryptedChunk.length; i++) {
370
+ const encryptedByte = encryptedChunk[i];
371
+ const decryptedByte = this.decryptByte(keys, encryptedByte);
372
+ encryptedChunk[i] = decryptedByte;
373
+ // Update keys after each byte - ensures state continues correctly across blocks
374
+ this.updateKeys(keys, decryptedByte);
375
+ }
376
+
377
+ // Yield the same buffer (now containing decrypted data) - no accumulation
378
+ yield encryptedChunk;
379
+ }
380
+ }
381
+
382
+ /**
383
+ * Initialize encryption keys from password
384
+ */
385
+ private initKeys(password: string): number[] {
386
+ const keys = [0x12345678, 0x23456789, 0x34567890];
387
+
388
+ for (let i = 0; i < password.length; i++) {
389
+ this.updateKeys(keys, password.charCodeAt(i));
390
+ }
391
+
392
+ return keys;
393
+ }
394
+
395
+ /**
396
+ * Update encryption keys with a byte
397
+ * Based on zip.js implementation - uses Math.imul for 32-bit integer multiplication
398
+ */
399
+ private updateKeys(keys: number[], byte: number): void {
400
+ keys[0] = ((keys[0] >>> 8) ^ ZipCrypto.CRC_TABLE[(keys[0] ^ byte) & 0xff]) >>> 0;
401
+ // Use Math.imul for multiplication (matches zip.js) and 0x08088405 constant
402
+ keys[1] = ((Math.imul(keys[1] + (keys[0] & 0xff), 0x08088405) + 1) & 0xFFFFFFFF) >>> 0;
403
+ keys[2] = ((keys[2] >>> 8) ^ ZipCrypto.CRC_TABLE[(keys[2] ^ (keys[1] >>> 24)) & 0xff]) >>> 0;
404
+ }
405
+
406
+ /**
407
+ * Encrypt a single byte
408
+ * Based on zip.js implementation - uses Math.imul for 32-bit integer multiplication
409
+ */
410
+ private encryptByte(keys: number[], byte: number): number {
411
+ const temp = keys[2] | 2;
412
+ // Use Math.imul like zip.js and mask with 0xFF
413
+ return (byte ^ (Math.imul(temp, (temp ^ 1)) >>> 8)) & 0xFF;
414
+ }
415
+
416
+ /**
417
+ * Decrypt a single byte
418
+ * Based on zip.js implementation - uses Math.imul for 32-bit integer multiplication
419
+ */
420
+ private decryptByte(keys: number[], encryptedByte: number): number {
421
+ const temp = keys[2] | 2;
422
+ // Use Math.imul like zip.js and mask with 0xFF
423
+ return (encryptedByte ^ (Math.imul(temp, (temp ^ 1)) >>> 8)) & 0xFF;
424
+ }
425
+
426
+ /**
427
+ * CRC32 lookup table (shared for all CRC32 operations)
428
+ */
429
+ static readonly CRC_TABLE = [
430
+ 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
431
+ 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
432
+ 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
433
+ 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
434
+ 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
435
+ 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
436
+ 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
437
+ 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
438
+ 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
439
+ 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
440
+ 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
441
+ 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
442
+ 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
443
+ 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
444
+ 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
445
+ 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
446
+ 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
447
+ 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
448
+ 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
449
+ 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
450
+ 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
451
+ 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
452
+ 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
453
+ 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
454
+ 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
455
+ 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
456
+ 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
457
+ 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
458
+ 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
459
+ 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
460
+ 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
461
+ 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
462
+ 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
463
+ 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
464
+ 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
465
+ 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
466
+ 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
467
+ 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
468
+ 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
469
+ 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
470
+ 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
471
+ 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
472
+ 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
473
+ ];
474
+
475
+ /**
476
+ * CRC32 implementation for ZIP crypto (single byte update)
477
+ * Used internally for key updates during encryption/decryption
478
+ */
479
+ private crc32(crc: number, byte: number): number {
480
+ return ((crc >>> 8) ^ ZipCrypto.CRC_TABLE[(crc ^ byte) & 0xff]) >>> 0;
481
+ }
482
+
483
+ /**
484
+ * Static CRC32 calculation for a full buffer
485
+ * Delegates to exported crc32() function
486
+ * @param buf - Buffer or string to calculate CRC32 for
487
+ * @returns CRC32 checksum as unsigned 32-bit integer
488
+ */
489
+ static crc32(buf: Buffer | string): number {
490
+ return crc32Impl(buf);
491
+ }
492
+
493
+ /**
494
+ * Static CRC32 incremental update (single byte)
495
+ * Delegates to exported crc32update() function
496
+ * @param crc - Current CRC32 value
497
+ * @param byte - Byte to update with
498
+ * @returns Updated CRC32 value
499
+ */
500
+ static crc32update(crc: number, byte: number): number {
501
+ return crc32updateImpl(crc, byte);
502
+ }
503
+ }
504
+
505
+ /**
506
+ * CRC32 calculation for a full buffer
507
+ * Main public API for CRC32 calculation
508
+ * @param buf - Buffer or string to calculate CRC32 for
509
+ * @returns CRC32 checksum as unsigned 32-bit integer
510
+ */
511
+ function crc32Impl(buf: Buffer | string): number {
512
+ if (typeof buf === "string") {
513
+ buf = Buffer.from(buf, "utf8");
514
+ }
515
+
516
+ let len = buf.length;
517
+ let crc = ~0;
518
+ for (let off = 0; off < len; ) {
519
+ crc = ZipCrypto.CRC_TABLE[(crc ^ buf[off++]) & 0xff] ^ (crc >>> 8);
520
+ }
521
+
522
+ // Finalize: xor with ~0 and cast as uint32
523
+ return ~crc >>> 0;
524
+ }
525
+
526
+ /**
527
+ * CRC32 incremental update (single byte)
528
+ * Used for streaming CRC32 calculation
529
+ * @param crc - Current CRC32 value
530
+ * @param byte - Byte to update with
531
+ * @returns Updated CRC32 value
532
+ */
533
+ function crc32updateImpl(crc: number, byte: number): number {
534
+ return ZipCrypto.CRC_TABLE[(crc ^ byte) & 0xff] ^ (crc >>> 8);
535
+ }
536
+
537
+ /**
538
+ * CRC32 calculation for a full buffer
539
+ * Main public API for CRC32 calculation
540
+ * @param buf - Buffer or string to calculate CRC32 for
541
+ * @returns CRC32 checksum as unsigned 32-bit integer
542
+ */
543
+ export function crc32(buf: Buffer | string): number {
544
+ return crc32Impl(buf);
545
+ }
546
+
547
+ /**
548
+ * CRC32 incremental update (single byte)
549
+ * Used for streaming CRC32 calculation
550
+ * @param crc - Current CRC32 value
551
+ * @param byte - Byte to update with
552
+ * @returns Updated CRC32 value
553
+ */
554
+ export function crc32update(crc: number, byte: number): number {
555
+ return crc32updateImpl(crc, byte);
556
+ }
557
+
558
+ // Cryptographic utility functions
559
+ import { createHash } from 'crypto';
560
+
561
+ // Return the SHA256 hash of a buffer
562
+ export function sha256(data: Buffer): string {
563
+ const hash = createHash('sha256');
564
+ return hash
565
+ .update(data)
566
+ .digest('hex');
567
+ }
568
+
569
+ /**
570
+ * Options for creating a DecryptionStream
571
+ */
572
+ export interface DecryptionStreamOptions {
573
+ password: string;
574
+ method: EncryptionMethod;
575
+ entry: ZipEntry;
576
+ }
577
+
578
+ /**
579
+ * Streaming decryption for chunked compressed data processing
580
+ * Encapsulated within ZipCrypto for PKZIP decryption
581
+ */
582
+ export class DecryptionStream {
583
+ private password: string;
584
+ private method: EncryptionMethod;
585
+ private entry: ZipEntry;
586
+ private zipCrypto: ZipCrypto;
587
+
588
+ constructor(options: DecryptionStreamOptions) {
589
+ this.password = options.password;
590
+ this.method = options.method;
591
+ this.entry = options.entry;
592
+ this.zipCrypto = new ZipCrypto();
593
+ }
594
+
595
+ /**
596
+ * Decrypt compressed data stream chunk by chunk
597
+ */
598
+ async *decrypt(encryptedStream: AsyncGenerator<Buffer>): AsyncGenerator<Buffer> {
599
+ if (this.method !== EncryptionMethod.ZIP_CRYPTO) {
600
+ throw new Error(`Unsupported encryption method: ${this.method}`);
601
+ }
602
+
603
+ // Calculate check byte based on entry
604
+ const checkByte = this.calculateCheckByte();
605
+
606
+ // Use ZipCrypto's streaming decryptor
607
+ yield* this.zipCrypto.createStreamDecryptor(this.password, checkByte, encryptedStream);
608
+ }
609
+
610
+ /**
611
+ * Calculate check byte for password verification
612
+ * For DATA_DESC files: lastModTimeDate >> 8
613
+ * For non-DATA_DESC files: CRC >> 24
614
+ */
615
+ private calculateCheckByte(): number {
616
+ if (this.entry.bitFlags & GP_FLAG.DATA_DESC) {
617
+ let lastModTimeDate = (this.entry as any).lastModTimeDate;
618
+ if (!lastModTimeDate || lastModTimeDate === 0) {
619
+ lastModTimeDate = this.entry.timeDateDOS || 0;
620
+ }
621
+ const checkByte = (lastModTimeDate >> 8) & 0xff;
622
+ return checkByte;
623
+ } else {
624
+ const crc = (this.entry as any).localCrc && (this.entry as any).localCrc !== 0
625
+ ? (this.entry as any).localCrc
626
+ : this.entry.crc;
627
+ const checkByte = (crc >> 24) & 0xff;
628
+ return checkByte;
629
+ }
630
+ }
631
+
632
+ /**
633
+ * Prepare entry for decryption by parsing local header from file handle
634
+ * This extracts the information needed for check byte calculation
635
+ * @param fileHandle - File handle to read from
636
+ * @param entry - ZIP entry to update
637
+ * @returns Check byte value for password verification
638
+ */
639
+ static async prepareEntryForDecryption(fileHandle: any, entry: ZipEntry): Promise<number> {
640
+ // Read local header to get lastModTimeDate for DATA_DESC files (needed for check byte)
641
+ const localHeaderBuffer = Buffer.alloc(30);
642
+ await fileHandle.read(localHeaderBuffer, 0, 30, entry.localHdrOffset);
643
+
644
+ // Use ZipCrypto to parse the header
645
+ const zipCrypto = new ZipCrypto();
646
+ const { checkByte } = zipCrypto.parseLocalHeaderForDecryption(localHeaderBuffer, entry);
647
+
648
+ return checkByte;
649
+ }
650
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * NeoZipKit Encryption Extension
3
+ * Provides encryption capabilities for neozip-cli
4
+ * Supports traditional ZIP encryption (PKZIP compatible)
5
+ */
6
+
7
+ export { ZipCrypto, crc32, crc32update } from './ZipCrypto';
8
+ export { EncryptionManager } from './Manager';
9
+ export {
10
+ EncryptionMethod,
11
+ EncryptionOptions,
12
+ EncryptionResult,
13
+ DecryptionResult,
14
+ EncryptionProvider
15
+ } from './types';
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Encryption types and interfaces for NeoZipKit extension
3
+ */
4
+
5
+ export enum EncryptionMethod {
6
+ NONE = 0,
7
+ ZIP_CRYPTO = 1
8
+ }
9
+
10
+ export interface EncryptionOptions {
11
+ method: EncryptionMethod;
12
+ password: string;
13
+ }
14
+
15
+ export interface EncryptionResult {
16
+ success: boolean;
17
+ encryptedData?: Buffer;
18
+ error?: string;
19
+ }
20
+
21
+ export interface DecryptionResult {
22
+ success: boolean;
23
+ decryptedData?: Buffer;
24
+ error?: string;
25
+ }
26
+
27
+ export interface EncryptionProvider {
28
+ encrypt(data: Buffer, options: EncryptionOptions): Promise<EncryptionResult>;
29
+ decrypt(data: Buffer, options: EncryptionOptions): Promise<DecryptionResult>;
30
+ canHandle(method: EncryptionMethod): boolean;
31
+ getMethodName(): string;
32
+ getKeyLength(): number;
33
+ }