mirror of
https://github.com/gchq/CyberChef
synced 2025-12-28 14:13:51 +00:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17cf154bc2 | ||
|
|
c7f6954b97 | ||
|
|
9ccc1613cf | ||
|
|
9730ce1f6a | ||
|
|
55a7981547 | ||
|
|
2d99c365dd | ||
|
|
59d8be511a | ||
|
|
8349ffc001 | ||
|
|
c1368c4ecb | ||
|
|
c6935e040d | ||
|
|
f79c3ae91a | ||
|
|
bc27cd2772 | ||
|
|
2b02c44ca4 | ||
|
|
59fe8d1c4b | ||
|
|
9a5d62c4c3 | ||
|
|
9fa82150ee | ||
|
|
d7561ec208 | ||
|
|
743b834f6d | ||
|
|
0658836f87 | ||
|
|
a4e20c7059 | ||
|
|
c04f409d23 | ||
|
|
1a9833132d | ||
|
|
9c3ddca269 | ||
|
|
39143fa6a1 | ||
|
|
1e83e0e935 | ||
|
|
c046cf5695 | ||
|
|
3086c25079 | ||
|
|
3700780d14 | ||
|
|
58b1fb8de5 |
@@ -13,6 +13,9 @@ All major and minor version changes will be documented in this file. Details of
|
||||
|
||||
## Details
|
||||
|
||||
### [9.50.0] - 2022-11-25
|
||||
- Added 'Shuffle' operation [@mikecat] | [#1472]
|
||||
|
||||
### [9.49.0] - 2022-11-11
|
||||
- Added 'LZ4 Compress' and 'LZ4 Decompress' operations [@n1474335] | [31a7f83]
|
||||
|
||||
@@ -324,6 +327,7 @@ All major and minor version changes will be documented in this file. Details of
|
||||
|
||||
|
||||
|
||||
[9.50.0]: https://github.com/gchq/CyberChef/releases/tag/v9.50.0
|
||||
[9.49.0]: https://github.com/gchq/CyberChef/releases/tag/v9.49.0
|
||||
[9.48.0]: https://github.com/gchq/CyberChef/releases/tag/v9.48.0
|
||||
[9.47.0]: https://github.com/gchq/CyberChef/releases/tag/v9.47.0
|
||||
@@ -568,4 +572,5 @@ All major and minor version changes will be documented in this file. Details of
|
||||
[#1308]: https://github.com/gchq/CyberChef/pull/1308
|
||||
[#1421]: https://github.com/gchq/CyberChef/pull/1421
|
||||
[#1427]: https://github.com/gchq/CyberChef/pull/1427
|
||||
[#1472]: https://github.com/gchq/CyberChef/pull/1472
|
||||
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "cyberchef",
|
||||
"version": "9.49.0",
|
||||
"version": "9.50.5",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "cyberchef",
|
||||
"version": "9.49.0",
|
||||
"version": "9.50.5",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cyberchef",
|
||||
"version": "9.49.0",
|
||||
"version": "9.50.5",
|
||||
"description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
|
||||
"author": "n1474335 <n1474335@gmail.com>",
|
||||
"homepage": "https://gchq.github.io/CyberChef",
|
||||
|
||||
@@ -249,6 +249,7 @@
|
||||
"To Table",
|
||||
"Reverse",
|
||||
"Sort",
|
||||
"Shuffle",
|
||||
"Unique",
|
||||
"Split",
|
||||
"Filter",
|
||||
|
||||
@@ -136,7 +136,7 @@ const getFeature = function() {
|
||||
|
||||
fs.writeFileSync(path.join(process.cwd(), "CHANGELOG.md"), changelogData);
|
||||
|
||||
console.log("Written CHANGELOG.md");
|
||||
console.log("Written CHANGELOG.md\nCommit changes and then run `npm version minor`.");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -105,13 +105,17 @@ export function fromHex(data, delim="Auto", byteLen=2) {
|
||||
throw new OperationError("Byte length must be a positive integer");
|
||||
|
||||
if (delim !== "None") {
|
||||
const delimRegex = delim === "Auto" ? /[^a-f\d]|(0x)/gi : Utils.regexRep(delim);
|
||||
data = data.replace(delimRegex, "");
|
||||
const delimRegex = delim === "Auto" ? /[^a-f\d]|0x/gi : Utils.regexRep(delim);
|
||||
data = data.split(delimRegex);
|
||||
} else {
|
||||
data = [data];
|
||||
}
|
||||
|
||||
const output = [];
|
||||
for (let i = 0; i < data.length; i += byteLen) {
|
||||
output.push(parseInt(data.substr(i, byteLen), 16));
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
for (let j = 0; j < data[i].length; j += byteLen) {
|
||||
output.push(parseInt(data[i].substr(j, byteLen), 16));
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ class ParseASN1HexString extends Operation {
|
||||
*/
|
||||
run(input, args) {
|
||||
const [index, truncateLen] = args;
|
||||
return r.ASN1HEX.dump(input.replace(/\s/g, ""), {
|
||||
return r.ASN1HEX.dump(input.replace(/\s/g, "").toLowerCase(), {
|
||||
"ommitLongOctet": truncateLen
|
||||
}, index);
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ class ParseX509Certificate extends Operation {
|
||||
|
||||
switch (inputFormat) {
|
||||
case "DER Hex":
|
||||
input = input.replace(/\s/g, "");
|
||||
input = input.replace(/\s/g, "").toLowerCase();
|
||||
cert.readCertHex(input);
|
||||
break;
|
||||
case "PEM":
|
||||
|
||||
@@ -52,8 +52,12 @@ class PseudoRandomNumberGenerator extends Operation {
|
||||
let bytes;
|
||||
|
||||
if (isWorkerEnvironment() && self.crypto) {
|
||||
bytes = self.crypto.getRandomValues(new Uint8Array(numBytes));
|
||||
bytes = Utils.arrayBufferToStr(bytes.buffer);
|
||||
bytes = new ArrayBuffer(numBytes);
|
||||
const CHUNK_SIZE = 65536;
|
||||
for (let i = 0; i < numBytes; i += CHUNK_SIZE) {
|
||||
self.crypto.getRandomValues(new Uint8Array(bytes, i, Math.min(numBytes - i, CHUNK_SIZE)));
|
||||
}
|
||||
bytes = Utils.arrayBufferToStr(bytes);
|
||||
} else {
|
||||
bytes = forge.random.getBytesSync(numBytes);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
|
||||
/**
|
||||
* Reverse operation
|
||||
@@ -26,7 +27,8 @@ class Reverse extends Operation {
|
||||
{
|
||||
"name": "By",
|
||||
"type": "option",
|
||||
"value": ["Character", "Line"]
|
||||
"value": ["Byte", "Character", "Line"],
|
||||
"defaultIndex": 1
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -57,6 +59,24 @@ class Reverse extends Operation {
|
||||
result.push(0x0a);
|
||||
}
|
||||
return result.slice(0, input.length);
|
||||
} else if (args[0] === "Character") {
|
||||
const inputString = Utils.byteArrayToUtf8(input);
|
||||
let result = "";
|
||||
for (let i = inputString.length - 1; i >= 0; i--) {
|
||||
const c = inputString.charCodeAt(i);
|
||||
if (i > 0 && 0xdc00 <= c && c <= 0xdfff) {
|
||||
const c2 = inputString.charCodeAt(i - 1);
|
||||
if (0xd800 <= c2 && c2 <= 0xdbff) {
|
||||
// surrogates
|
||||
result += inputString.charAt(i - 1);
|
||||
result += inputString.charAt(i);
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
result += inputString.charAt(i);
|
||||
}
|
||||
return Utils.strToUtf8ByteArray(result);
|
||||
} else {
|
||||
return input.reverse();
|
||||
}
|
||||
|
||||
78
src/core/operations/Shuffle.mjs
Normal file
78
src/core/operations/Shuffle.mjs
Normal file
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* @author mikecat
|
||||
* @copyright Crown Copyright 2022
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import {INPUT_DELIM_OPTIONS} from "../lib/Delim.mjs";
|
||||
|
||||
/**
|
||||
* Shuffle operation
|
||||
*/
|
||||
class Shuffle extends Operation {
|
||||
|
||||
/**
|
||||
* Shuffle constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Shuffle";
|
||||
this.module = "Default";
|
||||
this.description = "Randomly reorders input elements.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Shuffling";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Delimiter",
|
||||
type: "option",
|
||||
value: INPUT_DELIM_OPTIONS
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const delim = Utils.charRep(args[0]);
|
||||
if (input.length === 0) return input;
|
||||
|
||||
// return a random number in [0, 1)
|
||||
const rng = (typeof crypto) !== "undefined" && crypto.getRandomValues ? (function() {
|
||||
const buf = new Uint32Array(2);
|
||||
return function() {
|
||||
// generate 53-bit random integer: 21 + 32 bits
|
||||
crypto.getRandomValues(buf);
|
||||
const value = (buf[0] >>> (32 - 21)) * ((1 << 30) * 4) + buf[1];
|
||||
return value / ((1 << 23) * (1 << 30));
|
||||
};
|
||||
})() : Math.random;
|
||||
|
||||
// return a random integer in [0, max)
|
||||
const randint = function(max) {
|
||||
return Math.floor(rng() * max);
|
||||
};
|
||||
|
||||
// Split input into shuffleable sections
|
||||
const toShuffle = input.split(delim);
|
||||
|
||||
// shuffle elements
|
||||
for (let i = toShuffle.length - 1; i > 0; i--) {
|
||||
const idx = randint(i + 1);
|
||||
const tmp = toShuffle[idx];
|
||||
toShuffle[idx] = toShuffle[i];
|
||||
toShuffle[i] = tmp;
|
||||
}
|
||||
|
||||
return toShuffle.join(delim);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Shuffle;
|
||||
@@ -34,10 +34,50 @@ class Substitute extends Operation {
|
||||
"name": "Ciphertext",
|
||||
"type": "binaryString",
|
||||
"value": "XYZABCDEFGHIJKLMNOPQRSTUVW"
|
||||
},
|
||||
{
|
||||
"name": "Ignore case",
|
||||
"type": "boolean",
|
||||
"value": false
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a single character using the dictionary, if ignoreCase is true then
|
||||
* check in the dictionary for both upper and lower case versions of the character.
|
||||
* In output the input character case is preserved.
|
||||
* @param {string} char
|
||||
* @param {Object} dict
|
||||
* @param {boolean} ignoreCase
|
||||
* @returns {string}
|
||||
*/
|
||||
cipherSingleChar(char, dict, ignoreCase) {
|
||||
if (!ignoreCase)
|
||||
return dict[char] || char;
|
||||
|
||||
const isUpperCase = char === char.toUpperCase();
|
||||
|
||||
// convert using the dictionary keeping the case of the input character
|
||||
|
||||
if (dict[char] !== undefined) {
|
||||
// if the character is in the dictionary return the value with the input case
|
||||
return isUpperCase ? dict[char].toUpperCase() : dict[char].toLowerCase();
|
||||
}
|
||||
|
||||
// check for the other case, if it is in the dictionary return the value with the right case
|
||||
if (isUpperCase) {
|
||||
if (dict[char.toLowerCase()] !== undefined)
|
||||
return dict[char.toLowerCase()].toUpperCase();
|
||||
} else {
|
||||
if (dict[char.toUpperCase()] !== undefined)
|
||||
return dict[char.toUpperCase()].toLowerCase();
|
||||
}
|
||||
|
||||
return char;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
@@ -45,17 +85,23 @@ class Substitute extends Operation {
|
||||
*/
|
||||
run(input, args) {
|
||||
const plaintext = Utils.expandAlphRange([...args[0]]),
|
||||
ciphertext = Utils.expandAlphRange([...args[1]]);
|
||||
let output = "",
|
||||
index = -1;
|
||||
ciphertext = Utils.expandAlphRange([...args[1]]),
|
||||
ignoreCase = args[2];
|
||||
let output = "";
|
||||
|
||||
if (plaintext.length !== ciphertext.length) {
|
||||
output = "Warning: Plaintext and Ciphertext lengths differ\n\n";
|
||||
}
|
||||
|
||||
// create dictionary for conversion
|
||||
const dict = {};
|
||||
for (let i = 0; i < Math.min(ciphertext.length, plaintext.length); i++) {
|
||||
dict[plaintext[i]] = ciphertext[i];
|
||||
}
|
||||
|
||||
// map every letter with the conversion function
|
||||
for (const character of input) {
|
||||
index = plaintext.indexOf(character);
|
||||
output += index > -1 && index < ciphertext.length ? ciphertext[index] : character;
|
||||
output += this.cipherSingleChar(character, dict, ignoreCase);
|
||||
}
|
||||
|
||||
return output;
|
||||
|
||||
@@ -79,6 +79,9 @@ class UNIXTimestampToWindowsFiletime extends Operation {
|
||||
flipped += result.charAt(i);
|
||||
flipped += result.charAt(i + 1);
|
||||
}
|
||||
if (result.length % 2 !== 0) {
|
||||
flipped += "0" + result.charAt(0);
|
||||
}
|
||||
result = flipped;
|
||||
}
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ class ViewBitPlane extends Operation {
|
||||
* @returns {html}
|
||||
*/
|
||||
present(data) {
|
||||
if (!data.length) return "";
|
||||
if (!data.byteLength) return "";
|
||||
const type = isImage(data);
|
||||
|
||||
return `<img src="data:${type};base64,${toBase64(data)}">`;
|
||||
|
||||
@@ -52,7 +52,10 @@ class WindowsFiletimeToUNIXTimestamp extends Operation {
|
||||
if (format === "Hex (little endian)") {
|
||||
// Swap endianness
|
||||
let result = "";
|
||||
for (let i = input.length - 2; i >= 0; i -= 2) {
|
||||
if (input.length % 2 !== 0) {
|
||||
result += input.charAt(input.length - 1);
|
||||
}
|
||||
for (let i = input.length - input.length % 2 - 2; i >= 0; i -= 2) {
|
||||
result += input.charAt(i);
|
||||
result += input.charAt(i + 1);
|
||||
}
|
||||
|
||||
@@ -45,10 +45,10 @@ TestRegister.addApiTests([
|
||||
const result = chef.ADD("sample input", {
|
||||
key: {
|
||||
string: "some key",
|
||||
option: "Hex"
|
||||
option: "utf8"
|
||||
}
|
||||
});
|
||||
assert.equal(result.toString(), "aO[^ZS\u000eW\\^cb");
|
||||
assert.equal(result.toString(), "\xe6\xd0\xda\xd5\x8c\xd0\x85\xe2\xe1\xdf\xe2\xd9");
|
||||
}),
|
||||
|
||||
|
||||
@@ -121,10 +121,10 @@ Tiger-128`;
|
||||
const result = chef.AND("Scot-free", {
|
||||
key: {
|
||||
string: "Raining Cats and Dogs",
|
||||
option: "Hex",
|
||||
option: "utf8",
|
||||
}
|
||||
});
|
||||
assert.strictEqual(result.toString(), "\u0000\"M$(D E");
|
||||
assert.strictEqual(result.toString(), "Raid)fb A");
|
||||
}),
|
||||
|
||||
it("atBash Cipher", () => {
|
||||
@@ -371,10 +371,10 @@ color: white;
|
||||
},
|
||||
salt: {
|
||||
string: "Market",
|
||||
option: "Hex",
|
||||
option: "utf8",
|
||||
},
|
||||
});
|
||||
assert.strictEqual(result.toString(), "7c21a9f5063a4d62fb1050068245c181");
|
||||
assert.strictEqual(result.toString(), "4930d5d200e80f18c96b5550d13c6af8");
|
||||
}),
|
||||
|
||||
it("Derive PBKDF2 Key", () => {
|
||||
|
||||
@@ -124,6 +124,7 @@ import "./tests/UnescapeString.mjs";
|
||||
import "./tests/LS47.mjs";
|
||||
import "./tests/LZString.mjs";
|
||||
import "./tests/NTLM.mjs";
|
||||
import "./tests/Shuffle.mjs";
|
||||
|
||||
// Cannot test operations that use the File type yet
|
||||
// import "./tests/SplitColourChannels.mjs";
|
||||
|
||||
54
tests/operations/tests/Shuffle.mjs
Normal file
54
tests/operations/tests/Shuffle.mjs
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* @author mikecat
|
||||
* @copyright Crown Copyright 2022
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
import TestRegister from "../../lib/TestRegister.mjs";
|
||||
|
||||
TestRegister.addTests([
|
||||
{
|
||||
"name": "Shuffle empty",
|
||||
"input": "",
|
||||
"expectedOutput": "",
|
||||
"recipeConfig": [
|
||||
{
|
||||
"op": "Shuffle",
|
||||
"args": ["Comma"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Shuffle bytes",
|
||||
"input": "12345678",
|
||||
"expectedOutput": "31 32 33 34 35 36 37 38",
|
||||
"recipeConfig": [
|
||||
{
|
||||
"op": "Shuffle",
|
||||
"args": ["Nothing (separate chars)"]
|
||||
},
|
||||
{
|
||||
"op": "To Hex",
|
||||
"args": ["Space", 0]
|
||||
},
|
||||
{
|
||||
"op": "Sort",
|
||||
"args": ["Space", false, "Alphabetical (case sensitive)"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Shuffle lines",
|
||||
"input": "1\n2\n3\n4\n5\n6\n7\n8\n9\na\nb\nc\nd\ne\nf\n",
|
||||
"expectedOutput": "\n1\n2\n3\n4\n5\n6\n7\n8\n9\na\nb\nc\nd\ne\nf",
|
||||
"recipeConfig": [
|
||||
{
|
||||
"op": "Shuffle",
|
||||
"args": ["Line feed"]
|
||||
},
|
||||
{
|
||||
"op": "Sort",
|
||||
"args": ["Line feed", false, "Alphabetical (case sensitive)"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]);
|
||||
Reference in New Issue
Block a user