Merge branch 'gchq:master' into word_count2

This commit is contained in:
sw5678 2025-07-31 12:13:38 +01:00 committed by GitHub
commit b3143451e6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 716 additions and 67 deletions

View File

@ -29,8 +29,7 @@ RUN npm run build
######################################### #########################################
# We are using Github Actions: redhat-actions/buildah-build@v2 which needs manual selection of arch in base image # We are using Github Actions: redhat-actions/buildah-build@v2 which needs manual selection of arch in base image
# Remove TARGETARCH if docker buildx is supported in the CI release as --platform=$TARGETPLATFORM will be automatically set # Remove TARGETARCH if docker buildx is supported in the CI release as --platform=$TARGETPLATFORM will be automatically set
ARG TARGETARCH
ARG TARGETPLATFORM ARG TARGETPLATFORM
FROM ${TARGETARCH}/nginx:stable-alpine AS cyberchef FROM --platform=${TARGETPLATFORM} nginx:stable-alpine AS cyberchef
COPY --from=builder /app/build/prod /usr/share/nginx/html/ COPY --from=builder /app/build/prod /usr/share/nginx/html/

View File

@ -20,21 +20,36 @@ Cryptographic operations in CyberChef should not be relied upon to provide secur
[A live demo can be found here][1] - have fun! [A live demo can be found here][1] - have fun!
## Containers ## Running Locally with Docker
If you would like to try out CyberChef locally you can either build it yourself: **Prerequisites**
- [Docker](hhttps://www.docker.com/products/docker-desktop/)
- Docker Desktop must be open and running on your machine
#### Option 1: Build the Docker Image Yourself
1. Build the docker image
```bash ```bash
docker build --tag cyberchef --ulimit nofile=10000 . docker build --tag cyberchef --ulimit nofile=10000 .
```
2. Run the docker container
```bash
docker run -it -p 8080:80 cyberchef docker run -it -p 8080:80 cyberchef
``` ```
3. Navigate to `http://localhost:8080` in your browser
Or you can use our image directly: #### Option 2: Use the pre-built Docker Image
If you prefer to skip the build process, you can use the pre-built image
```bash ```bash
docker run -it -p 8080:80 ghcr.io/gchq/cyberchef:latest docker run -it -p 8080:80 ghcr.io/gchq/cyberchef:latest
``` ```
Just like before, navigate to `http://localhost:8080` in your browser.
This image is built and published through our [GitHub Workflows](.github/workflows/releases.yml) This image is built and published through our [GitHub Workflows](.github/workflows/releases.yml)
## How it works ## How it works

69
package-lock.json generated
View File

@ -46,6 +46,8 @@
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"flat": "^6.0.1", "flat": "^6.0.1",
"geodesy": "1.1.3", "geodesy": "1.1.3",
"handlebars": "^4.7.8",
"hash-wasm": "^4.12.0",
"highlight.js": "^11.9.0", "highlight.js": "^11.9.0",
"ieee754": "^1.2.1", "ieee754": "^1.2.1",
"jimp": "^0.22.12", "jimp": "^0.22.12",
@ -95,6 +97,7 @@
"ua-parser-js": "^1.0.38", "ua-parser-js": "^1.0.38",
"unorm": "^1.6.0", "unorm": "^1.6.0",
"utf8": "^3.0.0", "utf8": "^3.0.0",
"uuid": "^11.1.0",
"vkbeautify": "^0.99.3", "vkbeautify": "^0.99.3",
"xpath": "0.0.34", "xpath": "0.0.34",
"xregexp": "^5.1.1", "xregexp": "^5.1.1",
@ -10843,6 +10846,27 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/handlebars": {
"version": "4.7.8",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
"integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==",
"license": "MIT",
"dependencies": {
"minimist": "^1.2.5",
"neo-async": "^2.6.2",
"source-map": "^0.6.1",
"wordwrap": "^1.0.0"
},
"bin": {
"handlebars": "bin/handlebars"
},
"engines": {
"node": ">=0.4.7"
},
"optionalDependencies": {
"uglify-js": "^3.1.4"
}
},
"node_modules/has-ansi": { "node_modules/has-ansi": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
@ -10941,6 +10965,11 @@
"node": ">= 0.10" "node": ">= 0.10"
} }
}, },
"node_modules/hash-wasm": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/hash-wasm/-/hash-wasm-4.12.0.tgz",
"integrity": "sha512-+/2B2rYLb48I/evdOIhP+K/DD2ca2fgBjp6O+GBEnCDk2e4rpeXIK8GvIyRPjTezgmWn9gmKwkQjjx6BtqDHVQ=="
},
"node_modules/hash.js": { "node_modules/hash.js": {
"version": "1.1.7", "version": "1.1.7",
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
@ -13663,7 +13692,6 @@
"version": "2.6.2", "version": "2.6.2",
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/netmask": { "node_modules/netmask": {
@ -13885,6 +13913,15 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/nightwatch/node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"dev": true,
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/nightwatch/node_modules/yallist": { "node_modules/nightwatch/node_modules/yallist": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
@ -16828,6 +16865,15 @@
"node": ">=0.8.0" "node": ">=0.8.0"
} }
}, },
"node_modules/sockjs/node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"dev": true,
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/socks": { "node_modules/socks": {
"version": "2.8.3", "version": "2.8.3",
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz",
@ -16868,7 +16914,6 @@
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"devOptional": true,
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
@ -18115,13 +18160,15 @@
} }
}, },
"node_modules/uuid": { "node_modules/uuid": {
"version": "8.3.2", "version": "11.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==",
"dev": true, "funding": [
"license": "MIT", "https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"bin": { "bin": {
"uuid": "dist/bin/uuid" "uuid": "dist/esm/bin/uuid"
} }
}, },
"node_modules/v8flags": { "node_modules/v8flags": {
@ -18886,6 +18933,12 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/wordwrap": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
"integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==",
"license": "MIT"
},
"node_modules/worker-loader": { "node_modules/worker-loader": {
"version": "3.0.8", "version": "3.0.8",
"resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-3.0.8.tgz", "resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-3.0.8.tgz",

View File

@ -132,6 +132,8 @@
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"flat": "^6.0.1", "flat": "^6.0.1",
"geodesy": "1.1.3", "geodesy": "1.1.3",
"handlebars": "^4.7.8",
"hash-wasm": "^4.12.0",
"highlight.js": "^11.9.0", "highlight.js": "^11.9.0",
"ieee754": "^1.2.1", "ieee754": "^1.2.1",
"jimp": "^0.22.12", "jimp": "^0.22.12",
@ -181,6 +183,7 @@
"ua-parser-js": "^1.0.38", "ua-parser-js": "^1.0.38",
"unorm": "^1.6.0", "unorm": "^1.6.0",
"utf8": "^3.0.0", "utf8": "^3.0.0",
"uuid": "^11.1.0",
"vkbeautify": "^0.99.3", "vkbeautify": "^0.99.3",
"xpath": "0.0.34", "xpath": "0.0.34",
"xregexp": "^5.1.1", "xregexp": "^5.1.1",

View File

@ -376,7 +376,8 @@
"Extract EXIF", "Extract EXIF",
"Extract ID3", "Extract ID3",
"Extract Files", "Extract Files",
"RAKE" "RAKE",
"Template"
] ]
}, },
{ {
@ -426,6 +427,7 @@
"Snefru", "Snefru",
"BLAKE2b", "BLAKE2b",
"BLAKE2s", "BLAKE2s",
"BLAKE3",
"GOST Hash", "GOST Hash",
"Streebog", "Streebog",
"SSDEEP", "SSDEEP",
@ -550,6 +552,7 @@
"Pseudo-Random Number Generator", "Pseudo-Random Number Generator",
"Generate De Bruijn Sequence", "Generate De Bruijn Sequence",
"Generate UUID", "Generate UUID",
"Analyse UUID",
"Generate TOTP", "Generate TOTP",
"Generate HOTP", "Generate HOTP",
"Generate QR Code", "Generate QR Code",

View File

@ -0,0 +1,48 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2023
* @license Apache-2.0
*/
import * as uuid from "uuid";
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
/**
* Analyse UUID operation
*/
class AnalyseUUID extends Operation {
/**
* AnalyseUUID constructor
*/
constructor() {
super();
this.name = "Analyse UUID";
this.module = "Crypto";
this.description = "Tries to determine information about a given UUID and suggests which version may have been used to generate it";
this.infoURL = "https://wikipedia.org/wiki/Universally_unique_identifier";
this.inputType = "string";
this.outputType = "string";
this.args = [];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
try {
const uuidVersion = uuid.version(input);
return "UUID version: " + uuidVersion;
} catch (error) {
throw new OperationError("Invalid UUID");
}
}
}
export default AnalyseUUID;

View File

@ -0,0 +1,58 @@
/**
* @author xumptex [xumptex@outlook.fr]
* @copyright Crown Copyright 2025
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import { blake3 } from "hash-wasm";
/**
* BLAKE3 operation
*/
class BLAKE3 extends Operation {
/**
* BLAKE3 constructor
*/
constructor() {
super();
this.name = "BLAKE3";
this.module = "Hashing";
this.description = "Hashes the input using BLAKE3 (UTF-8 encoded), with an optional key (also UTF-8), and outputs the result in hexadecimal format.";
this.infoURL = "https://en.wikipedia.org/wiki/BLAKE_(hash_function)#BLAKE3";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "Size (bytes)",
"type": "number"
}, {
"name": "Key",
"type": "string",
"value": ""
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const key = args[1];
const size = args[0];
// Check if the user want a key hash or not
if (key === "") {
return blake3(input, size*8);
} if (key.length !== 32) {
throw new OperationError("The key must be exactly 32 bytes long");
}
return blake3(input, size*8, key);
}
}
export default BLAKE3;

View File

@ -51,7 +51,7 @@ class ExtractEmailAddresses extends Operation {
run(input, args) { run(input, args) {
const [displayTotal, sort, unique] = args, const [displayTotal, sort, unique] = args,
// email regex from: https://www.regextester.com/98066 // email regex from: https://www.regextester.com/98066
regex = /(?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9](?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9-]*[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9])?\.)+[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9](?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9-]*[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}\])/ig; regex = /(?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9](?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9-]*[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9])?\.)+[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9](?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9-]*[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\])/ig;
const results = search( const results = search(
input, input,

View File

@ -21,7 +21,7 @@ class ExtractIPAddresses extends Operation {
this.name = "Extract IP addresses"; this.name = "Extract IP addresses";
this.module = "Regex"; this.module = "Regex";
this.description = "Extracts all IPv4 and IPv6 addresses.<br><br>Warning: Given a string <code>710.65.0.456</code>, this will match <code>10.65.0.45</code> so always check the original input!"; this.description = "Extracts all IPv4 and IPv6 addresses.<br><br>Warning: Given a string <code>1.2.3.4.5.6.7.8</code>, this will match <code>1.2.3.4 and 5.6.7.8</code> so always check the original input!";
this.inputType = "string"; this.inputType = "string";
this.outputType = "string"; this.outputType = "string";
this.args = [ this.args = [
@ -65,7 +65,21 @@ class ExtractIPAddresses extends Operation {
*/ */
run(input, args) { run(input, args) {
const [includeIpv4, includeIpv6, removeLocal, displayTotal, sort, unique] = args, const [includeIpv4, includeIpv6, removeLocal, displayTotal, sort, unique] = args,
ipv4 = "(?:(?:\\d|[01]?\\d\\d|2[0-4]\\d|25[0-5])\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d|\\d)(?:\\/\\d{1,2})?",
// IPv4 decimal groups can have values 0 to 255. To construct a regex the following sub-regex is reused:
ipv4DecimalByte = "(?:25[0-5]|2[0-4]\\d|1?[0-9]\\d|\\d)",
ipv4OctalByte = "(?:0[1-3]?[0-7]{1,2})",
// Look behind and ahead will be used to exclude matches with additional decimal digits left and right of IP address
lookBehind = "(?<!\\d)",
lookAhead = "(?!\\d)",
// Each variant requires exactly 4 groups with literal . between.
ipv4Decimal = "(?:" + lookBehind + ipv4DecimalByte + "\\.){3}" + "(?:" + ipv4DecimalByte + lookAhead + ")",
ipv4Octal = "(?:" + lookBehind + ipv4OctalByte + "\\.){3}" + "(?:" + ipv4OctalByte + lookAhead + ")",
// Then we allow IPv4 addresses to be expressed either entirely in decimal or entirely in Octal
ipv4 = "(?:" + ipv4Decimal + "|" + ipv4Octal + ")",
ipv6 = "((?=.*::)(?!.*::.+::)(::)?([\\dA-F]{1,4}:(:|\\b)|){5}|([\\dA-F]{1,4}:){6})(([\\dA-F]{1,4}((?!\\3)::|:\\b|(?![\\dA-F])))|(?!\\2\\3)){2}"; ipv6 = "((?=.*::)(?!.*::.+::)(::)?([\\dA-F]{1,4}:(:|\\b)|){5}|([\\dA-F]{1,4}:){6})(([\\dA-F]{1,4}((?!\\3)::|:\\b|(?![\\dA-F])))|(?!\\2\\3)){2}";
let ips = ""; let ips = "";

View File

@ -5,8 +5,8 @@
*/ */
import Operation from "../Operation.mjs"; import Operation from "../Operation.mjs";
import crypto from "crypto"; import * as uuid from "uuid";
import OperationError from "../errors/OperationError.mjs";
/** /**
* Generate UUID operation * Generate UUID operation
*/ */
@ -20,11 +20,38 @@ class GenerateUUID extends Operation {
this.name = "Generate UUID"; this.name = "Generate UUID";
this.module = "Crypto"; this.module = "Crypto";
this.description = "Generates an RFC 4122 version 4 compliant Universally Unique Identifier (UUID), also known as a Globally Unique Identifier (GUID).<br><br>A version 4 UUID relies on random numbers, in this case generated using <code>window.crypto</code> if available and falling back to <code>Math.random</code> if not."; this.description =
"Generates an RFC 9562 (formerly RFC 4122) compliant Universally Unique Identifier (UUID), " +
"also known as a Globally Unique Identifier (GUID).<br>" +
"<br>" +
"We currently support generating the following UUID versions:<br>" +
"<ul>" +
"<li><strong>v1</strong>: Timestamp-based</li>" +
"<li><strong>v3</strong>: Namespace w/ MD5</li>" +
"<li><strong>v4</strong>: Random (default)</li>" +
"<li><strong>v5</strong>: Namespace w/ SHA-1</li>" +
"<li><strong>v6</strong>: Timestamp, reordered</li>" +
"<li><strong>v7</strong>: Unix Epoch time-based</li>" +
"</ul>" +
"UUIDs are generated using the <a href='https://npmjs.org/uuid/'><code>uuid</code><a> package.<br>";
this.infoURL = "https://wikipedia.org/wiki/Universally_unique_identifier"; this.infoURL = "https://wikipedia.org/wiki/Universally_unique_identifier";
this.inputType = "string"; this.inputType = "string";
this.outputType = "string"; this.outputType = "string";
this.args = []; this.args = [
{
name: "Version",
hint: "UUID version",
type: "option",
value: ["v1", "v3", "v4", "v5", "v6", "v7"],
defaultIndex: 2,
},
{
name: "Namespace",
hint: "UUID namespace (UUID; valid for v3 and v5)",
type: "string",
value: "1b671a64-40d5-491e-99b0-da01ff1f3341"
}
];
} }
/** /**
@ -33,16 +60,17 @@ class GenerateUUID extends Operation {
* @returns {string} * @returns {string}
*/ */
run(input, args) { run(input, args) {
const buf = new Uint32Array(4).map(() => { const [version, namespace] = args;
return crypto.randomBytes(4).readUInt32BE(0, true); const hasDesiredVersion = typeof uuid[version] === "function";
}); if (!hasDesiredVersion) throw new OperationError("Invalid UUID version");
let i = 0;
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) { const requiresNamespace = ["v3", "v5"].includes(version);
const r = (buf[i >> 3] >> ((i % 8) * 4)) & 0xf, if (!requiresNamespace) return uuid[version]();
v = c === "x" ? r : (r & 0x3 | 0x8);
i++; const hasValidNamespace = typeof namespace === "string" && uuid.validate(namespace);
return v.toString(16); if (!hasValidNamespace) throw new OperationError("Invalid UUID namespace");
});
return uuid[version](input, namespace);
} }
} }

View File

@ -72,7 +72,7 @@ class RailFenceCipherDecode extends Operation {
} }
} }
return plaintext.join("").trim(); return plaintext.join("");
} }
} }

View File

@ -66,7 +66,7 @@ class RailFenceCipherEncode extends Operation {
rows[rowIdx] += plaintext[pos]; rows[rowIdx] += plaintext[pos];
} }
return rows.join("").trim(); return rows.join("");
} }
} }

View File

@ -1,6 +1,7 @@
/** /**
* @author j433866 [j433866@gmail.com] * @author j433866 [j433866@gmail.com]
* @copyright Crown Copyright 2019 * @author 0xff1ce [github.com/0xff1ce]
* @copyright Crown Copyright 2024
* @license Apache-2.0 * @license Apache-2.0
*/ */
@ -22,7 +23,7 @@ class ShowOnMap extends Operation {
this.name = "Show on map"; this.name = "Show on map";
this.module = "Hashing"; this.module = "Hashing";
this.description = "Displays co-ordinates on a slippy map.<br><br>Co-ordinates will be converted to decimal degrees before being shown on the map.<br><br>Supported formats:<ul><li>Degrees Minutes Seconds (DMS)</li><li>Degrees Decimal Minutes (DDM)</li><li>Decimal Degrees (DD)</li><li>Geohash</li><li>Military Grid Reference System (MGRS)</li><li>Ordnance Survey National Grid (OSNG)</li><li>Universal Transverse Mercator (UTM)</li></ul><br>This operation will not work offline."; this.description = "Displays co-ordinates on a slippy map.<br><br>Co-ordinates will be converted to decimal degrees before being shown on the map.<br><br>Supported formats:<ul><li>Degrees Minutes Seconds (DMS)</li><li>Degrees Decimal Minutes (DDM)</li><li>Decimal Degrees (DD)</li><li>Geohash</li><li>Military Grid Reference System (MGRS)</li><li>Ordnance Survey National Grid (OSNG)</li><li>Universal Transverse Mercator (UTM)</li></ul><br>This operation will not work offline.";
this.infoURL = "https://foundation.wikimedia.org/wiki/Maps_Terms_of_Use"; this.infoURL = "https://osmfoundation.org/wiki/Terms_of_Use";
this.inputType = "string"; this.inputType = "string";
this.outputType = "string"; this.outputType = "string";
this.presentType = "html"; this.presentType = "html";
@ -85,10 +86,10 @@ class ShowOnMap extends Operation {
data = "0, 0"; data = "0, 0";
} }
const zoomLevel = args[0]; const zoomLevel = args[0];
const tileUrl = "https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}.png", const tileUrl = "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
tileAttribution = "<a href=\"https://wikimediafoundation.org/wiki/Maps_Terms_of_Use\">Wikimedia maps</a> | &copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors", tileAttribution = "&copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors",
leafletUrl = "https://unpkg.com/leaflet@1.5.0/dist/leaflet.js", leafletUrl = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.js",
leafletCssUrl = "https://unpkg.com/leaflet@1.5.0/dist/leaflet.css"; leafletCssUrl = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.css";
return `<link rel="stylesheet" href="${leafletCssUrl}" crossorigin=""/> return `<link rel="stylesheet" href="${leafletCssUrl}" crossorigin=""/>
<style> <style>
#output-text .cm-content, #output-text .cm-content,

View File

@ -0,0 +1,53 @@
/**
* @author kendallgoto [k@kgo.to]
* @copyright Crown Copyright 2025
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import Handlebars from "handlebars";
/**
* Template operation
*/
class Template extends Operation {
/**
* Template constructor
*/
constructor() {
super();
this.name = "Template";
this.module = "Handlebars";
this.description = "Render a template with Handlebars/Mustache substituting variables using JSON input. Templates will be rendered to plain-text only, to prevent XSS.";
this.infoURL = "https://handlebarsjs.com/";
this.inputType = "JSON";
this.outputType = "string";
this.args = [
{
name: "Template definition (.handlebars)",
type: "text",
value: ""
}
];
}
/**
* @param {JSON} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const [templateStr] = args;
try {
const template = Handlebars.compile(templateStr);
return template(input);
} catch (e) {
throw new OperationError(e);
}
}
}
export default Template;

View File

@ -45,11 +45,12 @@ class ToDecimal extends Operation {
* @returns {string} * @returns {string}
*/ */
run(input, args) { run(input, args) {
input = new Uint8Array(input);
const delim = Utils.charRep(args[0]), const delim = Utils.charRep(args[0]),
signed = args[1]; signed = args[1];
if (signed) { if (signed) {
input = input.map(v => v > 0x7F ? v - 0xFF - 1 : v); input = new Int8Array(input);
} else {
input = new Uint8Array(input);
} }
return input.join(delim); return input.join(delim);
} }

View File

@ -23,7 +23,13 @@ class URLDecode extends Operation {
this.infoURL = "https://wikipedia.org/wiki/Percent-encoding"; this.infoURL = "https://wikipedia.org/wiki/Percent-encoding";
this.inputType = "string"; this.inputType = "string";
this.outputType = "string"; this.outputType = "string";
this.args = []; this.args = [
{
"name": "Treat \"+\" as space",
"type": "boolean",
"value": true
},
];
this.checks = [ this.checks = [
{ {
pattern: ".*(?:%[\\da-f]{2}.*){4}", pattern: ".*(?:%[\\da-f]{2}.*){4}",
@ -39,7 +45,8 @@ class URLDecode extends Operation {
* @returns {string} * @returns {string}
*/ */
run(input, args) { run(input, args) {
const data = input.replace(/\+/g, "%20"); const plusIsSpace = args[0];
const data = plusIsSpace ? input.replace(/\+/g, "%20") : input;
try { try {
return decodeURIComponent(data); return decodeURIComponent(data);
} catch (err) { } catch (err) {

View File

@ -74,11 +74,11 @@ function transformArgs(opArgsList, newArgs) {
return opArgs.map((arg) => { return opArgs.map((arg) => {
if (arg.type === "option") { if (arg.type === "option") {
// pick default option if not already chosen // pick default option if not already chosen
return typeof arg.value === "string" ? arg.value : arg.value[0]; return typeof arg.value === "string" ? arg.value : arg.value[arg.defaultIndex ?? 0];
} }
if (arg.type === "editableOption") { if (arg.type === "editableOption") {
return typeof arg.value === "string" ? arg.value : arg.value[0].value; return typeof arg.value === "string" ? arg.value : arg.value[arg.defaultIndex ?? 0].value;
} }
if (arg.type === "toggleString") { if (arg.type === "toggleString") {

View File

@ -1,6 +1,8 @@
[ [
{ {
"@context": "http://schema.org", "@context": "http://schema.org",
"@graph": [
{
"@type": "Organization", "@type": "Organization",
"url": "https://gchq.github.io/CyberChef/", "url": "https://gchq.github.io/CyberChef/",
"logo": "https://gchq.github.io/CyberChef/images/cyberchef-128x128.png", "logo": "https://gchq.github.io/CyberChef/images/cyberchef-128x128.png",
@ -10,7 +12,6 @@
] ]
}, },
{ {
"@context": "http://schema.org",
"@type": "WebSite", "@type": "WebSite",
"url": "https://gchq.github.io/CyberChef/", "url": "https://gchq.github.io/CyberChef/",
"name": "CyberChef", "name": "CyberChef",
@ -21,3 +22,5 @@
} }
} }
] ]
}
]

View File

@ -580,10 +580,25 @@ Password: 282760`;
assert.strictEqual(result.toString().substr(0, 37), "-----BEGIN PGP PRIVATE KEY BLOCK-----"); assert.strictEqual(result.toString().substr(0, 37), "-----BEGIN PGP PRIVATE KEY BLOCK-----");
}), }),
it("Generate UUID", () => { ...[1, 3, 4, 5, 6, 7].map(version => it(`Generate UUID v${version}`, () => {
const result = chef.generateUUID(); const result = chef.generateUUID("", { "version": `v${version}` }).toString();
assert.ok(result.toString()); assert.ok(result);
assert.strictEqual(result.toString().length, 36); assert.strictEqual(result.length, 36);
})),
...[1, 3, 4, 5, 6, 7].map(version => it(`Analyze UUID v${version}`, () => {
const uuid = chef.generateUUID("", { "version": `v${version}` }).toString();
const result = chef.analyseUUID(uuid).toString();
const expected = `UUID version: ${version}`;
assert.strictEqual(result, expected);
})),
it("Generate UUID using defaults", () => {
const uuid = chef.generateUUID();
assert.ok(uuid);
const analysis = chef.analyseUUID(uuid).toString();
assert.strictEqual(analysis, "UUID version: 4");
}), }),
it("Gzip, Gunzip", () => { it("Gzip, Gunzip", () => {

View File

@ -29,6 +29,7 @@ import "./tests/BCD.mjs";
import "./tests/BitwiseOp.mjs"; import "./tests/BitwiseOp.mjs";
import "./tests/BLAKE2b.mjs"; import "./tests/BLAKE2b.mjs";
import "./tests/BLAKE2s.mjs"; import "./tests/BLAKE2s.mjs";
import "./tests/BLAKE3.mjs";
import "./tests/Bombe.mjs"; import "./tests/Bombe.mjs";
import "./tests/BSON.mjs"; import "./tests/BSON.mjs";
import "./tests/ByteRepr.mjs"; import "./tests/ByteRepr.mjs";
@ -65,6 +66,7 @@ import "./tests/ELFInfo.mjs";
import "./tests/Enigma.mjs"; import "./tests/Enigma.mjs";
import "./tests/ExtractEmailAddresses.mjs"; import "./tests/ExtractEmailAddresses.mjs";
import "./tests/ExtractHashes.mjs"; import "./tests/ExtractHashes.mjs";
import "./tests/ExtractIPAddresses.mjs";
import "./tests/Float.mjs"; import "./tests/Float.mjs";
import "./tests/FileTree.mjs"; import "./tests/FileTree.mjs";
import "./tests/FletcherChecksum.mjs"; import "./tests/FletcherChecksum.mjs";
@ -157,12 +159,14 @@ import "./tests/Subsection.mjs";
import "./tests/SwapCase.mjs"; import "./tests/SwapCase.mjs";
import "./tests/SymmetricDifference.mjs"; import "./tests/SymmetricDifference.mjs";
import "./tests/TakeNthBytes.mjs"; import "./tests/TakeNthBytes.mjs";
import "./tests/Template.mjs";
import "./tests/TextEncodingBruteForce.mjs"; import "./tests/TextEncodingBruteForce.mjs";
import "./tests/ToFromInsensitiveRegex.mjs"; import "./tests/ToFromInsensitiveRegex.mjs";
import "./tests/TranslateDateTimeFormat.mjs"; import "./tests/TranslateDateTimeFormat.mjs";
import "./tests/Typex.mjs"; import "./tests/Typex.mjs";
import "./tests/UnescapeString.mjs"; import "./tests/UnescapeString.mjs";
import "./tests/Unicode.mjs"; import "./tests/Unicode.mjs";
import "./tests/URLEncodeDecode.mjs";
import "./tests/RSA.mjs"; import "./tests/RSA.mjs";
import "./tests/CBOREncode.mjs"; import "./tests/CBOREncode.mjs";
import "./tests/CBORDecode.mjs"; import "./tests/CBORDecode.mjs";

View File

@ -0,0 +1,55 @@
/**
* BLAKE3 tests.
* @author xumptex [xumptex@outlook.fr]
* @copyright Crown Copyright 2025
* @license Apache-2.0
*/
import TestRegister from "../../lib/TestRegister.mjs";
TestRegister.addTests([
{
name: "BLAKE3: 8 - Hello world",
input: "Hello world",
expectedOutput: "e7e6fb7d2869d109",
recipeConfig: [
{ "op": "BLAKE3",
"args": [8, ""] }
]
},
{
name: "BLAKE3: 16 - Hello world 2",
input: "Hello world 2",
expectedOutput: "2a3df5fe5f0d3fcdd995fc203c7f7c52",
recipeConfig: [
{ "op": "BLAKE3",
"args": [16, ""] }
]
},
{
name: "BLAKE3: 32 - Hello world",
input: "Hello world",
expectedOutput: "e7e6fb7d2869d109b62cdb1227208d4016cdaa0af6603d95223c6a698137d945",
recipeConfig: [
{ "op": "BLAKE3",
"args": [32, ""] }
]
},
{
name: "BLAKE3: Key Test",
input: "Hello world",
expectedOutput: "59dd23ac9d025690",
recipeConfig: [
{ "op": "BLAKE3",
"args": [8, "ThiskeyisexactlythirtytwoBytesLo"] }
]
},
{
name: "BLAKE3: Key Test 2",
input: "Hello world",
expectedOutput: "c8302c9634c1da42",
recipeConfig: [
{ "op": "BLAKE3",
"args": [8, "ThiskeyisexactlythirtytwoByteslo"] }
]
}
]);

View File

@ -528,4 +528,15 @@ TestRegister.addTests([
} }
], ],
}, },
{
name: "Rail Fence Cipher Encode: Normal with Offset with Spaces",
input: "No one expects the spanish Inquisition.",
expectedOutput: " e n ut.ooeepcstesaihIqiiinNnxthpsnso",
recipeConfig: [
{
"op": "Rail Fence Cipher Encode",
"args": [3, 2]
}
],
},
]); ]);

View File

@ -0,0 +1,133 @@
/**
* ExtractIPAddresses tests.
*
* @author gchqdev365 [gchqdev365@outlook.com]
* @copyright Crown Copyright 2025
* @license Apache-2.0
*/
import TestRegister from "../../lib/TestRegister.mjs";
TestRegister.addTests([
{
name: "ExtractIPAddress All Zeros",
input: "0.0.0.0",
expectedOutput: "0.0.0.0",
recipeConfig: [
{
"op": "Extract IP addresses",
"args": [true, true, false, false, false, false]
},
],
},
{
name: "ExtractIPAddress All 10s",
input: "10.10.10.10",
expectedOutput: "10.10.10.10",
recipeConfig: [
{
"op": "Extract IP addresses",
"args": [true, true, false, false, false, false]
},
],
},
{
name: "ExtractIPAddress All 10s",
input: "100.100.100.100",
expectedOutput: "100.100.100.100",
recipeConfig: [
{
"op": "Extract IP addresses",
"args": [true, true, false, false, false, false]
},
],
},
{
name: "ExtractIPAddress 255s",
input: "255.255.255.255",
expectedOutput: "255.255.255.255",
recipeConfig: [
{
"op": "Extract IP addresses",
"args": [true, true, false, false, false, false]
},
],
},
{
name: "ExtractIPAddress double digits",
input: "10.10.10.10 25.25.25.25 99.99.99.99",
expectedOutput: "10.10.10.10\n25.25.25.25\n99.99.99.99",
recipeConfig: [
{
"op": "Extract IP addresses",
"args": [true, true, false, false, false, false]
},
],
},
{
name: "ExtractIPAddress 256 in middle",
input: "255.256.255.255 255.255.256.255",
expectedOutput: "",
recipeConfig: [
{
"op": "Extract IP addresses",
"args": [true, true, false, false, false, false]
},
],
},
{
name: "ExtractIPAddress 256 at each end",
input: "256.255.255.255 255.255.255.256",
expectedOutput: "",
recipeConfig: [
{
"op": "Extract IP addresses",
"args": [true, true, false, false, false, false]
},
],
},
{
name: "ExtractIPAddress silly example",
input: "710.65.0.456",
expectedOutput: "",
recipeConfig: [
{
"op": "Extract IP addresses",
"args": [true, true, false, false, false, false]
},
],
},
{
name: "ExtractIPAddress longer dotted decimal",
input: "1.2.3.4.5.6.7.8",
expectedOutput: "1.2.3.4\n5.6.7.8",
recipeConfig: [
{
"op": "Extract IP addresses",
"args": [true, true, false, false, false, false]
},
],
},
{
name: "ExtractIPAddress octal valid",
input: "01.01.01.01 0123.0123.0123.0123 0377.0377.0377.0377",
expectedOutput: "01.01.01.01\n0123.0123.0123.0123\n0377.0377.0377.0377",
recipeConfig: [
{
"op": "Extract IP addresses",
"args": [true, true, false, false, false, false]
},
],
},
{
name: "ExtractIPAddress octal invalid",
input: "0378.01.01.01 03.0377.2.3",
expectedOutput: "",
recipeConfig: [
{
"op": "Extract IP addresses",
"args": [true, true, false, false, false, false]
},
],
},
]);

View File

@ -0,0 +1,53 @@
/**
* @author kendallgoto [k@kgo.to]
* @copyright Crown Copyright 2025
* @license Apache-2.0
*/
import TestRegister from "../../lib/TestRegister.mjs";
TestRegister.addTests([
{
"name": "Template: Simple Print",
"input": "{}",
"expectedOutput": "Hello, world!",
"recipeConfig": [
{
"op": "Template",
"args": ["Hello, world!"]
}
]
},
{
"name": "Template: Print Basic Variables",
"input": "{\"one\": 1, \"two\": 2}",
"expectedOutput": "1 2",
"recipeConfig": [
{
"op": "Template",
"args": ["{{ one }} {{ two }}"]
}
]
},
{
"name": "Template: Partials",
"input": "{\"users\":[{\"name\":\"Someone\",\"age\":25},{\"name\":\"Someone Else\",\"age\":32}]}",
"expectedOutput": "Name: Someone\nAge: 25\n\nName: Someone Else\nAge: 32\n\n",
"recipeConfig": [
{
"op": "Template",
"args": ["{{#*inline \"user\"}}\nName: {{ name }}\nAge: {{ age }}\n{{/inline}}\n{{#each users}}\n{{> user}}\n\n{{/each}}"]
}
]
},
{
"name": "Template: Disallow XSS",
"input": "{\"test\": \"<script></script>\"}",
"expectedOutput": "<script></script>&lt;script&gt;&lt;/script&gt;",
"recipeConfig": [
{
"op": "Template",
"args": ["<script></script>{{ test }}"]
}
]
}
]);

View File

@ -0,0 +1,92 @@
/**
* URLEncode and URLDecode tests.
*
* @author es45411 [135977478+es45411@users.noreply.github.com]
*
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import TestRegister from "../../lib/TestRegister.mjs";
TestRegister.addTests([
// URL Decode
{
name: "URLDecode: nothing",
input: "",
expectedOutput: "",
recipeConfig: [
{
op: "URL Decode",
args: [],
},
],
},
{
name: "URLDecode: spaces without special chars",
input: "Hello%20world%21",
expectedOutput: "Hello world!",
recipeConfig: [
{
op: "URL Decode",
args: [],
},
],
},
{
name: "URLDecode: spaces with special chars",
input: "Hello%20world!",
expectedOutput: "Hello world!",
recipeConfig: [
{
op: "URL Decode",
args: [],
},
],
},
{
name: "URLDecode: decode plus as space",
input: "Hello%20world!",
expectedOutput: "Hello world!",
recipeConfig: [
{
op: "URL Decode",
args: [],
},
],
},
// URL Encode
{
name: "URLEncode: nothing",
input: "",
expectedOutput: "",
recipeConfig: [
{
op: "URL Encode",
args: [],
},
],
},
{
name: "URLEncode: spaces without special chars",
input: "Hello world!",
expectedOutput: "Hello%20world!",
recipeConfig: [
{
op: "URL Encode",
args: [],
},
],
},
{
name: "URLEncode: spaces with special chars",
input: "Hello world!",
expectedOutput: "Hello%20world%21",
recipeConfig: [
{
op: "URL Encode",
args: [true],
},
],
},
]);