cyberchef 11.0.0 → 11.1.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 (63) hide show
  1. package/CHANGELOG.md +144 -0
  2. package/Dockerfile +2 -2
  3. package/Gruntfile.js +10 -5
  4. package/README.md +3 -1
  5. package/SECURITY.md +8 -18
  6. package/package.json +35 -34
  7. package/src/core/config/Categories.json +6 -0
  8. package/src/core/config/OperationConfig.json +140 -16
  9. package/src/core/config/modules/Default.mjs +8 -0
  10. package/src/core/config/modules/PGP.mjs +2 -0
  11. package/src/core/config/scripts/generateOpsIndex.mjs +63 -0
  12. package/src/core/config/scripts/newOperation.mjs +31 -4
  13. package/src/core/operations/AESDecrypt.mjs +61 -16
  14. package/src/core/operations/AESEncrypt.mjs +26 -11
  15. package/src/core/operations/BLAKE3.mjs +13 -7
  16. package/src/core/operations/BSONDeserialise.mjs +2 -2
  17. package/src/core/operations/BSONSerialise.mjs +3 -2
  18. package/src/core/operations/Bcrypt.mjs +1 -1
  19. package/src/core/operations/BcryptCompare.mjs +1 -1
  20. package/src/core/operations/DecodeText.mjs +4 -0
  21. package/src/core/operations/EncodeText.mjs +4 -0
  22. package/src/core/operations/EscapeSmartCharacters.mjs +129 -0
  23. package/src/core/operations/GenerateLoremIpsum.mjs +34 -3
  24. package/src/core/operations/GeneratePGPKeyPair.mjs +8 -7
  25. package/src/core/operations/PGPSign.mjs +83 -0
  26. package/src/core/operations/ParseEthernetFrame.mjs +1 -1
  27. package/src/core/operations/ParseIPv4Header.mjs +1 -1
  28. package/src/core/operations/ParseObjectIDTimestamp.mjs +2 -2
  29. package/src/core/operations/ParseUserAgent.mjs +1 -1
  30. package/src/core/operations/ROR13.mjs +83 -0
  31. package/src/core/operations/RemoveANSIEscapeCodes.mjs +41 -0
  32. package/src/core/operations/SeriesChart.mjs +16 -0
  33. package/src/core/operations/Wrap.mjs +47 -0
  34. package/src/core/operations/index.mjs +10 -0
  35. package/src/node/index.mjs +25 -0
  36. package/src/web/App.mjs +19 -1
  37. package/src/web/HTMLIngredient.mjs +1 -0
  38. package/src/web/html/index.html +3 -3
  39. package/src/web/static/sitemap.mjs +3 -3
  40. package/src/web/waiters/RecipeWaiter.mjs +9 -1
  41. package/tests/browser/02_ops.js +7 -7
  42. package/tests/browser/03_recipe_load.js +48 -0
  43. package/tests/browser/browserUtils.js +6 -3
  44. package/tests/node/index.mjs +1 -0
  45. package/tests/node/tests/PGP.mjs +69 -0
  46. package/tests/node/tests/operations.mjs +41 -2
  47. package/tests/operations/index.mjs +71 -66
  48. package/tests/operations/tests/BLAKE3.mjs +18 -0
  49. package/tests/operations/tests/CharEnc.mjs +26 -0
  50. package/tests/operations/tests/Charts.mjs +11 -0
  51. package/tests/operations/tests/Crypt.mjs +288 -62
  52. package/tests/operations/tests/EscapeSmartCharacters.mjs +132 -0
  53. package/tests/operations/tests/FlaskSession.mjs +11 -8
  54. package/tests/operations/tests/GenerateLoremIpsum.mjs +80 -0
  55. package/tests/operations/tests/IPv6Transition.mjs +4 -4
  56. package/tests/operations/tests/PGP.mjs +178 -154
  57. package/tests/operations/tests/ParseEthernetFrame.mjs +11 -0
  58. package/tests/operations/tests/ParseIPv4Header.mjs +23 -0
  59. package/tests/operations/tests/ParseX509CRL.mjs +16 -16
  60. package/tests/operations/tests/ROR13.mjs +45 -0
  61. package/tests/operations/tests/Register.mjs +3 -1
  62. package/tests/operations/tests/RemoveANSIEscapeCodes.mjs +62 -0
  63. package/tests/operations/tests/Wrap.mjs +44 -0
@@ -144,6 +144,11 @@
144
144
  "Base64"
145
145
  ]
146
146
  },
147
+ {
148
+ "name": "IV Length",
149
+ "type": "number",
150
+ "value": 16
151
+ },
147
152
  {
148
153
  "name": "Mode",
149
154
  "type": "argSelector",
@@ -151,57 +156,57 @@
151
156
  {
152
157
  "name": "CBC",
153
158
  "off": [
154
- 5,
155
- 6
159
+ 6,
160
+ 7
156
161
  ]
157
162
  },
158
163
  {
159
164
  "name": "CFB",
160
165
  "off": [
161
- 5,
162
- 6
166
+ 6,
167
+ 7
163
168
  ]
164
169
  },
165
170
  {
166
171
  "name": "OFB",
167
172
  "off": [
168
- 5,
169
- 6
173
+ 6,
174
+ 7
170
175
  ]
171
176
  },
172
177
  {
173
178
  "name": "CTR",
174
179
  "off": [
175
- 5,
176
- 6
180
+ 6,
181
+ 7
177
182
  ]
178
183
  },
179
184
  {
180
185
  "name": "GCM",
181
186
  "on": [
182
- 5,
183
- 6
187
+ 6,
188
+ 7
184
189
  ]
185
190
  },
186
191
  {
187
192
  "name": "ECB",
188
193
  "off": [
189
- 5,
190
- 6
194
+ 6,
195
+ 7
191
196
  ]
192
197
  },
193
198
  {
194
199
  "name": "CBC/NoPadding",
195
200
  "off": [
196
- 5,
197
- 6
201
+ 6,
202
+ 7
198
203
  ]
199
204
  },
200
205
  {
201
206
  "name": "ECB/NoPadding",
202
207
  "off": [
203
- 5,
204
- 6
208
+ 6,
209
+ 7
205
210
  ]
206
211
  }
207
212
  ]
@@ -243,6 +248,39 @@
243
248
  "Latin1",
244
249
  "Base64"
245
250
  ]
251
+ },
252
+ {
253
+ "name": "IV from input",
254
+ "type": "argSelector",
255
+ "value": [
256
+ {
257
+ "name": "Off",
258
+ "on": [
259
+ 1
260
+ ],
261
+ "off": [
262
+ 2
263
+ ]
264
+ },
265
+ {
266
+ "name": "From start",
267
+ "on": [
268
+ 2
269
+ ],
270
+ "off": [
271
+ 1
272
+ ]
273
+ },
274
+ {
275
+ "name": "From end",
276
+ "on": [
277
+ 2
278
+ ],
279
+ "off": [
280
+ 1
281
+ ]
282
+ }
283
+ ]
246
284
  }
247
285
  ]
248
286
  },
@@ -357,6 +395,15 @@
357
395
  "Latin1",
358
396
  "Base64"
359
397
  ]
398
+ },
399
+ {
400
+ "name": "Include IV in output",
401
+ "type": "option",
402
+ "value": [
403
+ "Off",
404
+ "Prepend",
405
+ "Append"
406
+ ]
360
407
  }
361
408
  ]
362
409
  },
@@ -7552,6 +7599,26 @@
7552
7599
  }
7553
7600
  ]
7554
7601
  },
7602
+ "Escape Smart Characters": {
7603
+ "module": "Default",
7604
+ "description": "Converts smart (typographic) Unicode characters — e.g. smart quotes, em/en dashes, ellipses, ©, ®, ™, arrows — into their plain ASCII equivalents.<br><br>Characters with no ASCII mapping (e.g. <code>☣</code>) are handled according to the 'Unmappable characters' option.<br><br>e.g. <code>“Hello” — world…</code> becomes <code>\"Hello\" -- world...</code>",
7605
+ "infoURL": "",
7606
+ "inputType": "string",
7607
+ "outputType": "string",
7608
+ "flowControl": false,
7609
+ "manualBake": false,
7610
+ "args": [
7611
+ {
7612
+ "name": "Unmappable characters",
7613
+ "type": "option",
7614
+ "value": [
7615
+ "Include",
7616
+ "Remove",
7617
+ "Replace with '.'"
7618
+ ]
7619
+ }
7620
+ ]
7621
+ },
7555
7622
  "Escape string": {
7556
7623
  "module": "Default",
7557
7624
  "description": "Escapes special characters in a string so that they do not cause conflicts. For example, <code>Don't stop me now</code> becomes <code>Don\\'t stop me now</code>.<br><br>Supports the following escape sequences:<ul><li><code>\\n</code> (Line feed/newline)</li><li><code>\\r</code> (Carriage return)</li><li><code>\\t</code> (Horizontal tab)</li><li><code>\\b</code> (Backspace)</li><li><code>\\f</code> (Form feed)</li><li><code>\\xnn</code> (Hex, where n is 0-f)</li><li><code>\\\\</code> (Backslash)</li><li><code>\\'</code> (Single quote)</li><li><code>\\&quot;</code> (Double quote)</li><li><code>\\unnnn</code> (Unicode character)</li><li><code>\\u{nnnnnn}</code> (Unicode code point)</li></ul>",
@@ -13143,6 +13210,27 @@
13143
13210
  }
13144
13211
  ]
13145
13212
  },
13213
+ "PGP Sign": {
13214
+ "module": "PGP",
13215
+ "description": "Input: the message you want to sign\n<br><br>\nArguments: the ASCII-armoured PGP private key of the sender.\n<br><br>\nPretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.\n<br><br>\nThis function uses the Keybase implementation of PGP.",
13216
+ "infoURL": "https://wikipedia.org/wiki/Pretty_Good_Privacy",
13217
+ "inputType": "string",
13218
+ "outputType": "string",
13219
+ "flowControl": false,
13220
+ "manualBake": false,
13221
+ "args": [
13222
+ {
13223
+ "name": "Private key of signer",
13224
+ "type": "text",
13225
+ "value": ""
13226
+ },
13227
+ {
13228
+ "name": "Private key passphrase (optional)",
13229
+ "type": "string",
13230
+ "value": ""
13231
+ }
13232
+ ]
13233
+ },
13146
13234
  "PGP Verify": {
13147
13235
  "module": "PGP",
13148
13236
  "description": "Input: the ASCII-armoured encrypted PGP message you want to verify.\n<br><br>\nArgument: the ASCII-armoured PGP public key of the signer\n<br><br>\nThis operation uses PGP to decrypt a clearsigned message.\n<br><br>\nPretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.\n<br><br>\nThis function uses the Keybase implementation of PGP.",
@@ -14918,6 +15006,16 @@
14918
15006
  }
14919
15007
  ]
14920
15008
  },
15009
+ "ROR13": {
15010
+ "module": "Default",
15011
+ "description": "Computes a ROR13 hash used in API hashing techniques.",
15012
+ "infoURL": "",
15013
+ "inputType": "byteArray",
15014
+ "outputType": "string",
15015
+ "flowControl": false,
15016
+ "manualBake": false,
15017
+ "args": []
15018
+ },
14921
15019
  "ROT13": {
14922
15020
  "module": "Default",
14923
15021
  "description": "A simple caesar substitution cipher which rotates alphabet characters by the specified amount (default 13).",
@@ -15574,6 +15672,16 @@
15574
15672
  }
15575
15673
  ]
15576
15674
  },
15675
+ "Remove ANSI Escape Codes": {
15676
+ "module": "Default",
15677
+ "description": "Removes ANSI Escape Codes.",
15678
+ "infoURL": "https://wikipedia.org/wiki/ANSI_escape_code",
15679
+ "inputType": "string",
15680
+ "outputType": "string",
15681
+ "flowControl": false,
15682
+ "manualBake": false,
15683
+ "args": []
15684
+ },
15577
15685
  "Remove Diacritics": {
15578
15686
  "module": "Default",
15579
15687
  "description": "Replaces accented characters with their latin character equivalent. Accented characters are made up of Unicode combining characters, so unicode text formatting such as strikethroughs and underlines will also be removed.",
@@ -21895,6 +22003,22 @@
21895
22003
  }
21896
22004
  ]
21897
22005
  },
22006
+ "Wrap": {
22007
+ "module": "Default",
22008
+ "description": "Wraps the input text at a specified number of characters per line.",
22009
+ "infoURL": null,
22010
+ "inputType": "string",
22011
+ "outputType": "string",
22012
+ "flowControl": false,
22013
+ "manualBake": false,
22014
+ "args": [
22015
+ {
22016
+ "name": "Line Width",
22017
+ "type": "number",
22018
+ "value": 64
22019
+ }
22020
+ ]
22021
+ },
21898
22022
  "XKCD Random Number": {
21899
22023
  "module": "Default",
21900
22024
  "description": "RFC 1149.5 specifies 4 as the standard IEEE-vetted random number.",
@@ -41,6 +41,7 @@ import DropBytes from "../../operations/DropBytes.mjs";
41
41
  import DropNthBytes from "../../operations/DropNthBytes.mjs";
42
42
  import ELFInfo from "../../operations/ELFInfo.mjs";
43
43
  import EncodeNetBIOSName from "../../operations/EncodeNetBIOSName.mjs";
44
+ import EscapeSmartCharacters from "../../operations/EscapeSmartCharacters.mjs";
44
45
  import EscapeString from "../../operations/EscapeString.mjs";
45
46
  import EscapeUnicodeCharacters from "../../operations/EscapeUnicodeCharacters.mjs";
46
47
  import ExpandAlphabetRange from "../../operations/ExpandAlphabetRange.mjs";
@@ -131,11 +132,13 @@ import ParseUNIXFilePermissions from "../../operations/ParseUNIXFilePermissions.
131
132
  import PlayMedia from "../../operations/PlayMedia.mjs";
132
133
  import PowerSet from "../../operations/PowerSet.mjs";
133
134
  import RAKE from "../../operations/RAKE.mjs";
135
+ import ROR13 from "../../operations/ROR13.mjs";
134
136
  import ROT13 from "../../operations/ROT13.mjs";
135
137
  import ROT13BruteForce from "../../operations/ROT13BruteForce.mjs";
136
138
  import ROT47 from "../../operations/ROT47.mjs";
137
139
  import ROT47BruteForce from "../../operations/ROT47BruteForce.mjs";
138
140
  import ROT8000 from "../../operations/ROT8000.mjs";
141
+ import RemoveANSIEscapeCodes from "../../operations/RemoveANSIEscapeCodes.mjs";
139
142
  import RemoveDiacritics from "../../operations/RemoveDiacritics.mjs";
140
143
  import RemoveLineNumbers from "../../operations/RemoveLineNumbers.mjs";
141
144
  import RemoveNullBytes from "../../operations/RemoveNullBytes.mjs";
@@ -207,6 +210,7 @@ import Unique from "../../operations/Unique.mjs";
207
210
  import VarIntDecode from "../../operations/VarIntDecode.mjs";
208
211
  import VarIntEncode from "../../operations/VarIntEncode.mjs";
209
212
  import WindowsFiletimeToUNIXTimestamp from "../../operations/WindowsFiletimeToUNIXTimestamp.mjs";
213
+ import Wrap from "../../operations/Wrap.mjs";
210
214
  import XKCDRandomNumber from "../../operations/XKCDRandomNumber.mjs";
211
215
  import XOR from "../../operations/XOR.mjs";
212
216
  import XORBruteForce from "../../operations/XORBruteForce.mjs";
@@ -251,6 +255,7 @@ OpModules.Default = {
251
255
  "Drop nth bytes": DropNthBytes,
252
256
  "ELF Info": ELFInfo,
253
257
  "Encode NetBIOS Name": EncodeNetBIOSName,
258
+ "Escape Smart Characters": EscapeSmartCharacters,
254
259
  "Escape string": EscapeString,
255
260
  "Escape Unicode Characters": EscapeUnicodeCharacters,
256
261
  "Expand alphabet range": ExpandAlphabetRange,
@@ -341,11 +346,13 @@ OpModules.Default = {
341
346
  "Play Media": PlayMedia,
342
347
  "Power Set": PowerSet,
343
348
  "RAKE": RAKE,
349
+ "ROR13": ROR13,
344
350
  "ROT13": ROT13,
345
351
  "ROT13 Brute Force": ROT13BruteForce,
346
352
  "ROT47": ROT47,
347
353
  "ROT47 Brute Force": ROT47BruteForce,
348
354
  "ROT8000": ROT8000,
355
+ "Remove ANSI Escape Codes": RemoveANSIEscapeCodes,
349
356
  "Remove Diacritics": RemoveDiacritics,
350
357
  "Remove line numbers": RemoveLineNumbers,
351
358
  "Remove null bytes": RemoveNullBytes,
@@ -417,6 +424,7 @@ OpModules.Default = {
417
424
  "VarInt Decode": VarIntDecode,
418
425
  "VarInt Encode": VarIntEncode,
419
426
  "Windows Filetime to UNIX Timestamp": WindowsFiletimeToUNIXTimestamp,
427
+ "Wrap": Wrap,
420
428
  "XKCD Random Number": XKCDRandomNumber,
421
429
  "XOR": XOR,
422
430
  "XOR Brute Force": XORBruteForce,
@@ -10,6 +10,7 @@ import PGPDecrypt from "../../operations/PGPDecrypt.mjs";
10
10
  import PGPDecryptAndVerify from "../../operations/PGPDecryptAndVerify.mjs";
11
11
  import PGPEncrypt from "../../operations/PGPEncrypt.mjs";
12
12
  import PGPEncryptAndSign from "../../operations/PGPEncryptAndSign.mjs";
13
+ import PGPSign from "../../operations/PGPSign.mjs";
13
14
  import PGPVerify from "../../operations/PGPVerify.mjs";
14
15
 
15
16
  const OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
@@ -20,6 +21,7 @@ OpModules.PGP = {
20
21
  "PGP Decrypt and Verify": PGPDecryptAndVerify,
21
22
  "PGP Encrypt": PGPEncrypt,
22
23
  "PGP Encrypt and Sign": PGPEncryptAndSign,
24
+ "PGP Sign": PGPSign,
23
25
  "PGP Verify": PGPVerify,
24
26
  };
25
27
 
@@ -58,3 +58,66 @@ fs.writeFileSync(
58
58
  code
59
59
  );
60
60
  console.log("Written operation index.");
61
+
62
+ // find all test files
63
+ const testsDir = path.join(process.cwd() + "/tests/operations/tests/");
64
+ const testObjs = [];
65
+ fs.readdirSync(testsDir).forEach(file => {
66
+ if (!file.endsWith(".mjs")) return;
67
+ testObjs.push(file.split(".mjs")[0]);
68
+ });
69
+
70
+ // Construct test index file
71
+ code = `/**
72
+ * THIS FILE IS AUTOMATICALLY GENERATED BY src/core/config/scripts/generateOpsIndex.mjs
73
+ *
74
+ * @author john [john19696@protonmail.com]
75
+ * @author tlwr [toby@toby.codes]
76
+ * @author n1474335 [n1474335@gmail.com]
77
+ * @copyright Crown Copyright ${new Date().getUTCFullYear()}
78
+ * @license Apache-2.0
79
+ */
80
+
81
+ import {
82
+ setLongTestFailure,
83
+ logTestReport,
84
+ } from "../lib/utils.mjs";
85
+
86
+ import "../lib/wasmFetchPolyfill.mjs";
87
+
88
+ import TestRegister from "../lib/TestRegister.mjs";
89
+ `;
90
+
91
+ testObjs.forEach(obj => {
92
+ if (obj !== "SplitColourChannels")
93
+ code += `import "./tests/${obj}.mjs";\n`;
94
+ else
95
+ code += `// Cannot test operations that use the File type yet
96
+ // import "./tests/SplitColourChannels.mjs";\n`;
97
+ });
98
+
99
+ code += `
100
+
101
+ const testStatus = {
102
+ allTestsPassing: true,
103
+ counts: {
104
+ total: 0,
105
+ }
106
+ };
107
+
108
+ setLongTestFailure();
109
+
110
+ const logOpsTestReport = logTestReport.bind(null, testStatus);
111
+
112
+ (async function() {
113
+ const results = await TestRegister.runTests();
114
+ logOpsTestReport(results);
115
+ })();
116
+ `;
117
+
118
+ // Write tests file
119
+ fs.writeFileSync(
120
+ path.join(testsDir, "../index.mjs"),
121
+ code
122
+ );
123
+ console.log("Written operation tests index.");
@@ -23,7 +23,7 @@ if (!fs.existsSync(dir)) {
23
23
  console.log("Example> node --experimental-modules src/core/config/scripts/newOperation.mjs");
24
24
  process.exit(1);
25
25
  }
26
-
26
+ const testDir = path.join(process.cwd() + "/tests/operations/tests/");
27
27
  const ioTypes = ["string", "byteArray", "number", "html", "ArrayBuffer", "BigNumber", "JSON", "File", "List<File>"];
28
28
 
29
29
  const schema = {
@@ -123,6 +123,30 @@ prompt.get(schema, (err, result) => {
123
123
  return txt.charAt(0).toUpperCase() + txt.substr(1);
124
124
  }).replace(/[\s-()./]/g, "");
125
125
 
126
+ const testTemplate = `/**
127
+ * ${moduleName} tests
128
+ *
129
+ * @author ${result.authorName} [${result.authorEmail}]
130
+ * @copyright Crown Copyright ${(new Date()).getFullYear()}
131
+ * @license Apache-2.0
132
+ */
133
+
134
+ import TestRegister from "../../lib/TestRegister.mjs";
135
+
136
+ TestRegister.addTests([
137
+ {
138
+ name: "${result.opName}: test",
139
+ input: "Example input",
140
+ expectedOutput: "Expected output",
141
+ recipeConfig: [
142
+ {
143
+ op: "${result.opName}",
144
+ args: [],
145
+ },
146
+ ],
147
+ },
148
+ ]);
149
+ `;
126
150
 
127
151
  const template = `/**
128
152
  * @author ${result.authorName} [${result.authorEmail}]
@@ -218,13 +242,16 @@ export default ${moduleName};
218
242
  }
219
243
  fs.writeFileSync(filename, template);
220
244
 
245
+ const testFilename = path.join(testDir, `./${moduleName}.mjs`);
246
+ fs.writeFileSync(testFilename, testTemplate);
247
+
221
248
  console.log(`\nOperation template written to ${colors.green(filename)}`);
249
+ console.log(`\nOperation test template written to ${colors.green(testFilename)}`);
222
250
  console.log(`\nNext steps:
223
251
  1. Add your operation to ${colors.green("src/core/config/Categories.json")}
224
- 2. Write your operation code.
225
- 3. Write tests in ${colors.green("tests/operations/tests/")}
252
+ 2. Write your operation code in ${colors.green(filename)}
253
+ 3. Write your operation test code in ${colors.green(testFilename)}
226
254
  4. Run ${colors.cyan("npm run lint")} and ${colors.cyan("npm run test")}
227
255
  5. Submit a Pull Request to get your operation added to the official CyberChef repository.`);
228
256
 
229
257
  });
230
-
@@ -39,41 +39,46 @@ class AESDecrypt extends Operation {
39
39
  "value": "",
40
40
  "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
41
41
  },
42
+ {
43
+ "name": "IV Length",
44
+ "type": "number",
45
+ "value": 16
46
+ },
42
47
  {
43
48
  "name": "Mode",
44
49
  "type": "argSelector",
45
50
  "value": [
46
51
  {
47
52
  name: "CBC",
48
- off: [5, 6]
53
+ off: [6, 7]
49
54
  },
50
55
  {
51
56
  name: "CFB",
52
- off: [5, 6]
57
+ off: [6, 7]
53
58
  },
54
59
  {
55
60
  name: "OFB",
56
- off: [5, 6]
61
+ off: [6, 7]
57
62
  },
58
63
  {
59
64
  name: "CTR",
60
- off: [5, 6]
65
+ off: [6, 7]
61
66
  },
62
67
  {
63
68
  name: "GCM",
64
- on: [5, 6]
69
+ on: [6, 7]
65
70
  },
66
71
  {
67
72
  name: "ECB",
68
- off: [5, 6]
73
+ off: [6, 7]
69
74
  },
70
75
  {
71
76
  name: "CBC/NoPadding",
72
- off: [5, 6]
77
+ off: [6, 7]
73
78
  },
74
79
  {
75
80
  name: "ECB/NoPadding",
76
- off: [5, 6]
81
+ off: [6, 7]
77
82
  }
78
83
  ]
79
84
  },
@@ -98,6 +103,26 @@ class AESDecrypt extends Operation {
98
103
  "type": "toggleString",
99
104
  "value": "",
100
105
  "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
106
+ },
107
+ {
108
+ "name": "IV from input",
109
+ "type": "argSelector",
110
+ "value": [
111
+ {
112
+ name: "Off",
113
+ on: [1],
114
+ off: [2]
115
+ },
116
+ {
117
+ name: "From start",
118
+ on: [2],
119
+ off: [1]
120
+ }, {
121
+ name: "From end",
122
+ on: [2],
123
+ off: [1]
124
+ }
125
+ ]
101
126
  }
102
127
  ];
103
128
  }
@@ -110,14 +135,18 @@ class AESDecrypt extends Operation {
110
135
  * @throws {OperationError} if cannot decrypt input or invalid key length
111
136
  */
112
137
  run(input, args) {
138
+ let iv;
139
+
113
140
  const key = Utils.convertToByteString(args[0].string, args[0].option),
114
- iv = Utils.convertToByteString(args[1].string, args[1].option),
115
- mode = args[2].split("/")[0],
116
- noPadding = args[2].endsWith("NoPadding"),
117
- inputType = args[3],
118
- outputType = args[4],
119
- gcmTag = Utils.convertToByteString(args[5].string, args[5].option),
120
- aad = Utils.convertToByteString(args[6].string, args[6].option);
141
+ ivLength = args[2],
142
+ mode = args[3].split("/")[0],
143
+ noPadding = args[3].endsWith("NoPadding"),
144
+ inputType = args[4],
145
+ outputType = args[5],
146
+ gcmTag = Utils.convertToByteString(args[6].string, args[6].option),
147
+ aad = Utils.convertToByteString(args[7].string, args[7].option),
148
+ ivFromInput = args[8];
149
+
121
150
 
122
151
  if ([16, 24, 32].indexOf(key.length) < 0) {
123
152
  throw new OperationError(`Invalid key length: ${key.length} bytes
@@ -130,11 +159,27 @@ The following algorithms will be used based on the size of the key:
130
159
 
131
160
  input = Utils.convertToByteString(input, inputType);
132
161
 
162
+ if (ivFromInput !== "Off") {
163
+ if (input.length <= ivLength) {
164
+ throw new OperationError(`Input is too short to contain an IV of ${ivLength} bytes.`);
165
+ }
166
+
167
+ if (ivFromInput === "From start") {
168
+ iv = input.substr(0, ivLength);
169
+ input = input.substr(ivLength);
170
+ } else {
171
+ iv = input.substr(input.length - ivLength);
172
+ input = input.substr(0, input.length - ivLength);
173
+ }
174
+ } else {
175
+ iv = Utils.convertToByteString(args[1].string, args[1].option);
176
+ }
177
+
133
178
  const decipher = forge.cipher.createDecipher("AES-" + mode, key);
134
179
 
135
180
  /* Allow for a "no padding" mode */
136
181
  if (noPadding) {
137
- decipher.mode.unpad = function(output, options) {
182
+ decipher.mode.unpad = function (output, options) {
138
183
  return true;
139
184
  };
140
185
  }
@@ -8,6 +8,7 @@ import Operation from "../Operation.mjs";
8
8
  import Utils from "../Utils.mjs";
9
9
  import forge from "node-forge";
10
10
  import OperationError from "../errors/OperationError.mjs";
11
+ import { toHexFast } from "../lib/Hex.mjs";
11
12
 
12
13
  /**
13
14
  * AES Encrypt operation
@@ -92,6 +93,11 @@ class AESEncrypt extends Operation {
92
93
  "type": "toggleString",
93
94
  "value": "",
94
95
  "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
96
+ },
97
+ {
98
+ "name": "Include IV in output",
99
+ "type": "option",
100
+ "value": ["Off", "Prepend", "Append"]
95
101
  }
96
102
  ];
97
103
  }
@@ -107,10 +113,11 @@ class AESEncrypt extends Operation {
107
113
  const key = Utils.convertToByteString(args[0].string, args[0].option),
108
114
  iv = Utils.convertToByteString(args[1].string, args[1].option),
109
115
  mode = args[2].split("/")[0],
110
- noPadding = args[2].endsWith("NoPadding"),
116
+ noPadding = args[2].endsWith("NoPadding"),
111
117
  inputType = args[3],
112
118
  outputType = args[4],
113
- aad = Utils.convertToByteString(args[5].string, args[5].option);
119
+ aad = Utils.convertToByteString(args[5].string, args[5].option),
120
+ includeIV = args[6];
114
121
 
115
122
  if ([16, 24, 32].indexOf(key.length) < 0) {
116
123
  throw new OperationError(`Invalid key length: ${key.length} bytes
@@ -133,26 +140,34 @@ The following algorithms will be used based on the size of the key:
133
140
  additionalData: mode === "GCM" ? aad : undefined
134
141
  });
135
142
  if (noPadding) {
136
- cipher.mode.pad = function(output, options) {
143
+ cipher.mode.pad = function (output, options) {
137
144
  return true;
138
145
  };
139
146
  }
140
147
  cipher.update(forge.util.createBuffer(input));
141
148
  cipher.finish();
142
149
 
150
+ let output = cipher.output.getBytes();
151
+
152
+ if (includeIV === "Prepend") {
153
+ output = iv + output;
154
+ } else if (includeIV === "Append") {
155
+ output = output + iv;
156
+ }
157
+
143
158
  if (outputType === "Hex") {
159
+ output = toHexFast(Utils.strToByteArray(output));
160
+
144
161
  if (mode === "GCM") {
145
- return cipher.output.toHex() + "\n\n" +
162
+ return output + "\n\n" +
146
163
  "Tag: " + cipher.mode.tag.toHex();
147
164
  }
148
- return cipher.output.toHex();
149
- } else {
150
- if (mode === "GCM") {
151
- return cipher.output.getBytes() + "\n\n" +
152
- "Tag: " + cipher.mode.tag.getBytes();
153
- }
154
- return cipher.output.getBytes();
165
+ } else if (mode === "GCM") {
166
+ return output + "\n\n" +
167
+ "Tag: " + cipher.mode.tag.getBytes();
155
168
  }
169
+
170
+ return output;
156
171
  }
157
172
 
158
173
  }