cyberchef 11.2.0 → 11.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/AGENTS.md +75 -0
  2. package/CHANGELOG.md +147 -1
  3. package/Dockerfile +2 -2
  4. package/Gruntfile.js +1 -0
  5. package/package.json +22 -21
  6. package/src/core/Dish.mjs +1 -5
  7. package/src/core/Ingredient.mjs +92 -0
  8. package/src/core/Operation.mjs +19 -0
  9. package/src/core/Recipe.mjs +4 -0
  10. package/src/core/Utils.mjs +48 -0
  11. package/src/core/config/Categories.json +22 -6
  12. package/src/core/config/OperationConfig.json +4771 -1226
  13. package/src/core/config/modules/Ciphers.mjs +20 -0
  14. package/src/core/config/modules/Crypto.mjs +10 -0
  15. package/src/core/config/modules/Default.mjs +8 -0
  16. package/src/core/config/modules/File.mjs +16 -0
  17. package/src/core/config/modules/OpModules.mjs +2 -0
  18. package/src/core/config/scripts/generateHTMLEntities.mjs +139 -0
  19. package/src/core/config/scripts/htmlEntityOverrides.mjs +86 -0
  20. package/src/core/lib/Arithmetic.mjs +21 -5
  21. package/src/core/lib/BigIntUtils.mjs +0 -1
  22. package/src/core/lib/COBS.mjs +86 -0
  23. package/src/core/lib/Decimal.mjs +5 -5
  24. package/src/core/lib/HTMLEntities.mjs +2068 -0
  25. package/src/core/lib/Present.mjs +422 -0
  26. package/src/core/lib/TEA.mjs +494 -0
  27. package/src/core/lib/TLVParser.mjs +16 -7
  28. package/src/core/lib/Twofish.mjs +608 -0
  29. package/src/core/operations/AsconDecrypt.mjs +112 -0
  30. package/src/core/operations/AsconEncrypt.mjs +108 -0
  31. package/src/core/operations/AsconHash.mjs +49 -0
  32. package/src/core/operations/AsconMAC.mjs +68 -0
  33. package/src/core/operations/AutomatedValidationTestOp.mjs +84 -0
  34. package/src/core/operations/AvroToJSON.mjs +1 -1
  35. package/src/core/operations/BLAKE3.mjs +5 -1
  36. package/src/core/operations/BcryptCompare.mjs +11 -5
  37. package/src/core/operations/BitShiftLeft.mjs +4 -1
  38. package/src/core/operations/Bzip2Compress.mjs +1 -1
  39. package/src/core/operations/DechunkHTTPResponse.mjs +4 -1
  40. package/src/core/operations/ExtendedGCD.mjs +101 -0
  41. package/src/core/operations/FromBase.mjs +2 -1
  42. package/src/core/operations/FromCOBS.mjs +38 -0
  43. package/src/core/operations/FromDecimal.mjs +6 -1
  44. package/src/core/operations/FromHTMLEntity.mjs +2 -1458
  45. package/src/core/operations/GenerateHOTP.mjs +21 -6
  46. package/src/core/operations/GenerateImage.mjs +22 -10
  47. package/src/core/operations/GeneratePrime.mjs +154 -0
  48. package/src/core/operations/GenerateTOTP.mjs +24 -7
  49. package/src/core/operations/Gzip.mjs +1 -3
  50. package/src/core/operations/Jsonata.mjs +12 -0
  51. package/src/core/operations/MIMEDecoding.mjs +1 -1
  52. package/src/core/operations/MOD.mjs +62 -0
  53. package/src/core/operations/ModularInverse.mjs +107 -0
  54. package/src/core/operations/PRESENTDecrypt.mjs +94 -0
  55. package/src/core/operations/PRESENTEncrypt.mjs +94 -0
  56. package/src/core/operations/ParseURI.mjs +18 -5
  57. package/src/core/operations/PseudoRandomNumberGenerator.mjs +2 -1
  58. package/src/core/operations/RandomPrime.mjs +154 -0
  59. package/src/core/operations/RenderPDF.mjs +100 -0
  60. package/src/core/operations/SM4Encrypt.mjs +1 -1
  61. package/src/core/operations/SetDifference.mjs +8 -1
  62. package/src/core/operations/SetIntersection.mjs +8 -1
  63. package/src/core/operations/ShowOnMap.mjs +14 -2
  64. package/src/core/operations/TEADecrypt.mjs +98 -0
  65. package/src/core/operations/TEAEncrypt.mjs +98 -0
  66. package/src/core/operations/ToBase.mjs +4 -4
  67. package/src/core/operations/ToBase32.mjs +20 -4
  68. package/src/core/operations/ToBinary.mjs +4 -1
  69. package/src/core/operations/ToCOBS.mjs +38 -0
  70. package/src/core/operations/ToHTMLEntity.mjs +7 -1430
  71. package/src/core/operations/TwofishDecrypt.mjs +94 -0
  72. package/src/core/operations/TwofishEncrypt.mjs +94 -0
  73. package/src/core/operations/URLEncode.mjs +22 -18
  74. package/src/core/operations/UnescapeUnicodeCharacters.mjs +2 -1
  75. package/src/core/operations/ViewBitPlane.mjs +9 -3
  76. package/src/core/operations/Wrap.mjs +5 -0
  77. package/src/core/operations/XORBruteForce.mjs +4 -1
  78. package/src/core/operations/XORChecksum.mjs +2 -2
  79. package/src/core/operations/XTEADecrypt.mjs +110 -0
  80. package/src/core/operations/XTEAEncrypt.mjs +110 -0
  81. package/src/core/operations/index.mjs +42 -0
  82. package/src/core/vendor/ascon.mjs +162 -0
  83. package/src/core/vendor/htmlEntities/entity.json +2233 -0
  84. package/src/core/vendor/htmlEntities/entity.txt +14 -0
  85. package/src/node/api.mjs +4 -1
  86. package/src/node/index.mjs +105 -0
  87. package/src/web/HTMLOperation.mjs +2 -1
  88. package/src/web/stylesheets/layout/_io.css +8 -0
  89. package/tests/browser/00_nightwatch.js +26 -0
  90. package/tests/browser/02_ops.js +20 -4
  91. package/tests/node/index.mjs +1 -0
  92. package/tests/node/tests/Dish.mjs +19 -0
  93. package/tests/node/tests/NodeDish.mjs +36 -0
  94. package/tests/node/tests/ToHTMLEntity.mjs +82 -0
  95. package/tests/node/tests/Utils.mjs +77 -0
  96. package/tests/node/tests/nodeApi.mjs +33 -1
  97. package/tests/node/tests/operations.mjs +26 -1
  98. package/tests/operations/index.mjs +17 -0
  99. package/tests/operations/tests/Arithmetic.mjs +33 -0
  100. package/tests/operations/tests/Ascon.mjs +501 -0
  101. package/tests/operations/tests/AutomatedValidation.mjs +154 -0
  102. package/tests/operations/tests/BLAKE3.mjs +39 -1
  103. package/tests/operations/tests/Base32.mjs +22 -0
  104. package/tests/operations/tests/COBS.mjs +476 -0
  105. package/tests/operations/tests/CharEnc.mjs +2 -2
  106. package/tests/operations/tests/DechunkHTTPResponse.mjs +66 -0
  107. package/tests/operations/tests/ExtendedGCD.mjs +78 -0
  108. package/tests/operations/tests/FromBase.mjs +66 -0
  109. package/tests/operations/tests/FromDecimal.mjs +55 -0
  110. package/tests/operations/tests/GenerateLoremIpsum.mjs +2 -2
  111. package/tests/operations/tests/Gzip.mjs +60 -0
  112. package/tests/operations/tests/HTMLEntity.mjs +126 -0
  113. package/tests/operations/tests/Hash.mjs +44 -0
  114. package/tests/operations/tests/Hexdump.mjs +1 -1
  115. package/tests/operations/tests/Image.mjs +26 -0
  116. package/tests/operations/tests/Jsonata.mjs +23 -0
  117. package/tests/operations/tests/MIMEDecoding.mjs +33 -0
  118. package/tests/operations/tests/MOD.mjs +208 -0
  119. package/tests/operations/tests/Median.mjs +33 -0
  120. package/tests/operations/tests/ModularInverse.mjs +78 -0
  121. package/tests/operations/tests/OTP.mjs +167 -2
  122. package/tests/operations/tests/PRESENT.mjs +465 -0
  123. package/tests/operations/tests/ParseTLV.mjs +41 -0
  124. package/tests/operations/tests/RenderPDF.mjs +55 -0
  125. package/tests/operations/tests/SM2.mjs +1 -1
  126. package/tests/operations/tests/SetDifference.mjs +22 -0
  127. package/tests/operations/tests/SetIntersection.mjs +23 -1
  128. package/tests/operations/tests/ShowOnMap.mjs +61 -0
  129. package/tests/operations/tests/TEA.mjs +566 -0
  130. package/tests/operations/tests/Twofish.mjs +486 -0
  131. package/tests/operations/tests/URLEncodeDecode.mjs +26 -0
  132. package/tests/operations/tests/UnescapeUnicodeCharacters.mjs +88 -0
  133. package/tests/operations/tests/Wrap.mjs +44 -0
  134. package/webpack.config.js +3 -3
@@ -0,0 +1,94 @@
1
+ /**
2
+ * @author Medjedtxm
3
+ * @copyright Crown Copyright 2026
4
+ * @license Apache-2.0
5
+ */
6
+
7
+ import Operation from "../Operation.mjs";
8
+ import Utils from "../Utils.mjs";
9
+ import OperationError from "../errors/OperationError.mjs";
10
+ import { toHex } from "../lib/Hex.mjs";
11
+ import { encryptPRESENT } from "../lib/Present.mjs";
12
+
13
+ /**
14
+ * PRESENT Encrypt operation
15
+ */
16
+ class PRESENTEncrypt extends Operation {
17
+
18
+ /**
19
+ * PRESENTEncrypt constructor
20
+ */
21
+ constructor() {
22
+ super();
23
+
24
+ this.name = "PRESENT Encrypt";
25
+ this.module = "Ciphers";
26
+ this.description = "PRESENT is an ultra-lightweight block cipher designed for constrained environments such as RFID tags and sensor networks. It operates on 64-bit blocks and supports 80-bit or 128-bit keys with 31 rounds. Standardised in ISO/IEC 29192-2:2019.<br><br>When using CBC mode, the PKCS#7 padding scheme is used.";
27
+ this.infoURL = "https://wikipedia.org/wiki/PRESENT_(cipher)";
28
+ this.inputType = "string";
29
+ this.outputType = "string";
30
+ this.args = [
31
+ {
32
+ "name": "Key",
33
+ "type": "toggleString",
34
+ "value": "",
35
+ "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
36
+ },
37
+ {
38
+ "name": "IV",
39
+ "type": "toggleString",
40
+ "value": "",
41
+ "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
42
+ },
43
+ {
44
+ "name": "Mode",
45
+ "type": "option",
46
+ "value": ["CBC", "ECB"]
47
+ },
48
+ {
49
+ "name": "Input",
50
+ "type": "option",
51
+ "value": ["Raw", "Hex"]
52
+ },
53
+ {
54
+ "name": "Output",
55
+ "type": "option",
56
+ "value": ["Hex", "Raw"]
57
+ },
58
+ {
59
+ "name": "Padding",
60
+ "type": "option",
61
+ "value": ["PKCS5", "NO", "ZERO", "RANDOM", "BIT"]
62
+ }
63
+ ];
64
+ }
65
+
66
+ /**
67
+ * @param {string} input
68
+ * @param {Object[]} args
69
+ * @returns {string}
70
+ */
71
+ run(input, args) {
72
+ const key = Utils.convertToByteArray(args[0].string, args[0].option),
73
+ iv = Utils.convertToByteArray(args[1].string, args[1].option),
74
+ [,, mode, inputType, outputType, padding] = args;
75
+
76
+ if (key.length !== 10 && key.length !== 16)
77
+ throw new OperationError(`Invalid key length: ${key.length} bytes
78
+
79
+ PRESENT uses a key length of 10 bytes (80 bits) or 16 bytes (128 bits).`);
80
+
81
+ if (iv.length !== 8 && mode !== "ECB")
82
+ throw new OperationError(`Invalid IV length: ${iv.length} bytes
83
+
84
+ PRESENT uses an IV length of 8 bytes (64 bits).
85
+ Make sure you have specified the type correctly (e.g. Hex vs UTF8).`);
86
+
87
+ input = Utils.convertToByteArray(input, inputType);
88
+ const output = encryptPRESENT(input, key, iv, mode, padding);
89
+ return outputType === "Hex" ? toHex(output, "") : Utils.byteArrayToUtf8(output);
90
+ }
91
+
92
+ }
93
+
94
+ export default PRESENTEncrypt;
@@ -33,7 +33,7 @@ class ParseURI extends Operation {
33
33
  * @returns {string}
34
34
  */
35
35
  run(input, args) {
36
- const uri = url.parse(input, true);
36
+ const uri = url.parse(input, false);
37
37
 
38
38
  let output = "";
39
39
 
@@ -43,7 +43,20 @@ class ParseURI extends Operation {
43
43
  if (uri.port) output += "Port:\t\t" + uri.port + "\n";
44
44
  if (uri.pathname) output += "Path name:\t" + uri.pathname + "\n";
45
45
  if (uri.query) {
46
- const keys = Object.keys(uri.query);
46
+ const queryObj = Object.create(null);
47
+ for (const [key, value] of new URLSearchParams(uri.query)) {
48
+ if (Object.prototype.hasOwnProperty.call(queryObj, key)) {
49
+ if (Array.isArray(queryObj[key])) {
50
+ queryObj[key].push(value);
51
+ } else {
52
+ queryObj[key] = [queryObj[key], value];
53
+ }
54
+ } else {
55
+ queryObj[key] = value;
56
+ }
57
+ }
58
+
59
+ const keys = Object.keys(queryObj);
47
60
  let padding = 0;
48
61
 
49
62
  keys.forEach(k => {
@@ -51,10 +64,10 @@ class ParseURI extends Operation {
51
64
  });
52
65
 
53
66
  output += "Arguments:\n";
54
- for (const key in uri.query) {
67
+ for (const key in queryObj) {
55
68
  output += "\t" + key.padEnd(padding, " ");
56
- if (uri.query[key].length) {
57
- output += " = " + uri.query[key] + "\n";
69
+ if (queryObj[key].length) {
70
+ output += " = " + queryObj[key] + "\n";
58
71
  } else {
59
72
  output += "\n";
60
73
  }
@@ -31,7 +31,8 @@ class PseudoRandomNumberGenerator extends Operation {
31
31
  {
32
32
  "name": "Number of bytes",
33
33
  "type": "number",
34
- "value": 32
34
+ "value": 32,
35
+ "min": 1
35
36
  },
36
37
  {
37
38
  "name": "Output as",
@@ -0,0 +1,154 @@
1
+ /**
2
+ * @author p-leriche [philip.leriche@cantab.net]
3
+ * @copyright Crown Copyright 2025
4
+ * @license Apache-2.0
5
+ */
6
+
7
+ import Operation from "../Operation.mjs";
8
+ import OperationError from "../errors/OperationError.mjs";
9
+ import { modPow } from "../lib/BigIntUtils.mjs";
10
+
11
+ /* ---------- helper functions ---------- */
12
+
13
+ /**
14
+ * Generate random BigInt with specified bit length
15
+ */
16
+ function randBigInt(bits) {
17
+ const bytes = Math.ceil(bits / 8);
18
+ const a = new Uint8Array(bytes);
19
+ crypto.getRandomValues(a);
20
+
21
+ // Set high bit to ensure correct bit length
22
+ a[0] |= 1 << (7 - ((8 * bytes - bits)));
23
+ // Set low bit to ensure odd (primes > 2 are odd)
24
+ a[bytes - 1] |= 1;
25
+
26
+ let h = "";
27
+ for (const b of a) h += b.toString(16).padStart(2, "0");
28
+ return BigInt("0x" + h);
29
+ }
30
+
31
+ /**
32
+ * Miller-Rabin primality test
33
+ */
34
+ function isProbablePrime(n, rounds) {
35
+ if (n < 2n) return false;
36
+ if (n === 2n || n === 3n) return true;
37
+ if (n % 2n === 0n) return false;
38
+
39
+ // Write n-1 as 2^r * d
40
+ let d = n - 1n;
41
+ let r = 0n;
42
+ while (d % 2n === 0n) {
43
+ d /= 2n;
44
+ r++;
45
+ }
46
+
47
+ // Witness loop
48
+ for (let i = 0; i < rounds; i++) {
49
+ const a = randBigInt(n.toString(2).length - 1) % (n - 3n) + 2n;
50
+ let x = modPow(a, d, n);
51
+
52
+ if (x === 1n || x === n - 1n) continue;
53
+
54
+ let composite = true;
55
+ for (let j = 0n; j < r - 1n; j++) {
56
+ x = modPow(x, 2n, n);
57
+ if (x === n - 1n) {
58
+ composite = false;
59
+ break;
60
+ }
61
+ }
62
+
63
+ if (composite) return false;
64
+ }
65
+
66
+ return true;
67
+ }
68
+
69
+ /* ---------- operation class ---------- */
70
+
71
+ /**
72
+ * Generate Prime Number operation
73
+ */
74
+ class GeneratePrime extends Operation {
75
+ /**
76
+ * GeneratePrime constructor
77
+ */
78
+ constructor() {
79
+ super();
80
+
81
+ this.name = "Pseudo-Random Prime Generator";
82
+ this.module = "Crypto";
83
+ this.description =
84
+ "Generates a random probable prime number of specified bit length using the Miller-Rabin primality test.<br><br>" +
85
+ "<b>Primality guarantee:</b><br>" +
86
+ "For numbers . 3,317, the result is guaranteed prime (deterministic test).<br>" +
87
+ "For larger numbers, uses probabilistic testing:<br>" +
88
+ "- <b>Standard (7 rounds):</b> Probability of composite approx 1 in 16,000)<br><br>" +
89
+ "- <b>Crypto grade (40 rounds):</b> Probability of composite(approx 1 in 10^24)<br>" +
90
+ "Crypto grade is recommended for cryptographic applications (RSA, Diffie-Hellman, etc.).<br><br>" ;
91
+ this.infoURL = "https://wikipedia.org/wiki/Miller-Rabin_primality_test";
92
+ this.inputType = "string";
93
+ this.outputType = "string";
94
+ this.args = [
95
+ {
96
+ name: "Bit length",
97
+ type: "number",
98
+ value: 512,
99
+ min: 2
100
+ },
101
+ {
102
+ name: "Crypto grade",
103
+ type: "boolean",
104
+ value: false
105
+ },
106
+ {
107
+ name: "Output format",
108
+ type: "option",
109
+ value: ["Decimal", "Hexadecimal"]
110
+ }
111
+ ];
112
+ }
113
+
114
+ /**
115
+ * @param {string} input
116
+ * @param {Object[]} args
117
+ * @returns {string}
118
+ */
119
+ run(input, args) {
120
+ const [bits, cryptoGrade, outputFormat] = args;
121
+
122
+ if (bits < 2) {
123
+ throw new OperationError("Bit length must be at least 2");
124
+ }
125
+
126
+ if (bits > 4096) {
127
+ throw new OperationError("Bit length limited to 4096 bits for performance reasons");
128
+ }
129
+
130
+ const rounds = cryptoGrade ? 40 : 7;
131
+ let attempts = 0;
132
+ const maxAttempts = 10000;
133
+
134
+ let n = randBigInt(bits);
135
+
136
+ while (!isProbablePrime(n, rounds)) {
137
+ n = randBigInt(bits);
138
+ attempts++;
139
+
140
+ if (attempts > maxAttempts) {
141
+ throw new OperationError(`Failed to generate prime after ${maxAttempts} attempts. Try a different bit length.`);
142
+ }
143
+ }
144
+
145
+ // Return only the prime for pipeability
146
+ if (outputFormat === "Hexadecimal") {
147
+ return "0x" + n.toString(16);
148
+ } else {
149
+ return n.toString();
150
+ }
151
+ }
152
+ }
153
+
154
+ export default GeneratePrime;
@@ -0,0 +1,100 @@
1
+ /**
2
+ * @author Shailendra [singhshailendra.in]
3
+ * @copyright Crown Copyright 2017
4
+ * @license Apache-2.0
5
+ */
6
+
7
+ import { fromBase64, toBase64 } from "../lib/Base64.mjs";
8
+ import Operation from "../Operation.mjs";
9
+ import OperationError from "../errors/OperationError.mjs";
10
+ import Utils from "../Utils.mjs";
11
+
12
+ /**
13
+ * Render PDF operation
14
+ */
15
+ class RenderPDF extends Operation {
16
+
17
+ /**
18
+ * RenderPDF constructor
19
+ */
20
+ constructor() {
21
+ super();
22
+
23
+ this.name = "Render PDF";
24
+ this.module = "File";
25
+ this.description = "Displays the input as a PDF preview. Supports Raw and Base64 input formats.";
26
+ this.inputType = "string";
27
+ this.outputType = "byteArray";
28
+ this.presentType = "html";
29
+ this.args = [
30
+ {
31
+ "name": "Input format",
32
+ "type": "option",
33
+ "value": ["Base64", "Raw"],
34
+ }
35
+ ];
36
+ this.checks = [
37
+ {
38
+ pattern: "^%PDF-",
39
+ flags: "",
40
+ args: ["Raw"],
41
+ useful: true,
42
+ output: {
43
+ mime: "application/pdf"
44
+ }
45
+ }
46
+ ];
47
+ }
48
+
49
+ /**
50
+ * @param {string} input
51
+ * @param {Object[]} args
52
+ * @returns {byteArray}
53
+ */
54
+ run(input, args) {
55
+ const inputFormat = args[0];
56
+
57
+ if (!input.length) return [];
58
+
59
+ // Convert input to raw bytes
60
+ switch (inputFormat) {
61
+ case "Base64":
62
+ input = fromBase64(input, undefined, "byteArray");
63
+ break;
64
+ case "Raw":
65
+ default:
66
+ input = Utils.strToByteArray(input);
67
+ break;
68
+ }
69
+
70
+ // Check PDF signature
71
+ if (
72
+ input[0] !== 0x25 || // %
73
+ input[1] !== 0x50 || // P
74
+ input[2] !== 0x44 || // D
75
+ input[3] !== 0x46 // F
76
+ ) {
77
+ throw new OperationError("Input does not appear to be a PDF file.");
78
+ }
79
+
80
+ return input;
81
+ }
82
+
83
+ /**
84
+ * Displays the PDF using HTML for web apps.
85
+ *
86
+ * @param {byteArray} data
87
+ * @returns {html}
88
+ */
89
+ async present(data) {
90
+ if (!data.length) return "";
91
+
92
+ const base64 = toBase64(data);
93
+ const dataURI = "data:application/pdf;base64," + base64;
94
+
95
+ return `<iframe src="${dataURI}" style="width:100%;height:100%;border:1px solid #ccc;"></iframe>`;
96
+ }
97
+
98
+ }
99
+
100
+ export default RenderPDF;
@@ -43,7 +43,7 @@ class SM4Encrypt extends Operation {
43
43
  {
44
44
  "name": "Mode",
45
45
  "type": "option",
46
- "value": ["CBC", "CFB", "OFB", "CTR", "ECB"]
46
+ "value": ["CBC", "CFB", "OFB", "CTR", "ECB", "CBC/NoPadding", "ECB/NoPadding"]
47
47
  },
48
48
  {
49
49
  "name": "Input",
@@ -75,9 +75,16 @@ class SetDifference extends Operation {
75
75
  * @returns {Object[]}
76
76
  */
77
77
  runSetDifference(a, b) {
78
+ const excluded = new Set(b);
79
+ const seen = new Set();
80
+
78
81
  return a
79
82
  .filter((item) => {
80
- return b.indexOf(item) === -1;
83
+ if (excluded.has(item) || seen.has(item)) {
84
+ return false;
85
+ }
86
+ seen.add(item);
87
+ return true;
81
88
  })
82
89
  .join(this.itemDelimiter);
83
90
  }
@@ -75,9 +75,16 @@ class SetIntersection extends Operation {
75
75
  * @returns {Object[]}
76
76
  */
77
77
  runIntersect(a, b) {
78
+ const included = new Set(b);
79
+ const seen = new Set();
80
+
78
81
  return a
79
82
  .filter((item) => {
80
- return b.indexOf(item) > -1;
83
+ if (!included.has(item) || seen.has(item)) {
84
+ return false;
85
+ }
86
+ seen.add(item);
87
+ return true;
81
88
  })
82
89
  .join(this.itemDelimiter);
83
90
  }
@@ -36,7 +36,8 @@ class ShowOnMap extends Operation {
36
36
  {
37
37
  name: "Input Format",
38
38
  type: "option",
39
- value: ["Auto"].concat(FORMATS)
39
+ value: ["Auto"].concat(FORMATS),
40
+ allowEmpty: false
40
41
  },
41
42
  {
42
43
  name: "Input Delimiter",
@@ -49,7 +50,8 @@ class ShowOnMap extends Operation {
49
50
  "Comma",
50
51
  "Semi-colon",
51
52
  "Colon"
52
- ]
53
+ ],
54
+ allowEmpty: false
53
55
  }
54
56
  ];
55
57
  }
@@ -71,6 +73,16 @@ class ShowOnMap extends Operation {
71
73
  }
72
74
  latLong = latLong.replace(/[,]$/, "");
73
75
  latLong = latLong.replace(/°/g, "");
76
+
77
+ // The map requires a latitude and longitude pair. If the conversion only produced a
78
+ // single value (e.g. because the chosen input delimiter didn't match the input), bail
79
+ // out with a helpful message rather than passing it on to the map, which would throw an
80
+ // uncaught TypeError in the browser.
81
+ const coords = latLong.split(",").map(v => v.trim());
82
+ if (coords.length !== 2 || coords.some(v => v === "" || isNaN(Number(v)))) {
83
+ throw new OperationError(`Could not show coordinates '${latLong}' on the map. Expected a latitude and longitude pair - check that the input format and delimiter are correct.`);
84
+ }
85
+
74
86
  return latLong;
75
87
  }
76
88
  return input;
@@ -0,0 +1,98 @@
1
+ /**
2
+ * @author Medjedtxm
3
+ * @copyright Crown Copyright 2026
4
+ * @license Apache-2.0
5
+ */
6
+
7
+ import Operation from "../Operation.mjs";
8
+ import Utils from "../Utils.mjs";
9
+ import OperationError from "../errors/OperationError.mjs";
10
+ import { toHex } from "../lib/Hex.mjs";
11
+ import { decryptTEA, TEA_BLOCK_SIZE } from "../lib/TEA.mjs";
12
+
13
+ /**
14
+ * TEA Decrypt operation
15
+ */
16
+ class TEADecrypt extends Operation {
17
+
18
+ /**
19
+ * TEADecrypt constructor
20
+ */
21
+ constructor() {
22
+ super();
23
+
24
+ this.name = "TEA Decrypt";
25
+ this.module = "Ciphers";
26
+ this.description = "TEA (Tiny Encryption Algorithm) is a block cipher designed by David Wheeler and Roger Needham in 1994. It operates on 64-bit blocks using a 128-bit key and performs 32 cycles (64 Feistel rounds) with the DELTA constant 0x9E3779B9 derived from the golden ratio.<br><br>TEA is notable for its simplicity and compact implementation, making it frequently encountered in malware analysis and CTF challenges. Despite its elegance, TEA has known weaknesses including equivalent keys and susceptibility to related-key attacks, leading to successors XTEA and XXTEA.<br><br><b>Key:</b> Must be exactly 16 bytes (128 bits).<br><br><b>IV:</b> The Initialisation Vector should be 8 bytes (64 bits). If not entered, it will default to null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, the PKCS#5 padding scheme is used.";
27
+ this.infoURL = "https://wikipedia.org/wiki/Tiny_Encryption_Algorithm";
28
+ this.inputType = "string";
29
+ this.outputType = "string";
30
+ this.args = [
31
+ {
32
+ "name": "Key",
33
+ "type": "toggleString",
34
+ "value": "",
35
+ "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
36
+ },
37
+ {
38
+ "name": "IV",
39
+ "type": "toggleString",
40
+ "value": "",
41
+ "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
42
+ },
43
+ {
44
+ "name": "Mode",
45
+ "type": "option",
46
+ "value": ["CBC", "CFB", "OFB", "CTR", "ECB"]
47
+ },
48
+ {
49
+ "name": "Input",
50
+ "type": "option",
51
+ "value": ["Hex", "Raw"]
52
+ },
53
+ {
54
+ "name": "Output",
55
+ "type": "option",
56
+ "value": ["Raw", "Hex"]
57
+ },
58
+ {
59
+ "name": "Padding",
60
+ "type": "option",
61
+ "value": ["PKCS5", "NO", "ZERO", "RANDOM", "BIT"]
62
+ }
63
+ ];
64
+ }
65
+
66
+ /**
67
+ * @param {string} input
68
+ * @param {Object[]} args
69
+ * @returns {string}
70
+ */
71
+ run(input, args) {
72
+ const key = Utils.convertToByteArray(args[0].string, args[0].option),
73
+ iv = Utils.convertToByteArray(args[1].string, args[1].option),
74
+ [,, mode, inputType, outputType, padding] = args;
75
+
76
+ if (key.length !== 16)
77
+ throw new OperationError(`Invalid key length: ${key.length} bytes
78
+
79
+ TEA requires a key length of 16 bytes (128 bits).
80
+ Make sure you have specified the type correctly (e.g. Hex vs UTF8).`);
81
+
82
+ if (iv.length !== TEA_BLOCK_SIZE && iv.length !== 0 && mode !== "ECB")
83
+ throw new OperationError(`Invalid IV length: ${iv.length} bytes
84
+
85
+ TEA uses an IV length of ${TEA_BLOCK_SIZE} bytes (${TEA_BLOCK_SIZE * 8} bits).
86
+ Make sure you have specified the type correctly (e.g. Hex vs UTF8).`);
87
+
88
+ // Default IV to null bytes if empty (like AES)
89
+ const actualIv = iv.length === 0 ? new Array(TEA_BLOCK_SIZE).fill(0) : iv;
90
+
91
+ input = Utils.convertToByteArray(input, inputType);
92
+ const output = decryptTEA(input, key, actualIv, mode, padding);
93
+ return outputType === "Hex" ? toHex(output, "") : Utils.byteArrayToUtf8(output);
94
+ }
95
+
96
+ }
97
+
98
+ export default TEADecrypt;
@@ -0,0 +1,98 @@
1
+ /**
2
+ * @author Medjedtxm
3
+ * @copyright Crown Copyright 2026
4
+ * @license Apache-2.0
5
+ */
6
+
7
+ import Operation from "../Operation.mjs";
8
+ import Utils from "../Utils.mjs";
9
+ import OperationError from "../errors/OperationError.mjs";
10
+ import { toHex } from "../lib/Hex.mjs";
11
+ import { encryptTEA, TEA_BLOCK_SIZE } from "../lib/TEA.mjs";
12
+
13
+ /**
14
+ * TEA Encrypt operation
15
+ */
16
+ class TEAEncrypt extends Operation {
17
+
18
+ /**
19
+ * TEAEncrypt constructor
20
+ */
21
+ constructor() {
22
+ super();
23
+
24
+ this.name = "TEA Encrypt";
25
+ this.module = "Ciphers";
26
+ this.description = "TEA (Tiny Encryption Algorithm) is a block cipher designed by David Wheeler and Roger Needham in 1994. It operates on 64-bit blocks using a 128-bit key and performs 32 cycles (64 Feistel rounds) with the DELTA constant 0x9E3779B9 derived from the golden ratio.<br><br>TEA is notable for its simplicity and compact implementation, making it frequently encountered in malware analysis and CTF challenges. Despite its elegance, TEA has known weaknesses including equivalent keys and susceptibility to related-key attacks, leading to successors XTEA and XXTEA.<br><br><b>Key:</b> Must be exactly 16 bytes (128 bits).<br><br><b>IV:</b> The Initialisation Vector should be 8 bytes (64 bits). If not entered, it will default to null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, the PKCS#5 padding scheme is used.";
27
+ this.infoURL = "https://wikipedia.org/wiki/Tiny_Encryption_Algorithm";
28
+ this.inputType = "string";
29
+ this.outputType = "string";
30
+ this.args = [
31
+ {
32
+ "name": "Key",
33
+ "type": "toggleString",
34
+ "value": "",
35
+ "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
36
+ },
37
+ {
38
+ "name": "IV",
39
+ "type": "toggleString",
40
+ "value": "",
41
+ "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
42
+ },
43
+ {
44
+ "name": "Mode",
45
+ "type": "option",
46
+ "value": ["CBC", "CFB", "OFB", "CTR", "ECB"]
47
+ },
48
+ {
49
+ "name": "Input",
50
+ "type": "option",
51
+ "value": ["Raw", "Hex"]
52
+ },
53
+ {
54
+ "name": "Output",
55
+ "type": "option",
56
+ "value": ["Hex", "Raw"]
57
+ },
58
+ {
59
+ "name": "Padding",
60
+ "type": "option",
61
+ "value": ["PKCS5", "NO", "ZERO", "RANDOM", "BIT"]
62
+ }
63
+ ];
64
+ }
65
+
66
+ /**
67
+ * @param {string} input
68
+ * @param {Object[]} args
69
+ * @returns {string}
70
+ */
71
+ run(input, args) {
72
+ const key = Utils.convertToByteArray(args[0].string, args[0].option),
73
+ iv = Utils.convertToByteArray(args[1].string, args[1].option),
74
+ [,, mode, inputType, outputType, padding] = args;
75
+
76
+ if (key.length !== 16)
77
+ throw new OperationError(`Invalid key length: ${key.length} bytes
78
+
79
+ TEA requires a key length of 16 bytes (128 bits).
80
+ Make sure you have specified the type correctly (e.g. Hex vs UTF8).`);
81
+
82
+ if (iv.length !== TEA_BLOCK_SIZE && iv.length !== 0 && mode !== "ECB")
83
+ throw new OperationError(`Invalid IV length: ${iv.length} bytes
84
+
85
+ TEA uses an IV length of ${TEA_BLOCK_SIZE} bytes (${TEA_BLOCK_SIZE * 8} bits).
86
+ Make sure you have specified the type correctly (e.g. Hex vs UTF8).`);
87
+
88
+ // Default IV to null bytes if empty (like AES)
89
+ const actualIv = iv.length === 0 ? new Array(TEA_BLOCK_SIZE).fill(0) : iv;
90
+
91
+ input = Utils.convertToByteArray(input, inputType);
92
+ const output = encryptTEA(input, key, actualIv, mode, padding);
93
+ return outputType === "Hex" ? toHex(output, "") : Utils.byteArrayToUtf8(output);
94
+ }
95
+
96
+ }
97
+
98
+ export default TEAEncrypt;