cyberchef 10.22.1 → 10.24.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 (118) hide show
  1. package/CHANGELOG.md +182 -0
  2. package/CONTRIBUTING.md +40 -0
  3. package/Dockerfile +2 -0
  4. package/Gruntfile.js +2 -28
  5. package/README.md +1 -1
  6. package/babel.config.js +0 -6
  7. package/package.json +64 -63
  8. package/src/core/Chef.mjs +8 -1
  9. package/src/core/Ingredient.mjs +5 -2
  10. package/src/core/Operation.mjs +6 -1
  11. package/src/core/Recipe.mjs +10 -5
  12. package/src/core/config/Categories.json +20 -4
  13. package/src/core/config/OperationConfig.json +550 -26
  14. package/src/core/config/modules/Ciphers.mjs +6 -0
  15. package/src/core/config/modules/Crypto.mjs +6 -0
  16. package/src/core/config/modules/Default.mjs +8 -0
  17. package/src/core/config/modules/Shellcode.mjs +2 -0
  18. package/src/core/config/scripts/fixCryptoApiImports.mjs +54 -0
  19. package/src/core/config/scripts/fixSnackBarMarkup.mjs +28 -0
  20. package/src/core/lib/AudioBytes.mjs +103 -0
  21. package/src/core/lib/AudioMetaSchema.mjs +82 -0
  22. package/src/core/lib/AudioParsers.mjs +630 -0
  23. package/src/core/lib/BigIntUtils.mjs +73 -0
  24. package/src/core/lib/Extract.mjs +5 -0
  25. package/src/core/lib/Modhex.mjs +2 -0
  26. package/src/core/lib/ParityBit.mjs +50 -0
  27. package/src/core/lib/QRCode.mjs +30 -10
  28. package/src/core/lib/RC6.mjs +625 -0
  29. package/src/core/operations/A1Z26CipherDecode.mjs +1 -1
  30. package/src/core/operations/AddTextToImage.mjs +116 -64
  31. package/src/core/operations/AnalyseUUID.mjs +109 -6
  32. package/src/core/operations/BlurImage.mjs +10 -12
  33. package/src/core/operations/ContainImage.mjs +50 -40
  34. package/src/core/operations/ConvertImageFormat.mjs +33 -39
  35. package/src/core/operations/CoverImage.mjs +39 -37
  36. package/src/core/operations/CropImage.mjs +35 -21
  37. package/src/core/operations/DisassembleARM.mjs +193 -0
  38. package/src/core/operations/DitherImage.mjs +8 -8
  39. package/src/core/operations/EscapeUnicodeCharacters.mjs +0 -17
  40. package/src/core/operations/ExtractAudioMetadata.mjs +175 -0
  41. package/src/core/operations/ExtractEmailAddresses.mjs +2 -3
  42. package/src/core/operations/ExtractLSB.mjs +17 -11
  43. package/src/core/operations/ExtractRGBA.mjs +12 -10
  44. package/src/core/operations/FlaskSessionDecode.mjs +80 -0
  45. package/src/core/operations/FlaskSessionSign.mjs +89 -0
  46. package/src/core/operations/FlaskSessionVerify.mjs +136 -0
  47. package/src/core/operations/FlipImage.mjs +14 -10
  48. package/src/core/operations/GenerateImage.mjs +39 -32
  49. package/src/core/operations/ImageBrightnessContrast.mjs +10 -10
  50. package/src/core/operations/ImageFilter.mjs +14 -13
  51. package/src/core/operations/ImageHueSaturationLightness.mjs +22 -20
  52. package/src/core/operations/ImageOpacity.mjs +6 -8
  53. package/src/core/operations/InvertImage.mjs +4 -6
  54. package/src/core/operations/Jq.mjs +12 -4
  55. package/src/core/operations/NormaliseImage.mjs +5 -7
  56. package/src/core/operations/OffsetChecker.mjs +1 -1
  57. package/src/core/operations/ParityBit.mjs +128 -0
  58. package/src/core/operations/ParseEthernetFrame.mjs +112 -0
  59. package/src/core/operations/ParseIPv4Header.mjs +23 -6
  60. package/src/core/operations/ParseQRCode.mjs +13 -13
  61. package/src/core/operations/PseudoRandomIntegerGenerator.mjs +164 -0
  62. package/src/core/operations/RC6Decrypt.mjs +119 -0
  63. package/src/core/operations/RC6Encrypt.mjs +119 -0
  64. package/src/core/operations/RandomizeColourPalette.mjs +11 -11
  65. package/src/core/operations/RegularExpression.mjs +2 -1
  66. package/src/core/operations/RenderMarkdown.mjs +35 -4
  67. package/src/core/operations/ResizeImage.mjs +30 -23
  68. package/src/core/operations/RotateImage.mjs +8 -9
  69. package/src/core/operations/SQLBeautify.mjs +21 -3
  70. package/src/core/operations/SharpenImage.mjs +94 -62
  71. package/src/core/operations/SplitColourChannels.mjs +47 -21
  72. package/src/core/operations/TextIntegerConverter.mjs +123 -0
  73. package/src/core/operations/UnescapeUnicodeCharacters.mjs +17 -0
  74. package/src/core/operations/ViewBitPlane.mjs +16 -20
  75. package/src/core/operations/index.mjs +22 -0
  76. package/src/node/index.mjs +55 -0
  77. package/src/web/HTMLIngredient.mjs +24 -43
  78. package/src/web/HTMLOperation.mjs +8 -2
  79. package/src/web/Manager.mjs +1 -0
  80. package/src/web/html/index.html +6 -6
  81. package/src/web/static/fonts/bmfonts/Roboto72White.fnt +491 -485
  82. package/src/web/static/fonts/bmfonts/RobotoBlack72White.fnt +494 -488
  83. package/src/web/static/fonts/bmfonts/RobotoMono72White.fnt +110 -103
  84. package/src/web/static/fonts/bmfonts/RobotoSlab72White.fnt +498 -492
  85. package/src/web/stylesheets/layout/_banner.css +30 -0
  86. package/src/web/stylesheets/layout/_modals.css +5 -0
  87. package/src/web/stylesheets/utils/_overrides.css +7 -0
  88. package/src/web/waiters/ControlsWaiter.mjs +82 -0
  89. package/src/web/waiters/InputWaiter.mjs +12 -6
  90. package/src/web/waiters/OperationsWaiter.mjs +30 -13
  91. package/src/web/waiters/RecipeWaiter.mjs +2 -2
  92. package/tests/browser/02_ops.js +23 -3
  93. package/tests/node/index.mjs +1 -0
  94. package/tests/node/tests/lib/BigIntUtils.mjs +150 -0
  95. package/tests/node/tests/operations.mjs +11 -10
  96. package/tests/operations/index.mjs +13 -0
  97. package/tests/operations/tests/A1Z26CipherDecode.mjs +33 -0
  98. package/tests/operations/tests/AnalyseUUID.mjs +66 -0
  99. package/tests/operations/tests/DisassembleARM.mjs +377 -0
  100. package/tests/operations/tests/ExtractAudioMetadata.mjs +287 -0
  101. package/tests/operations/tests/ExtractEmailAddresses.mjs +38 -12
  102. package/tests/operations/tests/Fernet.mjs +18 -3
  103. package/tests/operations/tests/FlaskSession.mjs +246 -0
  104. package/tests/operations/tests/GenerateQRCode.mjs +67 -0
  105. package/tests/operations/tests/JWTSign.mjs +83 -8
  106. package/tests/operations/tests/Jq.mjs +32 -0
  107. package/tests/operations/tests/Modhex.mjs +20 -0
  108. package/tests/operations/tests/ParityBit.mjs +147 -0
  109. package/tests/operations/tests/ParseEthernetFrame.mjs +45 -0
  110. package/tests/operations/tests/RC6.mjs +487 -0
  111. package/tests/operations/tests/RegularExpression.mjs +75 -0
  112. package/tests/operations/tests/RenderMarkdown.mjs +110 -0
  113. package/tests/operations/tests/SQLBeautify.mjs +54 -0
  114. package/tests/operations/tests/TextIntegerConverter.mjs +199 -0
  115. package/tests/samples/Audio.mjs +73 -0
  116. package/tests/samples/Images.mjs +0 -12
  117. package/webpack.config.js +10 -7
  118. package/src/core/lib/ImageManipulation.mjs +0 -251
@@ -0,0 +1,625 @@
1
+ /**
2
+ * Complete implementation of RC6 block cipher encryption/decryption with
3
+ * configurable word size (w), rounds (r), and key length (b).
4
+ *
5
+ * RC6 was an AES finalist designed by Ron Rivest, Matt Robshaw, Ray Sidney, and Yiqun Lisa Yin.
6
+ * Reference: https://en.wikipedia.org/wiki/RC6
7
+ * Test Vectors: https://datatracker.ietf.org/doc/html/draft-krovetz-rc6-rc5-vectors-00
8
+ *
9
+ * The P and Q constants are derived from mathematical constants e (Euler's number) and
10
+ * φ (golden ratio) as specified in the IETF draft. Master 256-bit values are scaled to
11
+ * any word size.
12
+ *
13
+ * @author Medjedtxm
14
+ * @copyright Crown Copyright 2026
15
+ * @license Apache-2.0
16
+ */
17
+
18
+ import OperationError from "../errors/OperationError.mjs";
19
+
20
+ /**
21
+ * Master P constant (256-bit) from IETF draft-krovetz-rc6-rc5-vectors-00
22
+ * Derived from Odd((e-2) * 2^256) where e = 2.71828...
23
+ */
24
+ const P_256 = 0xb7e151628aed2a6abf7158809cf4f3c762e7160f38b4da56a784d9045190cfefn;
25
+
26
+ /**
27
+ * Master Q constant (256-bit) from IETF draft-krovetz-rc6-rc5-vectors-00
28
+ * Derived from Odd((φ-1) * 2^256) where φ = 1.61803... (golden ratio)
29
+ */
30
+ const Q_256 = 0x9e3779b97f4a7c15f39cc0605cedc8341082276bf3a27251f86c6a11d0c18e95n;
31
+
32
+ /**
33
+ * Get P constant for given word size by scaling the 256-bit master constant
34
+ * @param {number} w - Word size in bits
35
+ * @returns {bigint} - P constant for word size w
36
+ */
37
+ function getP(w) {
38
+ return (P_256 >> BigInt(256 - w)) | 1n; // Ensure odd
39
+ }
40
+
41
+ /**
42
+ * Get Q constant for given word size by scaling the 256-bit master constant
43
+ * @param {number} w - Word size in bits
44
+ * @returns {bigint} - Q constant for word size w
45
+ */
46
+ function getQ(w) {
47
+ return (Q_256 >> BigInt(256 - w)) | 1n; // Ensure odd
48
+ }
49
+
50
+ /**
51
+ * Get block size in bytes for given word size
52
+ * Block size = 4 words = 4 * (w/8) bytes
53
+ * @param {number} w - Word size in bits
54
+ * @returns {number} - Block size in bytes
55
+ */
56
+ export function getBlockSize(w) {
57
+ return 4 * (w / 8);
58
+ }
59
+
60
+ /**
61
+ * Get recommended number of rounds for given word size
62
+ * @param {number} w - Word size in bits
63
+ * @returns {number} - Recommended rounds
64
+ */
65
+ export function getDefaultRounds(w) {
66
+ if (w <= 16) return 16;
67
+ if (w <= 32) return 20;
68
+ if (w <= 64) return 24;
69
+ return 28;
70
+ }
71
+
72
+ /**
73
+ * Create mask for w-bit word
74
+ * @param {number} w - Word size in bits
75
+ * @returns {bigint} - Mask with w bits set
76
+ */
77
+ function wordMask(w) {
78
+ return (1n << BigInt(w)) - 1n;
79
+ }
80
+
81
+ /**
82
+ * Rotate left for arbitrary word size using BigInt
83
+ * Uses lower lg(w) bits of n for rotation amount (RC6 spec)
84
+ * @param {bigint} x - Value to rotate
85
+ * @param {bigint} n - Rotation amount
86
+ * @param {number} w - Word size in bits
87
+ * @param {bigint} lgMask - Mask for lower lg(w) bits
88
+ * @returns {bigint} - Rotated value
89
+ */
90
+ function ROL(x, n, w, lgMask) {
91
+ const mask = wordMask(w);
92
+ // Mask to lg(w) bits, then mod w for non-power-of-2 word sizes
93
+ // For power-of-2, (n & lgMask) < w always, so mod w is no-op
94
+ const shift = (n & lgMask) % BigInt(w);
95
+ return ((x << shift) | (x >> (BigInt(w) - shift))) & mask;
96
+ }
97
+
98
+ /**
99
+ * Rotate right for arbitrary word size using BigInt
100
+ * Uses lower lg(w) bits of n for rotation amount (RC6 spec)
101
+ * @param {bigint} x - Value to rotate
102
+ * @param {bigint} n - Rotation amount
103
+ * @param {number} w - Word size in bits
104
+ * @param {bigint} lgMask - Mask for lower lg(w) bits
105
+ * @returns {bigint} - Rotated value
106
+ */
107
+ function ROR(x, n, w, lgMask) {
108
+ const mask = wordMask(w);
109
+ // Mask to lg(w) bits, then mod w for non-power-of-2 word sizes
110
+ // For power-of-2, (n & lgMask) < w always, so mod w is no-op
111
+ const shift = (n & lgMask) % BigInt(w);
112
+ return ((x >> shift) | (x << (BigInt(w) - shift))) & mask;
113
+ }
114
+
115
+ /**
116
+ * Convert byte array to word array (little-endian) using BigInt
117
+ * @param {number[]} bytes - Input byte array
118
+ * @param {number} w - Word size in bits
119
+ * @returns {bigint[]} - Array of w-bit words as BigInt
120
+ */
121
+ function bytesToWords(bytes, w) {
122
+ const bytesPerWord = w / 8;
123
+ const words = [];
124
+ for (let i = 0; i < bytes.length; i += bytesPerWord) {
125
+ let word = 0n;
126
+ for (let j = 0; j < bytesPerWord && (i + j) < bytes.length; j++) {
127
+ word |= BigInt(bytes[i + j] || 0) << BigInt(j * 8);
128
+ }
129
+ words.push(word);
130
+ }
131
+ return words;
132
+ }
133
+
134
+ /**
135
+ * Convert word array to byte array (little-endian) using BigInt
136
+ * @param {bigint[]} words - Array of words
137
+ * @param {number} w - Word size in bits
138
+ * @returns {number[]} - Output byte array
139
+ */
140
+ function wordsToBytes(words, w) {
141
+ const bytesPerWord = w / 8;
142
+ const bytes = [];
143
+ for (const word of words) {
144
+ for (let j = 0; j < bytesPerWord; j++) {
145
+ bytes.push(Number((word >> BigInt(j * 8)) & 0xFFn));
146
+ }
147
+ }
148
+ return bytes;
149
+ }
150
+
151
+ /**
152
+ * Generate round subkeys from user key
153
+ *
154
+ * @param {number[]} key - User key as byte array
155
+ * @param {number} rounds - Number of rounds
156
+ * @param {number} w - Word size in bits
157
+ * @returns {bigint[]} - Array of 2r+4 subkeys as BigInt
158
+ */
159
+ function generateSubkeys(key, rounds, w) {
160
+ const bytesPerWord = w / 8;
161
+ const b = key.length;
162
+ const c = Math.max(Math.ceil(b / bytesPerWord), 1);
163
+
164
+ // Convert key bytes to words, pad with zeros if needed
165
+ const paddedKey = [...key];
166
+ while (paddedKey.length < c * bytesPerWord) {
167
+ paddedKey.push(0);
168
+ }
169
+ const L = bytesToWords(paddedKey, w);
170
+
171
+ // Number of subkeys: 2*r + 4
172
+ const t = 2 * rounds + 4;
173
+
174
+ // Get P and Q for this word size
175
+ const P = getP(w);
176
+ const Q = getQ(w);
177
+ const mask = wordMask(w);
178
+
179
+ // lg(w) mask for rotation amounts (floor of log2(w), per RC6 spec)
180
+ const lgw = Math.floor(Math.log2(w));
181
+ const lgMask = (1n << BigInt(lgw)) - 1n;
182
+
183
+ // Initialise S array with magic constants
184
+ const S = new Array(t);
185
+ S[0] = P;
186
+ for (let i = 1; i < t; i++) {
187
+ S[i] = (S[i - 1] + Q) & mask;
188
+ }
189
+
190
+ // Mix key into S
191
+ let A = 0n, B = 0n;
192
+ let i = 0, j = 0;
193
+ const v = 3 * Math.max(c, t);
194
+
195
+ for (let s = 0; s < v; s++) {
196
+ A = S[i] = ROL((S[i] + A + B) & mask, 3n, w, lgMask);
197
+ B = L[j] = ROL((L[j] + A + B) & mask, A + B, w, lgMask);
198
+ i = (i + 1) % t;
199
+ j = (j + 1) % c;
200
+ }
201
+
202
+ return S;
203
+ }
204
+
205
+ /**
206
+ * Encrypt a single block using RC6
207
+ *
208
+ * @param {number[]} block - Plaintext block (4*w/8 bytes)
209
+ * @param {bigint[]} S - Subkeys array
210
+ * @param {number} rounds - Number of rounds
211
+ * @param {number} w - Word size in bits
212
+ * @returns {number[]} - Ciphertext block
213
+ */
214
+ function encryptBlock(block, S, rounds, w) {
215
+ const mask = wordMask(w);
216
+ const lgw = BigInt(Math.floor(Math.log2(w)));
217
+ const lgMask = (1n << lgw) - 1n;
218
+
219
+ // Convert block to 4 words (A, B, C, D)
220
+ let [A, B, C, D] = bytesToWords(block, w);
221
+
222
+ // Pre-whitening
223
+ B = (B + S[0]) & mask;
224
+ D = (D + S[1]) & mask;
225
+
226
+ // Main rounds
227
+ for (let i = 1; i <= rounds; i++) {
228
+ // t = ROL(B * (2B + 1), lg(w))
229
+ const t = ROL((B * ((2n * B + 1n) & mask)) & mask, lgw, w, lgMask);
230
+
231
+ // u = ROL(D * (2D + 1), lg(w))
232
+ const u = ROL((D * ((2n * D + 1n) & mask)) & mask, lgw, w, lgMask);
233
+
234
+ // A = ROL(A ^ t, u) + S[2i]
235
+ A = (ROL(A ^ t, u, w, lgMask) + S[2 * i]) & mask;
236
+
237
+ // C = ROL(C ^ u, t) + S[2i + 1]
238
+ C = (ROL(C ^ u, t, w, lgMask) + S[2 * i + 1]) & mask;
239
+
240
+ // Rotate registers: (A, B, C, D) = (B, C, D, A)
241
+ const temp = A;
242
+ A = B;
243
+ B = C;
244
+ C = D;
245
+ D = temp;
246
+ }
247
+
248
+ // Post-whitening
249
+ A = (A + S[2 * rounds + 2]) & mask;
250
+ C = (C + S[2 * rounds + 3]) & mask;
251
+
252
+ // Convert words back to bytes
253
+ return wordsToBytes([A, B, C, D], w);
254
+ }
255
+
256
+ /**
257
+ * Decrypt a single block using RC6
258
+ *
259
+ * @param {number[]} block - Ciphertext block (4*w/8 bytes)
260
+ * @param {bigint[]} S - Subkeys array
261
+ * @param {number} rounds - Number of rounds
262
+ * @param {number} w - Word size in bits
263
+ * @returns {number[]} - Plaintext block
264
+ */
265
+ function decryptBlock(block, S, rounds, w) {
266
+ const mask = wordMask(w);
267
+ const lgw = BigInt(Math.floor(Math.log2(w)));
268
+ const lgMask = (1n << lgw) - 1n;
269
+
270
+ // Convert block to 4 words (A, B, C, D)
271
+ let [A, B, C, D] = bytesToWords(block, w);
272
+
273
+ // Reverse post-whitening
274
+ C = (C - S[2 * rounds + 3] + (1n << BigInt(w))) & mask;
275
+ A = (A - S[2 * rounds + 2] + (1n << BigInt(w))) & mask;
276
+
277
+ // Main rounds in reverse
278
+ for (let i = rounds; i >= 1; i--) {
279
+ // Reverse rotate registers: (A, B, C, D) = (D, A, B, C)
280
+ const temp = D;
281
+ D = C;
282
+ C = B;
283
+ B = A;
284
+ A = temp;
285
+
286
+ // u = ROL(D * (2D + 1), lg(w))
287
+ const u = ROL((D * ((2n * D + 1n) & mask)) & mask, lgw, w, lgMask);
288
+
289
+ // t = ROL(B * (2B + 1), lg(w))
290
+ const t = ROL((B * ((2n * B + 1n) & mask)) & mask, lgw, w, lgMask);
291
+
292
+ // C = ROR(C - S[2i + 1], t) ^ u
293
+ C = ROR((C - S[2 * i + 1] + (1n << BigInt(w))) & mask, t, w, lgMask) ^ u;
294
+
295
+ // A = ROR(A - S[2i], u) ^ t
296
+ A = ROR((A - S[2 * i] + (1n << BigInt(w))) & mask, u, w, lgMask) ^ t;
297
+ }
298
+
299
+ // Reverse pre-whitening
300
+ D = (D - S[1] + (1n << BigInt(w))) & mask;
301
+ B = (B - S[0] + (1n << BigInt(w))) & mask;
302
+
303
+ // Convert words back to bytes
304
+ return wordsToBytes([A, B, C, D], w);
305
+ }
306
+
307
+ /**
308
+ * XOR two blocks
309
+ * @param {number[]} a - First block
310
+ * @param {number[]} b - Second block
311
+ * @returns {number[]} - XOR result
312
+ */
313
+ function xorBlocks(a, b) {
314
+ const result = new Array(a.length);
315
+ for (let i = 0; i < a.length; i++) {
316
+ result[i] = a[i] ^ b[i];
317
+ }
318
+ return result;
319
+ }
320
+
321
+ /**
322
+ * Increment counter (little-endian)
323
+ * @param {number[]} counter - Counter block
324
+ * @returns {number[]} - Incremented counter
325
+ */
326
+ function incrementCounter(counter) {
327
+ const result = [...counter];
328
+ for (let i = 0; i < result.length; i++) {
329
+ result[i]++;
330
+ if (result[i] <= 255) break;
331
+ result[i] = 0;
332
+ }
333
+ return result;
334
+ }
335
+
336
+ /**
337
+ * Apply padding to message
338
+ * @param {number[]} message - Original message
339
+ * @param {string} padding - Padding type ("NO", "PKCS5", "ZERO", "RANDOM", "BIT")
340
+ * @param {number} blockSize - Block size in bytes
341
+ * @returns {number[]} - Padded message
342
+ */
343
+ function applyPadding(message, padding, blockSize) {
344
+ const remainder = message.length % blockSize;
345
+ let nPadding = remainder === 0 ? 0 : blockSize - remainder;
346
+
347
+ // For PKCS5, always add at least one byte (full block if already aligned)
348
+ if (padding === "PKCS5" && remainder === 0) {
349
+ nPadding = blockSize;
350
+ }
351
+
352
+ if (nPadding === 0) return [...message];
353
+
354
+ const paddedMessage = [...message];
355
+
356
+ switch (padding) {
357
+ case "NO":
358
+ throw new OperationError(`No padding requested but input is not a ${blockSize}-byte multiple.`);
359
+
360
+ case "PKCS5":
361
+ for (let i = 0; i < nPadding; i++) {
362
+ paddedMessage.push(nPadding);
363
+ }
364
+ break;
365
+
366
+ case "ZERO":
367
+ for (let i = 0; i < nPadding; i++) {
368
+ paddedMessage.push(0);
369
+ }
370
+ break;
371
+
372
+ case "RANDOM":
373
+ for (let i = 0; i < nPadding; i++) {
374
+ paddedMessage.push(Math.floor(Math.random() * 256));
375
+ }
376
+ break;
377
+
378
+ case "BIT":
379
+ paddedMessage.push(0x80);
380
+ for (let i = 1; i < nPadding; i++) {
381
+ paddedMessage.push(0);
382
+ }
383
+ break;
384
+
385
+ default:
386
+ throw new OperationError(`Unknown padding type: ${padding}`);
387
+ }
388
+
389
+ return paddedMessage;
390
+ }
391
+
392
+ /**
393
+ * Remove padding from message
394
+ * @param {number[]} message - Padded message
395
+ * @param {string} padding - Padding type ("NO", "PKCS5", "ZERO", "RANDOM", "BIT")
396
+ * @param {number} blockSize - Block size in bytes
397
+ * @returns {number[]} - Unpadded message
398
+ */
399
+ function removePadding(message, padding, blockSize) {
400
+ if (message.length === 0) return message;
401
+
402
+ switch (padding) {
403
+ case "NO":
404
+ case "ZERO":
405
+ case "RANDOM":
406
+ // These padding types cannot be reliably removed
407
+ return message;
408
+
409
+ case "PKCS5": {
410
+ const padByte = message[message.length - 1];
411
+ if (padByte > 0 && padByte <= blockSize) {
412
+ // Verify padding
413
+ for (let i = 0; i < padByte; i++) {
414
+ if (message[message.length - 1 - i] !== padByte) {
415
+ throw new OperationError("Invalid PKCS#5 padding.");
416
+ }
417
+ }
418
+ return message.slice(0, message.length - padByte);
419
+ }
420
+ throw new OperationError("Invalid PKCS#5 padding.");
421
+ }
422
+
423
+ case "BIT": {
424
+ // Find 0x80 byte working backwards, skipping zeros
425
+ for (let i = message.length - 1; i >= 0; i--) {
426
+ if (message[i] === 0x80) {
427
+ return message.slice(0, i);
428
+ } else if (message[i] !== 0) {
429
+ throw new OperationError("Invalid BIT padding.");
430
+ }
431
+ }
432
+ throw new OperationError("Invalid BIT padding.");
433
+ }
434
+
435
+ default:
436
+ throw new OperationError(`Unknown padding type: ${padding}`);
437
+ }
438
+ }
439
+
440
+ /**
441
+ * Encrypt using RC6 cipher with specified block mode
442
+ *
443
+ * @param {number[]} message - Plaintext as byte array
444
+ * @param {number[]} key - Key as byte array
445
+ * @param {number[]} iv - IV (block size bytes, not used for ECB)
446
+ * @param {string} mode - Block cipher mode ("ECB", "CBC", "CFB", "OFB", "CTR")
447
+ * @param {string} padding - Padding type ("NO", "PKCS5", "ZERO", "RANDOM", "BIT")
448
+ * @param {number} rounds - Number of rounds (default: 20)
449
+ * @param {number} w - Word size in bits (default: 32)
450
+ * @returns {number[]} - Ciphertext as byte array
451
+ */
452
+ export function encryptRC6(message, key, iv, mode = "ECB", padding = "PKCS5", rounds = 20, w = 32) {
453
+ const blockSize = getBlockSize(w);
454
+ const messageLength = message.length;
455
+ if (messageLength === 0) return [];
456
+
457
+ const S = generateSubkeys(key, rounds, w);
458
+
459
+ // Apply padding for ECB/CBC modes
460
+ let paddedMessage;
461
+ if (mode === "ECB" || mode === "CBC") {
462
+ paddedMessage = applyPadding(message, padding, blockSize);
463
+ } else {
464
+ // Stream modes (CFB, OFB, CTR) don't need padding
465
+ paddedMessage = [...message];
466
+ }
467
+
468
+ const cipherText = [];
469
+
470
+ switch (mode) {
471
+ case "ECB":
472
+ for (let i = 0; i < paddedMessage.length; i += blockSize) {
473
+ const block = paddedMessage.slice(i, i + blockSize);
474
+ cipherText.push(...encryptBlock(block, S, rounds, w));
475
+ }
476
+ break;
477
+
478
+ case "CBC": {
479
+ let ivBlock = [...iv];
480
+ for (let i = 0; i < paddedMessage.length; i += blockSize) {
481
+ const block = paddedMessage.slice(i, i + blockSize);
482
+ const xored = xorBlocks(block, ivBlock);
483
+ ivBlock = encryptBlock(xored, S, rounds, w);
484
+ cipherText.push(...ivBlock);
485
+ }
486
+ break;
487
+ }
488
+
489
+ case "CFB": {
490
+ let ivBlock = [...iv];
491
+ for (let i = 0; i < paddedMessage.length; i += blockSize) {
492
+ const encrypted = encryptBlock(ivBlock, S, rounds, w);
493
+ const block = paddedMessage.slice(i, i + blockSize);
494
+ // Pad block if shorter than blockSize
495
+ while (block.length < blockSize) block.push(0);
496
+ ivBlock = xorBlocks(encrypted, block);
497
+ cipherText.push(...ivBlock);
498
+ }
499
+ return cipherText.slice(0, messageLength);
500
+ }
501
+
502
+ case "OFB": {
503
+ let ivBlock = [...iv];
504
+ for (let i = 0; i < paddedMessage.length; i += blockSize) {
505
+ ivBlock = encryptBlock(ivBlock, S, rounds, w);
506
+ const block = paddedMessage.slice(i, i + blockSize);
507
+ // Pad block if shorter than blockSize
508
+ while (block.length < blockSize) block.push(0);
509
+ cipherText.push(...xorBlocks(ivBlock, block));
510
+ }
511
+ return cipherText.slice(0, messageLength);
512
+ }
513
+
514
+ case "CTR": {
515
+ let counter = [...iv];
516
+ for (let i = 0; i < paddedMessage.length; i += blockSize) {
517
+ const encrypted = encryptBlock(counter, S, rounds, w);
518
+ const block = paddedMessage.slice(i, i + blockSize);
519
+ // Pad block if shorter than blockSize
520
+ while (block.length < blockSize) block.push(0);
521
+ cipherText.push(...xorBlocks(encrypted, block));
522
+ counter = incrementCounter(counter);
523
+ }
524
+ return cipherText.slice(0, messageLength);
525
+ }
526
+
527
+ default:
528
+ throw new OperationError(`Invalid block cipher mode: ${mode}`);
529
+ }
530
+
531
+ return cipherText;
532
+ }
533
+
534
+ /**
535
+ * Decrypt using RC6 cipher with specified block mode
536
+ *
537
+ * @param {number[]} cipherText - Ciphertext as byte array
538
+ * @param {number[]} key - Key as byte array
539
+ * @param {number[]} iv - IV (block size bytes, not used for ECB)
540
+ * @param {string} mode - Block cipher mode ("ECB", "CBC", "CFB", "OFB", "CTR")
541
+ * @param {string} padding - Padding type ("NO", "PKCS5", "ZERO", "RANDOM", "BIT")
542
+ * @param {number} rounds - Number of rounds (default: 20)
543
+ * @param {number} w - Word size in bits (default: 32)
544
+ * @returns {number[]} - Plaintext as byte array
545
+ */
546
+ export function decryptRC6(cipherText, key, iv, mode = "ECB", padding = "PKCS5", rounds = 20, w = 32) {
547
+ const blockSize = getBlockSize(w);
548
+ const originalLength = cipherText.length;
549
+ if (originalLength === 0) return [];
550
+
551
+ const S = generateSubkeys(key, rounds, w);
552
+
553
+ if (mode === "ECB" || mode === "CBC") {
554
+ if ((originalLength % blockSize) !== 0)
555
+ throw new OperationError(`Invalid ciphertext length: ${originalLength} bytes. Must be a multiple of ${blockSize}.`);
556
+ } else {
557
+ // Pad for stream modes
558
+ while ((cipherText.length % blockSize) !== 0)
559
+ cipherText.push(0);
560
+ }
561
+
562
+ const plainText = [];
563
+
564
+ switch (mode) {
565
+ case "ECB":
566
+ for (let i = 0; i < cipherText.length; i += blockSize) {
567
+ const block = cipherText.slice(i, i + blockSize);
568
+ plainText.push(...decryptBlock(block, S, rounds, w));
569
+ }
570
+ break;
571
+
572
+ case "CBC": {
573
+ let ivBlock = [...iv];
574
+ for (let i = 0; i < cipherText.length; i += blockSize) {
575
+ const block = cipherText.slice(i, i + blockSize);
576
+ const decrypted = decryptBlock(block, S, rounds, w);
577
+ plainText.push(...xorBlocks(decrypted, ivBlock));
578
+ ivBlock = block;
579
+ }
580
+ break;
581
+ }
582
+
583
+ case "CFB": {
584
+ let ivBlock = [...iv];
585
+ for (let i = 0; i < cipherText.length; i += blockSize) {
586
+ const encrypted = encryptBlock(ivBlock, S, rounds, w);
587
+ const block = cipherText.slice(i, i + blockSize);
588
+ plainText.push(...xorBlocks(encrypted, block));
589
+ ivBlock = block;
590
+ }
591
+ return plainText.slice(0, originalLength);
592
+ }
593
+
594
+ case "OFB": {
595
+ let ivBlock = [...iv];
596
+ for (let i = 0; i < cipherText.length; i += blockSize) {
597
+ ivBlock = encryptBlock(ivBlock, S, rounds, w);
598
+ const block = cipherText.slice(i, i + blockSize);
599
+ plainText.push(...xorBlocks(ivBlock, block));
600
+ }
601
+ return plainText.slice(0, originalLength);
602
+ }
603
+
604
+ case "CTR": {
605
+ let counter = [...iv];
606
+ for (let i = 0; i < cipherText.length; i += blockSize) {
607
+ const encrypted = encryptBlock(counter, S, rounds, w);
608
+ const block = cipherText.slice(i, i + blockSize);
609
+ plainText.push(...xorBlocks(encrypted, block));
610
+ counter = incrementCounter(counter);
611
+ }
612
+ return plainText.slice(0, originalLength);
613
+ }
614
+
615
+ default:
616
+ throw new OperationError(`Invalid block cipher mode: ${mode}`);
617
+ }
618
+
619
+ // Remove padding for ECB/CBC modes
620
+ if (mode === "ECB" || mode === "CBC") {
621
+ return removePadding(plainText, padding, blockSize);
622
+ }
623
+
624
+ return plainText.slice(0, originalLength);
625
+ }
@@ -76,7 +76,7 @@ class A1Z26CipherDecode extends Operation {
76
76
  const delim = Utils.charRep(args[0] || "Space");
77
77
 
78
78
  if (input.length === 0) {
79
- return [];
79
+ return "";
80
80
  }
81
81
 
82
82
  const bites = input.split(delim);