cyberchef 11.1.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.
- package/AGENTS.md +75 -0
- package/CHANGELOG.md +173 -2
- package/Dockerfile +2 -2
- package/Gruntfile.js +1 -0
- package/README.md +7 -6
- package/SECURITY.md +3 -1
- package/package.json +22 -21
- package/src/core/Chef.mjs +2 -0
- package/src/core/Dish.mjs +1 -5
- package/src/core/Ingredient.mjs +92 -0
- package/src/core/Operation.mjs +19 -0
- package/src/core/Recipe.mjs +4 -0
- package/src/core/Utils.mjs +48 -0
- package/src/core/config/Categories.json +22 -6
- package/src/core/config/OperationConfig.json +4774 -1228
- package/src/core/config/modules/Ciphers.mjs +20 -0
- package/src/core/config/modules/Crypto.mjs +10 -0
- package/src/core/config/modules/Default.mjs +8 -0
- package/src/core/config/modules/File.mjs +16 -0
- package/src/core/config/modules/OpModules.mjs +2 -0
- package/src/core/config/scripts/generateHTMLEntities.mjs +139 -0
- package/src/core/config/scripts/htmlEntityOverrides.mjs +86 -0
- package/src/core/dishTypes/DishType.mjs +1 -1
- package/src/core/errors/ExcludedOperationError.mjs +1 -1
- package/src/core/lib/Arithmetic.mjs +21 -5
- package/src/core/lib/BigIntUtils.mjs +0 -1
- package/src/core/lib/COBS.mjs +86 -0
- package/src/core/lib/Charts.mjs +3 -3
- package/src/core/lib/Decimal.mjs +5 -5
- package/src/core/lib/HTMLEntities.mjs +2068 -0
- package/src/core/lib/Present.mjs +422 -0
- package/src/core/lib/Protocol.mjs +8 -6
- package/src/core/lib/TEA.mjs +494 -0
- package/src/core/lib/TLVParser.mjs +16 -7
- package/src/core/lib/Twofish.mjs +608 -0
- package/src/core/operations/AsconDecrypt.mjs +112 -0
- package/src/core/operations/AsconEncrypt.mjs +108 -0
- package/src/core/operations/AsconHash.mjs +49 -0
- package/src/core/operations/AsconMAC.mjs +68 -0
- package/src/core/operations/AutomatedValidationTestOp.mjs +84 -0
- package/src/core/operations/AvroToJSON.mjs +1 -1
- package/src/core/operations/BLAKE3.mjs +5 -1
- package/src/core/operations/BcryptCompare.mjs +11 -5
- package/src/core/operations/BitShiftLeft.mjs +4 -1
- package/src/core/operations/Bzip2Compress.mjs +1 -1
- package/src/core/operations/DechunkHTTPResponse.mjs +4 -1
- package/src/core/operations/ExtendedGCD.mjs +101 -0
- package/src/core/operations/FromBase.mjs +2 -1
- package/src/core/operations/FromCOBS.mjs +38 -0
- package/src/core/operations/FromDecimal.mjs +6 -1
- package/src/core/operations/FromHTMLEntity.mjs +2 -1458
- package/src/core/operations/GenerateDeBruijnSequence.mjs +8 -0
- package/src/core/operations/GenerateHOTP.mjs +21 -6
- package/src/core/operations/GenerateImage.mjs +22 -10
- package/src/core/operations/GeneratePrime.mjs +154 -0
- package/src/core/operations/GenerateTOTP.mjs +24 -7
- package/src/core/operations/Gzip.mjs +1 -3
- package/src/core/operations/Jsonata.mjs +12 -0
- package/src/core/operations/MIMEDecoding.mjs +1 -1
- package/src/core/operations/MOD.mjs +62 -0
- package/src/core/operations/ModularInverse.mjs +107 -0
- package/src/core/operations/PRESENTDecrypt.mjs +94 -0
- package/src/core/operations/PRESENTEncrypt.mjs +94 -0
- package/src/core/operations/ParityBit.mjs +1 -1
- package/src/core/operations/ParseIPv4Header.mjs +1 -1
- package/src/core/operations/ParseURI.mjs +18 -5
- package/src/core/operations/PseudoRandomNumberGenerator.mjs +2 -1
- package/src/core/operations/RandomPrime.mjs +154 -0
- package/src/core/operations/RenderPDF.mjs +100 -0
- package/src/core/operations/SHA2.mjs +1 -1
- package/src/core/operations/SM4Encrypt.mjs +1 -1
- package/src/core/operations/SetDifference.mjs +8 -1
- package/src/core/operations/SetIntersection.mjs +8 -1
- package/src/core/operations/ShowOnMap.mjs +14 -2
- package/src/core/operations/TEADecrypt.mjs +98 -0
- package/src/core/operations/TEAEncrypt.mjs +98 -0
- package/src/core/operations/ToBase.mjs +4 -4
- package/src/core/operations/ToBase32.mjs +20 -4
- package/src/core/operations/ToBinary.mjs +4 -1
- package/src/core/operations/ToCOBS.mjs +38 -0
- package/src/core/operations/ToHTMLEntity.mjs +7 -1430
- package/src/core/operations/ToHexdump.mjs +7 -1
- package/src/core/operations/TwofishDecrypt.mjs +94 -0
- package/src/core/operations/TwofishEncrypt.mjs +94 -0
- package/src/core/operations/URLEncode.mjs +22 -18
- package/src/core/operations/UnescapeUnicodeCharacters.mjs +2 -1
- package/src/core/operations/ViewBitPlane.mjs +9 -3
- package/src/core/operations/Wrap.mjs +5 -0
- package/src/core/operations/XORBruteForce.mjs +4 -1
- package/src/core/operations/XORChecksum.mjs +10 -3
- package/src/core/operations/XTEADecrypt.mjs +110 -0
- package/src/core/operations/XTEAEncrypt.mjs +110 -0
- package/src/core/operations/index.mjs +42 -0
- package/src/core/vendor/ascon.mjs +162 -0
- package/src/core/vendor/htmlEntities/entity.json +2233 -0
- package/src/core/vendor/htmlEntities/entity.txt +14 -0
- package/src/node/api.mjs +4 -1
- package/src/node/index.mjs +105 -0
- package/src/web/HTMLOperation.mjs +2 -1
- package/src/web/stylesheets/layout/_io.css +8 -0
- package/tests/browser/00_nightwatch.js +26 -0
- package/tests/browser/02_ops.js +20 -4
- package/tests/node/index.mjs +2 -0
- package/tests/node/tests/Dish.mjs +19 -0
- package/tests/node/tests/NodeDish.mjs +36 -0
- package/tests/node/tests/ToHTMLEntity.mjs +82 -0
- package/tests/node/tests/Utils.mjs +77 -0
- package/tests/node/tests/lib/ChartsProtocolPrototypePollution.mjs +90 -0
- package/tests/node/tests/nodeApi.mjs +33 -1
- package/tests/node/tests/operations.mjs +26 -1
- package/tests/operations/index.mjs +17 -0
- package/tests/operations/tests/Arithmetic.mjs +33 -0
- package/tests/operations/tests/Ascon.mjs +501 -0
- package/tests/operations/tests/AutomatedValidation.mjs +154 -0
- package/tests/operations/tests/BLAKE3.mjs +39 -1
- package/tests/operations/tests/Base32.mjs +22 -0
- package/tests/operations/tests/COBS.mjs +476 -0
- package/tests/operations/tests/CharEnc.mjs +2 -2
- package/tests/operations/tests/DechunkHTTPResponse.mjs +66 -0
- package/tests/operations/tests/ExtendedGCD.mjs +78 -0
- package/tests/operations/tests/FromBase.mjs +66 -0
- package/tests/operations/tests/FromDecimal.mjs +55 -0
- package/tests/operations/tests/GenerateLoremIpsum.mjs +2 -2
- package/tests/operations/tests/Gzip.mjs +60 -0
- package/tests/operations/tests/HTMLEntity.mjs +126 -0
- package/tests/operations/tests/Hash.mjs +44 -0
- package/tests/operations/tests/Hexdump.mjs +11 -0
- package/tests/operations/tests/Image.mjs +26 -0
- package/tests/operations/tests/Jsonata.mjs +23 -0
- package/tests/operations/tests/MIMEDecoding.mjs +33 -0
- package/tests/operations/tests/MOD.mjs +208 -0
- package/tests/operations/tests/Median.mjs +33 -0
- package/tests/operations/tests/ModularInverse.mjs +78 -0
- package/tests/operations/tests/OTP.mjs +167 -2
- package/tests/operations/tests/PRESENT.mjs +465 -0
- package/tests/operations/tests/ParseIPv4Header.mjs +11 -0
- package/tests/operations/tests/ParseTLV.mjs +41 -0
- package/tests/operations/tests/RenderPDF.mjs +55 -0
- package/tests/operations/tests/SM2.mjs +1 -1
- package/tests/operations/tests/SetDifference.mjs +22 -0
- package/tests/operations/tests/SetIntersection.mjs +23 -1
- package/tests/operations/tests/ShowOnMap.mjs +61 -0
- package/tests/operations/tests/TEA.mjs +566 -0
- package/tests/operations/tests/Twofish.mjs +486 -0
- package/tests/operations/tests/URLEncodeDecode.mjs +26 -0
- package/tests/operations/tests/UnescapeUnicodeCharacters.mjs +88 -0
- package/tests/operations/tests/Wrap.mjs +44 -0
- package/webpack.config.js +3 -3
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TEA and XTEA block cipher implementation.
|
|
3
|
+
*
|
|
4
|
+
* TEA (Tiny Encryption Algorithm) — Wheeler & Needham, 1994.
|
|
5
|
+
* XTEA (Extended TEA) — Wheeler & Needham, 1997.
|
|
6
|
+
*
|
|
7
|
+
* Both operate on 64-bit blocks with 128-bit keys.
|
|
8
|
+
* TEA uses 32 cycles (64 Feistel rounds).
|
|
9
|
+
* XTEA uses 32 cycles (64 Feistel rounds) with improved key schedule.
|
|
10
|
+
*
|
|
11
|
+
* References:
|
|
12
|
+
* https://en.wikipedia.org/wiki/Tiny_Encryption_Algorithm
|
|
13
|
+
* https://en.wikipedia.org/wiki/XTEA
|
|
14
|
+
* https://www.cix.co.uk/~klockstone/teavect.htm
|
|
15
|
+
*
|
|
16
|
+
* @author Medjedtxm
|
|
17
|
+
* @copyright Crown Copyright 2026
|
|
18
|
+
* @license Apache-2.0
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import OperationError from "../errors/OperationError.mjs";
|
|
22
|
+
|
|
23
|
+
/** TEA/XTEA constants */
|
|
24
|
+
const DELTA = 0x9E3779B9;
|
|
25
|
+
const BLOCK_SIZE = 8; // 64-bit block = 8 bytes
|
|
26
|
+
const ROUNDS = 32; // 32 cycles
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Convert byte array to array of 32-bit unsigned integers (big-endian)
|
|
30
|
+
* @param {number[]} bytes
|
|
31
|
+
* @returns {number[]}
|
|
32
|
+
*/
|
|
33
|
+
function bytesToUint32(bytes) {
|
|
34
|
+
const words = [];
|
|
35
|
+
for (let i = 0; i < bytes.length; i += 4) {
|
|
36
|
+
words.push(
|
|
37
|
+
((bytes[i] << 24) | (bytes[i + 1] << 16) |
|
|
38
|
+
(bytes[i + 2] << 8) | bytes[i + 3]) >>> 0
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
return words;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Convert array of 32-bit unsigned integers to byte array (big-endian)
|
|
46
|
+
* @param {number[]} words
|
|
47
|
+
* @returns {number[]}
|
|
48
|
+
*/
|
|
49
|
+
function uint32ToBytes(words) {
|
|
50
|
+
const bytes = [];
|
|
51
|
+
for (const w of words) {
|
|
52
|
+
bytes.push((w >>> 24) & 0xFF);
|
|
53
|
+
bytes.push((w >>> 16) & 0xFF);
|
|
54
|
+
bytes.push((w >>> 8) & 0xFF);
|
|
55
|
+
bytes.push(w & 0xFF);
|
|
56
|
+
}
|
|
57
|
+
return bytes;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* TEA encrypt a single 64-bit block
|
|
62
|
+
* Reference: Wheeler & Needham, 1994
|
|
63
|
+
*
|
|
64
|
+
* @param {number[]} block - 8 bytes (plaintext)
|
|
65
|
+
* @param {number[]} key - 16 bytes (128-bit key)
|
|
66
|
+
* @returns {number[]} - 8 bytes (ciphertext)
|
|
67
|
+
*/
|
|
68
|
+
function teaEncryptBlock(block, key) {
|
|
69
|
+
const v = bytesToUint32(block);
|
|
70
|
+
const k = bytesToUint32(key);
|
|
71
|
+
let v0 = v[0], v1 = v[1];
|
|
72
|
+
let sum = 0;
|
|
73
|
+
|
|
74
|
+
for (let i = 0; i < ROUNDS; i++) {
|
|
75
|
+
sum = (sum + DELTA) >>> 0;
|
|
76
|
+
v0 = (v0 + ((((v1 << 4) + k[0]) ^ (v1 + sum) ^ ((v1 >>> 5) + k[1])))) >>> 0;
|
|
77
|
+
v1 = (v1 + ((((v0 << 4) + k[2]) ^ (v0 + sum) ^ ((v0 >>> 5) + k[3])))) >>> 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return uint32ToBytes([v0, v1]);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* TEA decrypt a single 64-bit block
|
|
85
|
+
*
|
|
86
|
+
* @param {number[]} block - 8 bytes (ciphertext)
|
|
87
|
+
* @param {number[]} key - 16 bytes (128-bit key)
|
|
88
|
+
* @returns {number[]} - 8 bytes (plaintext)
|
|
89
|
+
*/
|
|
90
|
+
function teaDecryptBlock(block, key) {
|
|
91
|
+
const v = bytesToUint32(block);
|
|
92
|
+
const k = bytesToUint32(key);
|
|
93
|
+
let v0 = v[0], v1 = v[1];
|
|
94
|
+
let sum = (DELTA * ROUNDS) >>> 0;
|
|
95
|
+
|
|
96
|
+
for (let i = 0; i < ROUNDS; i++) {
|
|
97
|
+
v1 = (v1 - ((((v0 << 4) + k[2]) ^ (v0 + sum) ^ ((v0 >>> 5) + k[3])))) >>> 0;
|
|
98
|
+
v0 = (v0 - ((((v1 << 4) + k[0]) ^ (v1 + sum) ^ ((v1 >>> 5) + k[1])))) >>> 0;
|
|
99
|
+
sum = (sum - DELTA) >>> 0;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return uint32ToBytes([v0, v1]);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* XTEA encrypt a single 64-bit block
|
|
107
|
+
* Reference: Wheeler & Needham, 1997
|
|
108
|
+
*
|
|
109
|
+
* @param {number[]} block - 8 bytes (plaintext)
|
|
110
|
+
* @param {number[]} key - 16 bytes (128-bit key)
|
|
111
|
+
* @param {number} rounds - Number of rounds (default 32)
|
|
112
|
+
* @returns {number[]} - 8 bytes (ciphertext)
|
|
113
|
+
*/
|
|
114
|
+
function xteaEncryptBlock(block, key, rounds) {
|
|
115
|
+
const v = bytesToUint32(block);
|
|
116
|
+
const k = bytesToUint32(key);
|
|
117
|
+
let v0 = v[0], v1 = v[1];
|
|
118
|
+
let sum = 0;
|
|
119
|
+
|
|
120
|
+
for (let i = 0; i < rounds; i++) {
|
|
121
|
+
v0 = (v0 + ((((v1 << 4) ^ (v1 >>> 5)) + v1) ^ (sum + k[sum & 3]))) >>> 0;
|
|
122
|
+
sum = (sum + DELTA) >>> 0;
|
|
123
|
+
v1 = (v1 + ((((v0 << 4) ^ (v0 >>> 5)) + v0) ^ (sum + k[(sum >>> 11) & 3]))) >>> 0;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return uint32ToBytes([v0, v1]);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* XTEA decrypt a single 64-bit block
|
|
131
|
+
*
|
|
132
|
+
* @param {number[]} block - 8 bytes (ciphertext)
|
|
133
|
+
* @param {number[]} key - 16 bytes (128-bit key)
|
|
134
|
+
* @param {number} rounds - Number of rounds (default 32)
|
|
135
|
+
* @returns {number[]} - 8 bytes (plaintext)
|
|
136
|
+
*/
|
|
137
|
+
function xteaDecryptBlock(block, key, rounds) {
|
|
138
|
+
const v = bytesToUint32(block);
|
|
139
|
+
const k = bytesToUint32(key);
|
|
140
|
+
let v0 = v[0], v1 = v[1];
|
|
141
|
+
let sum = (DELTA * rounds) >>> 0;
|
|
142
|
+
|
|
143
|
+
for (let i = 0; i < rounds; i++) {
|
|
144
|
+
v1 = (v1 - ((((v0 << 4) ^ (v0 >>> 5)) + v0) ^ (sum + k[(sum >>> 11) & 3]))) >>> 0;
|
|
145
|
+
sum = (sum - DELTA) >>> 0;
|
|
146
|
+
v0 = (v0 - ((((v1 << 4) ^ (v1 >>> 5)) + v1) ^ (sum + k[sum & 3]))) >>> 0;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return uint32ToBytes([v0, v1]);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* XOR two byte arrays of equal length
|
|
154
|
+
* @param {number[]} a
|
|
155
|
+
* @param {number[]} b
|
|
156
|
+
* @returns {number[]}
|
|
157
|
+
*/
|
|
158
|
+
function xorBlocks(a, b) {
|
|
159
|
+
return a.map((byte, i) => byte ^ b[i]);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Increment a byte array as a big-endian counter
|
|
164
|
+
* @param {number[]} counter
|
|
165
|
+
* @returns {number[]}
|
|
166
|
+
*/
|
|
167
|
+
function incrementCounter(counter) {
|
|
168
|
+
const result = [...counter];
|
|
169
|
+
for (let i = result.length - 1; i >= 0; i--) {
|
|
170
|
+
result[i] = (result[i] + 1) & 0xFF;
|
|
171
|
+
if (result[i] !== 0) break;
|
|
172
|
+
}
|
|
173
|
+
return result;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Apply padding to message
|
|
178
|
+
* @param {number[]} message
|
|
179
|
+
* @param {string} padding - "NO", "PKCS5", "ZERO", "RANDOM", "BIT"
|
|
180
|
+
* @returns {number[]}
|
|
181
|
+
*/
|
|
182
|
+
function applyPadding(message, padding) {
|
|
183
|
+
const remainder = message.length % BLOCK_SIZE;
|
|
184
|
+
if (remainder === 0 && padding !== "PKCS5") return [...message];
|
|
185
|
+
|
|
186
|
+
const nPadding = (remainder === 0 && padding === "PKCS5") ?
|
|
187
|
+
BLOCK_SIZE :
|
|
188
|
+
BLOCK_SIZE - remainder;
|
|
189
|
+
|
|
190
|
+
if (nPadding === 0) return [...message];
|
|
191
|
+
|
|
192
|
+
const padded = [...message];
|
|
193
|
+
|
|
194
|
+
switch (padding) {
|
|
195
|
+
case "NO":
|
|
196
|
+
throw new OperationError(
|
|
197
|
+
`No padding requested but input length (${message.length} bytes) is not a multiple of ${BLOCK_SIZE} bytes.`
|
|
198
|
+
);
|
|
199
|
+
case "PKCS5":
|
|
200
|
+
for (let i = 0; i < nPadding; i++) padded.push(nPadding);
|
|
201
|
+
break;
|
|
202
|
+
case "ZERO":
|
|
203
|
+
for (let i = 0; i < nPadding; i++) padded.push(0);
|
|
204
|
+
break;
|
|
205
|
+
case "RANDOM":
|
|
206
|
+
for (let i = 0; i < nPadding; i++) padded.push(Math.floor(Math.random() * 256));
|
|
207
|
+
break;
|
|
208
|
+
case "BIT":
|
|
209
|
+
padded.push(0x80);
|
|
210
|
+
for (let i = 1; i < nPadding; i++) padded.push(0);
|
|
211
|
+
break;
|
|
212
|
+
default:
|
|
213
|
+
throw new OperationError(`Unknown padding type: ${padding}`);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return padded;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Remove padding from message
|
|
221
|
+
* @param {number[]} message
|
|
222
|
+
* @param {string} padding
|
|
223
|
+
* @returns {number[]}
|
|
224
|
+
*/
|
|
225
|
+
function removePadding(message, padding) {
|
|
226
|
+
if (message.length === 0) return message;
|
|
227
|
+
|
|
228
|
+
switch (padding) {
|
|
229
|
+
case "NO":
|
|
230
|
+
case "ZERO":
|
|
231
|
+
case "RANDOM":
|
|
232
|
+
return message;
|
|
233
|
+
|
|
234
|
+
case "PKCS5": {
|
|
235
|
+
const padByte = message[message.length - 1];
|
|
236
|
+
if (padByte > 0 && padByte <= BLOCK_SIZE) {
|
|
237
|
+
for (let i = 0; i < padByte; i++) {
|
|
238
|
+
if (message[message.length - 1 - i] !== padByte) {
|
|
239
|
+
throw new OperationError("Invalid PKCS#5 padding.");
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return message.slice(0, message.length - padByte);
|
|
243
|
+
}
|
|
244
|
+
throw new OperationError("Invalid PKCS#5 padding.");
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
case "BIT": {
|
|
248
|
+
for (let i = message.length - 1; i >= 0; i--) {
|
|
249
|
+
if (message[i] === 0x80) return message.slice(0, i);
|
|
250
|
+
if (message[i] !== 0) throw new OperationError("Invalid BIT padding.");
|
|
251
|
+
}
|
|
252
|
+
throw new OperationError("Invalid BIT padding.");
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
default:
|
|
256
|
+
throw new OperationError(`Unknown padding type: ${padding}`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Encrypt with block cipher modes
|
|
262
|
+
*
|
|
263
|
+
* @param {number[]} message - Plaintext bytes
|
|
264
|
+
* @param {number[]} key - 16-byte key
|
|
265
|
+
* @param {number[]} iv - 8-byte IV (ignored for ECB)
|
|
266
|
+
* @param {string} mode - "ECB", "CBC", "CFB", "OFB", "CTR"
|
|
267
|
+
* @param {string} padding - "PKCS5", "NO", "ZERO", "RANDOM", "BIT"
|
|
268
|
+
* @param {Function} encryptBlockFn - Block encrypt function
|
|
269
|
+
* @returns {number[]} - Ciphertext bytes
|
|
270
|
+
*/
|
|
271
|
+
function encryptWithMode(message, key, iv, mode, padding, encryptBlockFn) {
|
|
272
|
+
const messageLength = message.length;
|
|
273
|
+
if (messageLength === 0) return [];
|
|
274
|
+
|
|
275
|
+
let data;
|
|
276
|
+
if (mode === "ECB" || mode === "CBC") {
|
|
277
|
+
data = applyPadding(message, padding);
|
|
278
|
+
} else {
|
|
279
|
+
data = [...message];
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const cipherText = [];
|
|
283
|
+
|
|
284
|
+
switch (mode) {
|
|
285
|
+
case "ECB":
|
|
286
|
+
for (let i = 0; i < data.length; i += BLOCK_SIZE) {
|
|
287
|
+
cipherText.push(...encryptBlockFn(data.slice(i, i + BLOCK_SIZE), key));
|
|
288
|
+
}
|
|
289
|
+
break;
|
|
290
|
+
|
|
291
|
+
case "CBC": {
|
|
292
|
+
let ivBlock = [...iv];
|
|
293
|
+
for (let i = 0; i < data.length; i += BLOCK_SIZE) {
|
|
294
|
+
const block = data.slice(i, i + BLOCK_SIZE);
|
|
295
|
+
const xored = xorBlocks(block, ivBlock);
|
|
296
|
+
ivBlock = encryptBlockFn(xored, key);
|
|
297
|
+
cipherText.push(...ivBlock);
|
|
298
|
+
}
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
case "CFB": {
|
|
303
|
+
let ivBlock = [...iv];
|
|
304
|
+
for (let i = 0; i < data.length; i += BLOCK_SIZE) {
|
|
305
|
+
const encrypted = encryptBlockFn(ivBlock, key);
|
|
306
|
+
const block = data.slice(i, i + BLOCK_SIZE);
|
|
307
|
+
while (block.length < BLOCK_SIZE) block.push(0);
|
|
308
|
+
ivBlock = xorBlocks(encrypted, block);
|
|
309
|
+
cipherText.push(...ivBlock);
|
|
310
|
+
}
|
|
311
|
+
return cipherText.slice(0, messageLength);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
case "OFB": {
|
|
315
|
+
let ivBlock = [...iv];
|
|
316
|
+
for (let i = 0; i < data.length; i += BLOCK_SIZE) {
|
|
317
|
+
ivBlock = encryptBlockFn(ivBlock, key);
|
|
318
|
+
const block = data.slice(i, i + BLOCK_SIZE);
|
|
319
|
+
while (block.length < BLOCK_SIZE) block.push(0);
|
|
320
|
+
cipherText.push(...xorBlocks(ivBlock, block));
|
|
321
|
+
}
|
|
322
|
+
return cipherText.slice(0, messageLength);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
case "CTR": {
|
|
326
|
+
let counter = [...iv];
|
|
327
|
+
for (let i = 0; i < data.length; i += BLOCK_SIZE) {
|
|
328
|
+
const encrypted = encryptBlockFn(counter, key);
|
|
329
|
+
const block = data.slice(i, i + BLOCK_SIZE);
|
|
330
|
+
while (block.length < BLOCK_SIZE) block.push(0);
|
|
331
|
+
cipherText.push(...xorBlocks(encrypted, block));
|
|
332
|
+
counter = incrementCounter(counter);
|
|
333
|
+
}
|
|
334
|
+
return cipherText.slice(0, messageLength);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
default:
|
|
338
|
+
throw new OperationError(`Invalid block cipher mode: ${mode}`);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return cipherText;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Decrypt with block cipher modes
|
|
346
|
+
*
|
|
347
|
+
* @param {number[]} cipherText - Ciphertext bytes
|
|
348
|
+
* @param {number[]} key - 16-byte key
|
|
349
|
+
* @param {number[]} iv - 8-byte IV (ignored for ECB)
|
|
350
|
+
* @param {string} mode - "ECB", "CBC", "CFB", "OFB", "CTR"
|
|
351
|
+
* @param {string} padding - "PKCS5", "NO", "ZERO", "RANDOM", "BIT"
|
|
352
|
+
* @param {Function} encryptBlockFn - Block encrypt function (used for stream modes)
|
|
353
|
+
* @param {Function} decryptBlockFn - Block decrypt function (used for ECB/CBC)
|
|
354
|
+
* @returns {number[]} - Plaintext bytes
|
|
355
|
+
*/
|
|
356
|
+
function decryptWithMode(cipherText, key, iv, mode, padding, encryptBlockFn, decryptBlockFn) {
|
|
357
|
+
const originalLength = cipherText.length;
|
|
358
|
+
if (originalLength === 0) return [];
|
|
359
|
+
|
|
360
|
+
if (mode === "ECB" || mode === "CBC") {
|
|
361
|
+
if ((originalLength % BLOCK_SIZE) !== 0)
|
|
362
|
+
throw new OperationError(
|
|
363
|
+
`Invalid ciphertext length: ${originalLength} bytes. Must be a multiple of ${BLOCK_SIZE}.`
|
|
364
|
+
);
|
|
365
|
+
} else {
|
|
366
|
+
while ((cipherText.length % BLOCK_SIZE) !== 0)
|
|
367
|
+
cipherText.push(0);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const plainText = [];
|
|
371
|
+
|
|
372
|
+
switch (mode) {
|
|
373
|
+
case "ECB":
|
|
374
|
+
for (let i = 0; i < cipherText.length; i += BLOCK_SIZE) {
|
|
375
|
+
plainText.push(...decryptBlockFn(cipherText.slice(i, i + BLOCK_SIZE), key));
|
|
376
|
+
}
|
|
377
|
+
break;
|
|
378
|
+
|
|
379
|
+
case "CBC": {
|
|
380
|
+
let ivBlock = [...iv];
|
|
381
|
+
for (let i = 0; i < cipherText.length; i += BLOCK_SIZE) {
|
|
382
|
+
const block = cipherText.slice(i, i + BLOCK_SIZE);
|
|
383
|
+
const decrypted = decryptBlockFn(block, key);
|
|
384
|
+
plainText.push(...xorBlocks(decrypted, ivBlock));
|
|
385
|
+
ivBlock = block;
|
|
386
|
+
}
|
|
387
|
+
break;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
case "CFB": {
|
|
391
|
+
let ivBlock = [...iv];
|
|
392
|
+
for (let i = 0; i < cipherText.length; i += BLOCK_SIZE) {
|
|
393
|
+
const encrypted = encryptBlockFn(ivBlock, key);
|
|
394
|
+
const block = cipherText.slice(i, i + BLOCK_SIZE);
|
|
395
|
+
plainText.push(...xorBlocks(encrypted, block));
|
|
396
|
+
ivBlock = block;
|
|
397
|
+
}
|
|
398
|
+
return plainText.slice(0, originalLength);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
case "OFB": {
|
|
402
|
+
let ivBlock = [...iv];
|
|
403
|
+
for (let i = 0; i < cipherText.length; i += BLOCK_SIZE) {
|
|
404
|
+
ivBlock = encryptBlockFn(ivBlock, key);
|
|
405
|
+
const block = cipherText.slice(i, i + BLOCK_SIZE);
|
|
406
|
+
plainText.push(...xorBlocks(ivBlock, block));
|
|
407
|
+
}
|
|
408
|
+
return plainText.slice(0, originalLength);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
case "CTR": {
|
|
412
|
+
let counter = [...iv];
|
|
413
|
+
for (let i = 0; i < cipherText.length; i += BLOCK_SIZE) {
|
|
414
|
+
const encrypted = encryptBlockFn(counter, key);
|
|
415
|
+
const block = cipherText.slice(i, i + BLOCK_SIZE);
|
|
416
|
+
plainText.push(...xorBlocks(encrypted, block));
|
|
417
|
+
counter = incrementCounter(counter);
|
|
418
|
+
}
|
|
419
|
+
return plainText.slice(0, originalLength);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
default:
|
|
423
|
+
throw new OperationError(`Invalid block cipher mode: ${mode}`);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (mode === "ECB" || mode === "CBC") {
|
|
427
|
+
return removePadding(plainText, padding);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return plainText.slice(0, originalLength);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
// ==================== PUBLIC API ====================
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Encrypt using TEA cipher
|
|
438
|
+
* @param {number[]} message - Plaintext bytes
|
|
439
|
+
* @param {number[]} key - 16-byte key
|
|
440
|
+
* @param {number[]} iv - 8-byte IV
|
|
441
|
+
* @param {string} mode - Block cipher mode
|
|
442
|
+
* @param {string} padding - Padding type
|
|
443
|
+
* @returns {number[]} - Ciphertext bytes
|
|
444
|
+
*/
|
|
445
|
+
export function encryptTEA(message, key, iv, mode = "ECB", padding = "PKCS5") {
|
|
446
|
+
return encryptWithMode(message, key, iv, mode, padding, teaEncryptBlock);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Decrypt using TEA cipher
|
|
451
|
+
* @param {number[]} cipherText - Ciphertext bytes
|
|
452
|
+
* @param {number[]} key - 16-byte key
|
|
453
|
+
* @param {number[]} iv - 8-byte IV
|
|
454
|
+
* @param {string} mode - Block cipher mode
|
|
455
|
+
* @param {string} padding - Padding type
|
|
456
|
+
* @returns {number[]} - Plaintext bytes
|
|
457
|
+
*/
|
|
458
|
+
export function decryptTEA(cipherText, key, iv, mode = "ECB", padding = "PKCS5") {
|
|
459
|
+
return decryptWithMode(cipherText, key, iv, mode, padding, teaEncryptBlock, teaDecryptBlock);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Encrypt using XTEA cipher
|
|
464
|
+
* @param {number[]} message - Plaintext bytes
|
|
465
|
+
* @param {number[]} key - 16-byte key
|
|
466
|
+
* @param {number[]} iv - 8-byte IV
|
|
467
|
+
* @param {string} mode - Block cipher mode
|
|
468
|
+
* @param {string} padding - Padding type
|
|
469
|
+
* @param {number} rounds - Number of rounds (default 32)
|
|
470
|
+
* @returns {number[]} - Ciphertext bytes
|
|
471
|
+
*/
|
|
472
|
+
export function encryptXTEA(message, key, iv, mode = "ECB", padding = "PKCS5", rounds = 32) {
|
|
473
|
+
const encFn = (block, k) => xteaEncryptBlock(block, k, rounds);
|
|
474
|
+
return encryptWithMode(message, key, iv, mode, padding, encFn);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Decrypt using XTEA cipher
|
|
479
|
+
* @param {number[]} cipherText - Ciphertext bytes
|
|
480
|
+
* @param {number[]} key - 16-byte key
|
|
481
|
+
* @param {number[]} iv - 8-byte IV
|
|
482
|
+
* @param {string} mode - Block cipher mode
|
|
483
|
+
* @param {string} padding - Padding type
|
|
484
|
+
* @param {number} rounds - Number of rounds (default 32)
|
|
485
|
+
* @returns {number[]} - Plaintext bytes
|
|
486
|
+
*/
|
|
487
|
+
export function decryptXTEA(cipherText, key, iv, mode = "ECB", padding = "PKCS5", rounds = 32) {
|
|
488
|
+
const encFn = (block, k) => xteaEncryptBlock(block, k, rounds);
|
|
489
|
+
const decFn = (block, k) => xteaDecryptBlock(block, k, rounds);
|
|
490
|
+
return decryptWithMode(cipherText, key, iv, mode, padding, encFn, decFn);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/** Block size in bytes (exported for operation validation) */
|
|
494
|
+
export const TEA_BLOCK_SIZE = BLOCK_SIZE;
|
|
@@ -33,20 +33,29 @@ export default class TLVParser {
|
|
|
33
33
|
* @returns {number}
|
|
34
34
|
*/
|
|
35
35
|
getLength() {
|
|
36
|
+
let bytesInLength = this.bytesInLength;
|
|
37
|
+
let bigEndian = false;
|
|
38
|
+
|
|
36
39
|
if (this.basicEncodingRules) {
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
const firstLengthByte = this.input[this.location];
|
|
41
|
+
this.location++;
|
|
42
|
+
|
|
43
|
+
if (firstLengthByte & 0x80) {
|
|
44
|
+
bytesInLength = firstLengthByte & ~0x80;
|
|
45
|
+
bigEndian = true;
|
|
40
46
|
} else {
|
|
41
|
-
|
|
42
|
-
return bit & ~0x80;
|
|
47
|
+
return firstLengthByte & ~0x80;
|
|
43
48
|
}
|
|
44
49
|
}
|
|
45
50
|
|
|
46
51
|
let length = 0;
|
|
47
52
|
|
|
48
|
-
for (let i = 0; i <
|
|
49
|
-
|
|
53
|
+
for (let i = 0; i < bytesInLength; i++) {
|
|
54
|
+
if (bigEndian) {
|
|
55
|
+
length = (length << 8) + this.input[this.location];
|
|
56
|
+
} else {
|
|
57
|
+
length += this.input[this.location] * Math.pow(Math.pow(2, 8), i);
|
|
58
|
+
}
|
|
50
59
|
this.location++;
|
|
51
60
|
}
|
|
52
61
|
|