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.
- package/AGENTS.md +75 -0
- package/CHANGELOG.md +147 -1
- package/Dockerfile +2 -2
- package/Gruntfile.js +1 -0
- package/package.json +22 -21
- 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 +4771 -1226
- 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/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/Decimal.mjs +5 -5
- package/src/core/lib/HTMLEntities.mjs +2068 -0
- package/src/core/lib/Present.mjs +422 -0
- 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/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/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/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/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 +2 -2
- 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 +1 -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/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 +1 -1
- 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/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,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
|
+
}
|