cyberchef 11.2.0 → 11.3.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 (134) hide show
  1. package/AGENTS.md +75 -0
  2. package/CHANGELOG.md +147 -1
  3. package/Dockerfile +2 -2
  4. package/Gruntfile.js +1 -0
  5. package/package.json +22 -21
  6. package/src/core/Dish.mjs +1 -5
  7. package/src/core/Ingredient.mjs +92 -0
  8. package/src/core/Operation.mjs +19 -0
  9. package/src/core/Recipe.mjs +4 -0
  10. package/src/core/Utils.mjs +48 -0
  11. package/src/core/config/Categories.json +22 -6
  12. package/src/core/config/OperationConfig.json +4771 -1226
  13. package/src/core/config/modules/Ciphers.mjs +20 -0
  14. package/src/core/config/modules/Crypto.mjs +10 -0
  15. package/src/core/config/modules/Default.mjs +8 -0
  16. package/src/core/config/modules/File.mjs +16 -0
  17. package/src/core/config/modules/OpModules.mjs +2 -0
  18. package/src/core/config/scripts/generateHTMLEntities.mjs +139 -0
  19. package/src/core/config/scripts/htmlEntityOverrides.mjs +86 -0
  20. package/src/core/lib/Arithmetic.mjs +21 -5
  21. package/src/core/lib/BigIntUtils.mjs +0 -1
  22. package/src/core/lib/COBS.mjs +86 -0
  23. package/src/core/lib/Decimal.mjs +5 -5
  24. package/src/core/lib/HTMLEntities.mjs +2068 -0
  25. package/src/core/lib/Present.mjs +422 -0
  26. package/src/core/lib/TEA.mjs +494 -0
  27. package/src/core/lib/TLVParser.mjs +16 -7
  28. package/src/core/lib/Twofish.mjs +608 -0
  29. package/src/core/operations/AsconDecrypt.mjs +112 -0
  30. package/src/core/operations/AsconEncrypt.mjs +108 -0
  31. package/src/core/operations/AsconHash.mjs +49 -0
  32. package/src/core/operations/AsconMAC.mjs +68 -0
  33. package/src/core/operations/AutomatedValidationTestOp.mjs +84 -0
  34. package/src/core/operations/AvroToJSON.mjs +1 -1
  35. package/src/core/operations/BLAKE3.mjs +5 -1
  36. package/src/core/operations/BcryptCompare.mjs +11 -5
  37. package/src/core/operations/BitShiftLeft.mjs +4 -1
  38. package/src/core/operations/Bzip2Compress.mjs +1 -1
  39. package/src/core/operations/DechunkHTTPResponse.mjs +4 -1
  40. package/src/core/operations/ExtendedGCD.mjs +101 -0
  41. package/src/core/operations/FromBase.mjs +2 -1
  42. package/src/core/operations/FromCOBS.mjs +38 -0
  43. package/src/core/operations/FromDecimal.mjs +6 -1
  44. package/src/core/operations/FromHTMLEntity.mjs +2 -1458
  45. package/src/core/operations/GenerateHOTP.mjs +21 -6
  46. package/src/core/operations/GenerateImage.mjs +22 -10
  47. package/src/core/operations/GeneratePrime.mjs +154 -0
  48. package/src/core/operations/GenerateTOTP.mjs +24 -7
  49. package/src/core/operations/Gzip.mjs +1 -3
  50. package/src/core/operations/Jsonata.mjs +12 -0
  51. package/src/core/operations/MIMEDecoding.mjs +1 -1
  52. package/src/core/operations/MOD.mjs +62 -0
  53. package/src/core/operations/ModularInverse.mjs +107 -0
  54. package/src/core/operations/PRESENTDecrypt.mjs +94 -0
  55. package/src/core/operations/PRESENTEncrypt.mjs +94 -0
  56. package/src/core/operations/ParseURI.mjs +18 -5
  57. package/src/core/operations/PseudoRandomNumberGenerator.mjs +2 -1
  58. package/src/core/operations/RandomPrime.mjs +154 -0
  59. package/src/core/operations/RenderPDF.mjs +100 -0
  60. package/src/core/operations/SM4Encrypt.mjs +1 -1
  61. package/src/core/operations/SetDifference.mjs +8 -1
  62. package/src/core/operations/SetIntersection.mjs +8 -1
  63. package/src/core/operations/ShowOnMap.mjs +14 -2
  64. package/src/core/operations/TEADecrypt.mjs +98 -0
  65. package/src/core/operations/TEAEncrypt.mjs +98 -0
  66. package/src/core/operations/ToBase.mjs +4 -4
  67. package/src/core/operations/ToBase32.mjs +20 -4
  68. package/src/core/operations/ToBinary.mjs +4 -1
  69. package/src/core/operations/ToCOBS.mjs +38 -0
  70. package/src/core/operations/ToHTMLEntity.mjs +7 -1430
  71. package/src/core/operations/TwofishDecrypt.mjs +94 -0
  72. package/src/core/operations/TwofishEncrypt.mjs +94 -0
  73. package/src/core/operations/URLEncode.mjs +22 -18
  74. package/src/core/operations/UnescapeUnicodeCharacters.mjs +2 -1
  75. package/src/core/operations/ViewBitPlane.mjs +9 -3
  76. package/src/core/operations/Wrap.mjs +5 -0
  77. package/src/core/operations/XORBruteForce.mjs +4 -1
  78. package/src/core/operations/XORChecksum.mjs +2 -2
  79. package/src/core/operations/XTEADecrypt.mjs +110 -0
  80. package/src/core/operations/XTEAEncrypt.mjs +110 -0
  81. package/src/core/operations/index.mjs +42 -0
  82. package/src/core/vendor/ascon.mjs +162 -0
  83. package/src/core/vendor/htmlEntities/entity.json +2233 -0
  84. package/src/core/vendor/htmlEntities/entity.txt +14 -0
  85. package/src/node/api.mjs +4 -1
  86. package/src/node/index.mjs +105 -0
  87. package/src/web/HTMLOperation.mjs +2 -1
  88. package/src/web/stylesheets/layout/_io.css +8 -0
  89. package/tests/browser/00_nightwatch.js +26 -0
  90. package/tests/browser/02_ops.js +20 -4
  91. package/tests/node/index.mjs +1 -0
  92. package/tests/node/tests/Dish.mjs +19 -0
  93. package/tests/node/tests/NodeDish.mjs +36 -0
  94. package/tests/node/tests/ToHTMLEntity.mjs +82 -0
  95. package/tests/node/tests/Utils.mjs +77 -0
  96. package/tests/node/tests/nodeApi.mjs +33 -1
  97. package/tests/node/tests/operations.mjs +26 -1
  98. package/tests/operations/index.mjs +17 -0
  99. package/tests/operations/tests/Arithmetic.mjs +33 -0
  100. package/tests/operations/tests/Ascon.mjs +501 -0
  101. package/tests/operations/tests/AutomatedValidation.mjs +154 -0
  102. package/tests/operations/tests/BLAKE3.mjs +39 -1
  103. package/tests/operations/tests/Base32.mjs +22 -0
  104. package/tests/operations/tests/COBS.mjs +476 -0
  105. package/tests/operations/tests/CharEnc.mjs +2 -2
  106. package/tests/operations/tests/DechunkHTTPResponse.mjs +66 -0
  107. package/tests/operations/tests/ExtendedGCD.mjs +78 -0
  108. package/tests/operations/tests/FromBase.mjs +66 -0
  109. package/tests/operations/tests/FromDecimal.mjs +55 -0
  110. package/tests/operations/tests/GenerateLoremIpsum.mjs +2 -2
  111. package/tests/operations/tests/Gzip.mjs +60 -0
  112. package/tests/operations/tests/HTMLEntity.mjs +126 -0
  113. package/tests/operations/tests/Hash.mjs +44 -0
  114. package/tests/operations/tests/Hexdump.mjs +1 -1
  115. package/tests/operations/tests/Image.mjs +26 -0
  116. package/tests/operations/tests/Jsonata.mjs +23 -0
  117. package/tests/operations/tests/MIMEDecoding.mjs +33 -0
  118. package/tests/operations/tests/MOD.mjs +208 -0
  119. package/tests/operations/tests/Median.mjs +33 -0
  120. package/tests/operations/tests/ModularInverse.mjs +78 -0
  121. package/tests/operations/tests/OTP.mjs +167 -2
  122. package/tests/operations/tests/PRESENT.mjs +465 -0
  123. package/tests/operations/tests/ParseTLV.mjs +41 -0
  124. package/tests/operations/tests/RenderPDF.mjs +55 -0
  125. package/tests/operations/tests/SM2.mjs +1 -1
  126. package/tests/operations/tests/SetDifference.mjs +22 -0
  127. package/tests/operations/tests/SetIntersection.mjs +23 -1
  128. package/tests/operations/tests/ShowOnMap.mjs +61 -0
  129. package/tests/operations/tests/TEA.mjs +566 -0
  130. package/tests/operations/tests/Twofish.mjs +486 -0
  131. package/tests/operations/tests/URLEncodeDecode.mjs +26 -0
  132. package/tests/operations/tests/UnescapeUnicodeCharacters.mjs +88 -0
  133. package/tests/operations/tests/Wrap.mjs +44 -0
  134. package/webpack.config.js +3 -3
@@ -0,0 +1,422 @@
1
+ /**
2
+ * Complete implementation of PRESENT block cipher encryption/decryption with
3
+ * ECB and CBC block modes.
4
+ *
5
+ * PRESENT is an ultra-lightweight block cipher designed for constrained environments.
6
+ * Standardised in ISO/IEC 29192-2:2019.
7
+ *
8
+ * Reference: "PRESENT: An Ultra-Lightweight Block Cipher"
9
+ * https://link.springer.com/chapter/10.1007/978-3-540-74735-2_31
10
+ *
11
+ * @author Medjedtxm
12
+ * @copyright Crown Copyright 2026
13
+ * @license Apache-2.0
14
+ */
15
+
16
+ import OperationError from "../errors/OperationError.mjs";
17
+
18
+ /** Number of rounds */
19
+ const NROUNDS = 31;
20
+
21
+ /** Block size in bytes (64 bits) */
22
+ const BLOCKSIZE = 8;
23
+
24
+ /** The 4-bit S-box (16 values) */
25
+ const SBOX = [
26
+ 0xC, 0x5, 0x6, 0xB, 0x9, 0x0, 0xA, 0xD,
27
+ 0x3, 0xE, 0xF, 0x8, 0x4, 0x7, 0x1, 0x2
28
+ ];
29
+
30
+ /** Inverse S-box for decryption */
31
+ const SBOX_INV = [
32
+ 0x5, 0xE, 0xF, 0x8, 0xC, 0x1, 0x2, 0xD,
33
+ 0xB, 0x4, 0x6, 0x3, 0x0, 0x7, 0x9, 0xA
34
+ ];
35
+
36
+ /** P-layer permutation table (bit i goes to position P[i]) */
37
+ const PBOX = [
38
+ 0, 16, 32, 48, 1, 17, 33, 49, 2, 18, 34, 50, 3, 19, 35, 51,
39
+ 4, 20, 36, 52, 5, 21, 37, 53, 6, 22, 38, 54, 7, 23, 39, 55,
40
+ 8, 24, 40, 56, 9, 25, 41, 57, 10, 26, 42, 58, 11, 27, 43, 59,
41
+ 12, 28, 44, 60, 13, 29, 45, 61, 14, 30, 46, 62, 15, 31, 47, 63
42
+ ];
43
+
44
+ /** Inverse P-layer permutation for decryption */
45
+ const PBOX_INV = new Array(64);
46
+ for (let i = 0; i < 64; i++) {
47
+ PBOX_INV[PBOX[i]] = i;
48
+ }
49
+
50
+ /**
51
+ * Convert byte array to BigInt (big-endian)
52
+ * @param {number[]} bytes - Array of bytes
53
+ * @returns {bigint} - 64-bit value as BigInt
54
+ */
55
+ function bytesToBigInt(bytes) {
56
+ let result = 0n;
57
+ for (let i = 0; i < bytes.length; i++) {
58
+ result = (result << 8n) | BigInt(bytes[i]);
59
+ }
60
+ return result;
61
+ }
62
+
63
+ /**
64
+ * Convert BigInt to byte array (big-endian)
65
+ * @param {bigint} value - BigInt value
66
+ * @param {number} length - Desired byte array length
67
+ * @returns {number[]} - Array of bytes
68
+ */
69
+ function bigIntToBytes(value, length) {
70
+ const bytes = [];
71
+ for (let i = length - 1; i >= 0; i--) {
72
+ bytes[i] = Number(value & 0xFFn);
73
+ value >>= 8n;
74
+ }
75
+ return bytes;
76
+ }
77
+
78
+ /**
79
+ * Apply S-box substitution layer to 64-bit state
80
+ * @param {bigint} state - 64-bit state
81
+ * @param {number[]} sbox - S-box to use
82
+ * @returns {bigint} - Substituted state
83
+ */
84
+ function sBoxLayer(state, sbox) {
85
+ let result = 0n;
86
+ for (let i = 0; i < 16; i++) {
87
+ const nibble = Number((state >> BigInt(i * 4)) & 0xFn);
88
+ result |= BigInt(sbox[nibble]) << BigInt(i * 4);
89
+ }
90
+ return result;
91
+ }
92
+
93
+ /**
94
+ * Apply P-layer permutation to 64-bit state
95
+ * @param {bigint} state - 64-bit state
96
+ * @param {number[]} pbox - Permutation table to use
97
+ * @returns {bigint} - Permuted state
98
+ */
99
+ function pLayer(state, pbox) {
100
+ let result = 0n;
101
+ for (let i = 0; i < 64; i++) {
102
+ if ((state >> BigInt(i)) & 1n) {
103
+ result |= 1n << BigInt(pbox[i]);
104
+ }
105
+ }
106
+ return result;
107
+ }
108
+
109
+ /**
110
+ * Generate round keys for 80-bit key
111
+ * @param {number[]} key - 10-byte key
112
+ * @returns {bigint[]} - Array of 32 round keys (64-bit each)
113
+ */
114
+ function generateRoundKeys80(key) {
115
+ // Key register is 80 bits
116
+ let keyReg = bytesToBigInt(key);
117
+ const roundKeys = [];
118
+
119
+ for (let i = 1; i <= NROUNDS + 1; i++) {
120
+ // Extract round key (leftmost 64 bits)
121
+ roundKeys.push(keyReg >> 16n);
122
+
123
+ // Rotate left by 61 positions
124
+ keyReg = ((keyReg << 61n) | (keyReg >> 19n)) & ((1n << 80n) - 1n);
125
+
126
+ // Apply S-box to leftmost 4 bits
127
+ const leftNibble = Number(keyReg >> 76n);
128
+ keyReg = (keyReg & ((1n << 76n) - 1n)) | (BigInt(SBOX[leftNibble]) << 76n);
129
+
130
+ // XOR round counter to bits 19-15
131
+ keyReg ^= BigInt(i) << 15n;
132
+ }
133
+
134
+ return roundKeys;
135
+ }
136
+
137
+ /**
138
+ * Generate round keys for 128-bit key
139
+ * @param {number[]} key - 16-byte key
140
+ * @returns {bigint[]} - Array of 32 round keys (64-bit each)
141
+ */
142
+ function generateRoundKeys128(key) {
143
+ // Key register is 128 bits
144
+ let keyReg = bytesToBigInt(key);
145
+ const roundKeys = [];
146
+
147
+ for (let i = 1; i <= NROUNDS + 1; i++) {
148
+ // Extract round key (leftmost 64 bits)
149
+ roundKeys.push(keyReg >> 64n);
150
+
151
+ // Rotate left by 61 positions
152
+ keyReg = ((keyReg << 61n) | (keyReg >> 67n)) & ((1n << 128n) - 1n);
153
+
154
+ // Apply S-box to leftmost 8 bits (two nibbles: bits 127-124 and 123-120)
155
+ const leftByte = Number((keyReg >> 120n) & 0xFFn);
156
+ const leftNibble1 = (leftByte >> 4) & 0xF; // bits 127-124
157
+ const leftNibble2 = leftByte & 0xF; // bits 123-120
158
+ keyReg = (keyReg & ((1n << 120n) - 1n)) |
159
+ (BigInt((SBOX[leftNibble1] << 4) | SBOX[leftNibble2]) << 120n);
160
+
161
+ // XOR round counter to bits 66-62
162
+ keyReg ^= BigInt(i) << 62n;
163
+ }
164
+
165
+ return roundKeys;
166
+ }
167
+
168
+ /**
169
+ * Encrypt a single 64-bit block
170
+ * @param {bigint} block - 64-bit plaintext block
171
+ * @param {bigint[]} roundKeys - Round keys
172
+ * @returns {bigint} - 64-bit ciphertext block
173
+ */
174
+ function encryptBlock(block, roundKeys) {
175
+ let state = block;
176
+
177
+ for (let i = 0; i < NROUNDS; i++) {
178
+ // Add round key
179
+ state ^= roundKeys[i];
180
+ // S-box layer
181
+ state = sBoxLayer(state, SBOX);
182
+ // P-layer
183
+ state = pLayer(state, PBOX);
184
+ }
185
+
186
+ // Final round key addition
187
+ state ^= roundKeys[NROUNDS];
188
+
189
+ return state;
190
+ }
191
+
192
+ /**
193
+ * Decrypt a single 64-bit block
194
+ * @param {bigint} block - 64-bit ciphertext block
195
+ * @param {bigint[]} roundKeys - Round keys
196
+ * @returns {bigint} - 64-bit plaintext block
197
+ */
198
+ function decryptBlock(block, roundKeys) {
199
+ let state = block;
200
+
201
+ // Reverse key addition
202
+ state ^= roundKeys[NROUNDS];
203
+
204
+ for (let i = NROUNDS - 1; i >= 0; i--) {
205
+ // Inverse P-layer
206
+ state = pLayer(state, PBOX_INV);
207
+ // Inverse S-box layer
208
+ state = sBoxLayer(state, SBOX_INV);
209
+ // Add round key
210
+ state ^= roundKeys[i];
211
+ }
212
+
213
+ return state;
214
+ }
215
+
216
+ /**
217
+ * Apply padding to message
218
+ * @param {number[]} message - Original message
219
+ * @param {string} padding - Padding type ("NO", "PKCS5", "ZERO", "RANDOM", "BIT")
220
+ * @param {number} blockSize - Block size in bytes
221
+ * @returns {number[]} - Padded message
222
+ */
223
+ function applyPadding(message, padding, blockSize) {
224
+ const remainder = message.length % blockSize;
225
+ let nPadding = remainder === 0 ? 0 : blockSize - remainder;
226
+
227
+ // For PKCS5, always add at least one byte (full block if already aligned)
228
+ if (padding === "PKCS5" && remainder === 0) {
229
+ nPadding = blockSize;
230
+ }
231
+
232
+ if (nPadding === 0) return [...message];
233
+
234
+ const paddedMessage = [...message];
235
+
236
+ switch (padding) {
237
+ case "NO":
238
+ throw new OperationError(`No padding requested but input is not a ${blockSize}-byte multiple.`);
239
+
240
+ case "PKCS5":
241
+ for (let i = 0; i < nPadding; i++) {
242
+ paddedMessage.push(nPadding);
243
+ }
244
+ break;
245
+
246
+ case "ZERO":
247
+ for (let i = 0; i < nPadding; i++) {
248
+ paddedMessage.push(0);
249
+ }
250
+ break;
251
+
252
+ case "RANDOM":
253
+ for (let i = 0; i < nPadding; i++) {
254
+ paddedMessage.push(Math.floor(Math.random() * 256));
255
+ }
256
+ break;
257
+
258
+ case "BIT":
259
+ paddedMessage.push(0x80);
260
+ for (let i = 1; i < nPadding; i++) {
261
+ paddedMessage.push(0);
262
+ }
263
+ break;
264
+
265
+ default:
266
+ throw new OperationError(`Unknown padding type: ${padding}`);
267
+ }
268
+
269
+ return paddedMessage;
270
+ }
271
+
272
+ /**
273
+ * Remove padding from message
274
+ * @param {number[]} message - Padded message
275
+ * @param {string} padding - Padding type ("NO", "PKCS5", "ZERO", "RANDOM", "BIT")
276
+ * @param {number} blockSize - Block size in bytes
277
+ * @returns {number[]} - Unpadded message
278
+ */
279
+ function removePadding(message, padding, blockSize) {
280
+ if (message.length === 0) return message;
281
+
282
+ switch (padding) {
283
+ case "NO":
284
+ case "ZERO":
285
+ case "RANDOM":
286
+ // These padding types cannot be reliably removed
287
+ return message;
288
+
289
+ case "PKCS5": {
290
+ const padByte = message[message.length - 1];
291
+ if (padByte > 0 && padByte <= blockSize) {
292
+ // Verify padding
293
+ for (let i = 0; i < padByte; i++) {
294
+ if (message[message.length - 1 - i] !== padByte) {
295
+ throw new OperationError("Invalid PKCS#5 padding.");
296
+ }
297
+ }
298
+ return message.slice(0, message.length - padByte);
299
+ }
300
+ throw new OperationError("Invalid PKCS#5 padding.");
301
+ }
302
+
303
+ case "BIT": {
304
+ // Find 0x80 byte working backwards, skipping zeros
305
+ for (let i = message.length - 1; i >= 0; i--) {
306
+ if (message[i] === 0x80) {
307
+ return message.slice(0, i);
308
+ } else if (message[i] !== 0) {
309
+ throw new OperationError("Invalid BIT padding.");
310
+ }
311
+ }
312
+ throw new OperationError("Invalid BIT padding.");
313
+ }
314
+
315
+ default:
316
+ throw new OperationError(`Unknown padding type: ${padding}`);
317
+ }
318
+ }
319
+
320
+ /**
321
+ * Encrypt using PRESENT cipher with specified block mode
322
+ *
323
+ * @param {number[]} message - Plaintext as byte array
324
+ * @param {number[]} key - Key (10 bytes for 80-bit or 16 bytes for 128-bit)
325
+ * @param {number[]} iv - IV (8 bytes, not used for ECB)
326
+ * @param {string} mode - Block cipher mode ("ECB" or "CBC")
327
+ * @param {string} padding - Padding type ("NO", "PKCS5", "ZERO", "RANDOM", "BIT")
328
+ * @returns {number[]} - Ciphertext as byte array
329
+ */
330
+ export function encryptPRESENT(message, key, iv, mode = "ECB", padding = "PKCS5") {
331
+ if (message.length === 0) return [];
332
+
333
+ // Generate round keys based on key length
334
+ const roundKeys = key.length === 10 ?
335
+ generateRoundKeys80(key) :
336
+ generateRoundKeys128(key);
337
+
338
+ // Apply padding
339
+ const paddedMessage = applyPadding(message, padding, BLOCKSIZE);
340
+
341
+ const cipherText = [];
342
+
343
+ switch (mode) {
344
+ case "ECB":
345
+ for (let i = 0; i < paddedMessage.length; i += BLOCKSIZE) {
346
+ const block = bytesToBigInt(paddedMessage.slice(i, i + BLOCKSIZE));
347
+ const encrypted = encryptBlock(block, roundKeys);
348
+ cipherText.push(...bigIntToBytes(encrypted, BLOCKSIZE));
349
+ }
350
+ break;
351
+
352
+ case "CBC": {
353
+ let ivBlock = bytesToBigInt(iv);
354
+ for (let i = 0; i < paddedMessage.length; i += BLOCKSIZE) {
355
+ let block = bytesToBigInt(paddedMessage.slice(i, i + BLOCKSIZE));
356
+ block ^= ivBlock;
357
+ const encrypted = encryptBlock(block, roundKeys);
358
+ cipherText.push(...bigIntToBytes(encrypted, BLOCKSIZE));
359
+ ivBlock = encrypted;
360
+ }
361
+ break;
362
+ }
363
+
364
+ default:
365
+ throw new OperationError(`Invalid block cipher mode: ${mode}`);
366
+ }
367
+
368
+ return cipherText;
369
+ }
370
+
371
+ /**
372
+ * Decrypt using PRESENT cipher with specified block mode
373
+ *
374
+ * @param {number[]} cipherText - Ciphertext as byte array
375
+ * @param {number[]} key - Key (10 bytes for 80-bit or 16 bytes for 128-bit)
376
+ * @param {number[]} iv - IV (8 bytes, not used for ECB)
377
+ * @param {string} mode - Block cipher mode ("ECB" or "CBC")
378
+ * @param {string} padding - Padding type ("NO", "PKCS5", "ZERO", "RANDOM", "BIT")
379
+ * @returns {number[]} - Plaintext as byte array
380
+ */
381
+ export function decryptPRESENT(cipherText, key, iv, mode = "ECB", padding = "PKCS5") {
382
+ if (cipherText.length === 0) return [];
383
+
384
+ if (cipherText.length % BLOCKSIZE !== 0) {
385
+ throw new OperationError(`Invalid ciphertext length: ${cipherText.length} bytes. Must be a multiple of 8.`);
386
+ }
387
+
388
+ // Generate round keys based on key length
389
+ const roundKeys = key.length === 10 ?
390
+ generateRoundKeys80(key) :
391
+ generateRoundKeys128(key);
392
+
393
+ const plainText = [];
394
+
395
+ switch (mode) {
396
+ case "ECB":
397
+ for (let i = 0; i < cipherText.length; i += BLOCKSIZE) {
398
+ const block = bytesToBigInt(cipherText.slice(i, i + BLOCKSIZE));
399
+ const decrypted = decryptBlock(block, roundKeys);
400
+ plainText.push(...bigIntToBytes(decrypted, BLOCKSIZE));
401
+ }
402
+ break;
403
+
404
+ case "CBC": {
405
+ let ivBlock = bytesToBigInt(iv);
406
+ for (let i = 0; i < cipherText.length; i += BLOCKSIZE) {
407
+ const block = bytesToBigInt(cipherText.slice(i, i + BLOCKSIZE));
408
+ let decrypted = decryptBlock(block, roundKeys);
409
+ decrypted ^= ivBlock;
410
+ plainText.push(...bigIntToBytes(decrypted, BLOCKSIZE));
411
+ ivBlock = block;
412
+ }
413
+ break;
414
+ }
415
+
416
+ default:
417
+ throw new OperationError(`Invalid block cipher mode: ${mode}`);
418
+ }
419
+
420
+ // Remove padding
421
+ return removePadding(plainText, padding, BLOCKSIZE);
422
+ }