cyberchef 10.22.0 → 10.23.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.
- package/CHANGELOG.md +131 -0
- package/CONTRIBUTING.md +37 -0
- package/Dockerfile +2 -0
- package/Gruntfile.js +0 -12
- package/README.md +1 -1
- package/babel.config.js +0 -6
- package/package.json +63 -62
- package/src/core/Chef.mjs +8 -1
- package/src/core/Ingredient.mjs +5 -2
- package/src/core/Operation.mjs +6 -1
- package/src/core/Recipe.mjs +10 -5
- package/src/core/config/Categories.json +18 -3
- package/src/core/config/OperationConfig.json +22310 -0
- package/src/core/config/modules/Bletchley.mjs +28 -0
- package/src/core/config/modules/Charts.mjs +24 -0
- package/src/core/config/modules/Ciphers.mjs +128 -0
- package/src/core/config/modules/Code.mjs +60 -0
- package/src/core/config/modules/Compression.mjs +52 -0
- package/src/core/config/modules/Crypto.mjs +134 -0
- package/src/core/config/modules/Default.mjs +424 -0
- package/src/core/config/modules/Diff.mjs +16 -0
- package/src/core/config/modules/Encodings.mjs +42 -0
- package/src/core/config/modules/Handlebars.mjs +16 -0
- package/src/core/config/modules/Hashing.mjs +30 -0
- package/src/core/config/modules/Image.mjs +70 -0
- package/src/core/config/modules/Jq.mjs +16 -0
- package/src/core/config/modules/OCR.mjs +16 -0
- package/src/core/config/modules/OpModules.mjs +63 -0
- package/src/core/config/modules/PGP.mjs +26 -0
- package/src/core/config/modules/Protobuf.mjs +18 -0
- package/src/core/config/modules/PublicKey.mjs +36 -0
- package/src/core/config/modules/Regex.mjs +40 -0
- package/src/core/config/modules/Serialise.mjs +26 -0
- package/src/core/config/modules/Shellcode.mjs +18 -0
- package/src/core/config/modules/URL.mjs +20 -0
- package/src/core/config/modules/UserAgent.mjs +16 -0
- package/src/core/config/modules/Yara.mjs +16 -0
- package/src/core/lib/AudioBytes.mjs +103 -0
- package/src/core/lib/AudioMetaSchema.mjs +82 -0
- package/src/core/lib/AudioParsers.mjs +630 -0
- package/src/core/lib/BigIntUtils.mjs +73 -0
- package/src/core/lib/Modhex.mjs +2 -0
- package/src/core/lib/QRCode.mjs +30 -10
- package/src/core/lib/RC6.mjs +625 -0
- package/src/core/operations/A1Z26CipherDecode.mjs +1 -1
- package/src/core/operations/AddTextToImage.mjs +116 -64
- package/src/core/operations/BlurImage.mjs +10 -12
- package/src/core/operations/ContainImage.mjs +50 -40
- package/src/core/operations/ConvertImageFormat.mjs +33 -39
- package/src/core/operations/CoverImage.mjs +39 -37
- package/src/core/operations/CropImage.mjs +35 -21
- package/src/core/operations/DisassembleARM.mjs +193 -0
- package/src/core/operations/DitherImage.mjs +8 -8
- package/src/core/operations/EscapeUnicodeCharacters.mjs +0 -17
- package/src/core/operations/ExtractAudioMetadata.mjs +175 -0
- package/src/core/operations/ExtractLSB.mjs +17 -11
- package/src/core/operations/ExtractRGBA.mjs +12 -10
- package/src/core/operations/FlaskSessionDecode.mjs +80 -0
- package/src/core/operations/FlaskSessionSign.mjs +89 -0
- package/src/core/operations/FlaskSessionVerify.mjs +136 -0
- package/src/core/operations/FlipImage.mjs +14 -10
- package/src/core/operations/GenerateImage.mjs +39 -32
- package/src/core/operations/ImageBrightnessContrast.mjs +10 -10
- package/src/core/operations/ImageFilter.mjs +14 -13
- package/src/core/operations/ImageHueSaturationLightness.mjs +22 -20
- package/src/core/operations/ImageOpacity.mjs +6 -8
- package/src/core/operations/InvertImage.mjs +4 -6
- package/src/core/operations/Jq.mjs +12 -4
- package/src/core/operations/NormaliseImage.mjs +5 -7
- package/src/core/operations/OffsetChecker.mjs +1 -1
- package/src/core/operations/ParseEthernetFrame.mjs +112 -0
- package/src/core/operations/ParseIPv4Header.mjs +23 -6
- package/src/core/operations/ParseQRCode.mjs +13 -13
- package/src/core/operations/PseudoRandomIntegerGenerator.mjs +164 -0
- package/src/core/operations/RC6Decrypt.mjs +119 -0
- package/src/core/operations/RC6Encrypt.mjs +119 -0
- package/src/core/operations/RandomizeColourPalette.mjs +11 -11
- package/src/core/operations/ResizeImage.mjs +30 -23
- package/src/core/operations/RotateImage.mjs +8 -9
- package/src/core/operations/SQLBeautify.mjs +21 -3
- package/src/core/operations/SharpenImage.mjs +94 -62
- package/src/core/operations/SplitColourChannels.mjs +47 -21
- package/src/core/operations/TextIntegerConverter.mjs +123 -0
- package/src/core/operations/UnescapeUnicodeCharacters.mjs +17 -0
- package/src/core/operations/ViewBitPlane.mjs +16 -20
- package/src/core/operations/index.mjs +960 -0
- package/src/node/index.mjs +2423 -0
- package/src/web/HTMLIngredient.mjs +24 -43
- package/src/web/Manager.mjs +1 -0
- package/src/web/html/index.html +6 -6
- package/src/web/static/fonts/bmfonts/Roboto72White.fnt +491 -485
- package/src/web/static/fonts/bmfonts/RobotoBlack72White.fnt +494 -488
- package/src/web/static/fonts/bmfonts/RobotoMono72White.fnt +110 -103
- package/src/web/static/fonts/bmfonts/RobotoSlab72White.fnt +498 -492
- package/src/web/stylesheets/layout/_banner.css +30 -0
- package/src/web/stylesheets/layout/_modals.css +5 -0
- package/src/web/stylesheets/utils/_overrides.css +7 -0
- package/src/web/waiters/ControlsWaiter.mjs +82 -0
- package/src/web/waiters/InputWaiter.mjs +12 -6
- package/src/web/waiters/RecipeWaiter.mjs +2 -2
- package/tests/browser/02_ops.js +23 -3
- package/tests/node/index.mjs +1 -0
- package/tests/node/tests/lib/BigIntUtils.mjs +150 -0
- package/tests/node/tests/operations.mjs +9 -7
- package/tests/operations/index.mjs +8 -0
- package/tests/operations/tests/A1Z26CipherDecode.mjs +33 -0
- package/tests/operations/tests/DisassembleARM.mjs +377 -0
- package/tests/operations/tests/ExtractAudioMetadata.mjs +287 -0
- package/tests/operations/tests/FlaskSession.mjs +246 -0
- package/tests/operations/tests/GenerateQRCode.mjs +67 -0
- package/tests/operations/tests/JWTSign.mjs +83 -8
- package/tests/operations/tests/Jq.mjs +32 -0
- package/tests/operations/tests/Modhex.mjs +20 -0
- package/tests/operations/tests/ParseEthernetFrame.mjs +45 -0
- package/tests/operations/tests/RC6.mjs +487 -0
- package/tests/operations/tests/SQLBeautify.mjs +54 -0
- package/tests/operations/tests/TextIntegerConverter.mjs +199 -0
- package/tests/samples/Audio.mjs +73 -0
- package/tests/samples/Images.mjs +0 -12
- package/webpack.config.js +10 -7
- 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
|
+
}
|