diff --git a/Dockerfile b/Dockerfile
index d63a8ca3b..ba605fd71 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -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
# Remove TARGETARCH if docker buildx is supported in the CI release as --platform=$TARGETPLATFORM will be automatically set
-ARG TARGETARCH
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/
diff --git a/README.md b/README.md
index 5549bda2a..89f0371d5 100755
--- a/README.md
+++ b/README.md
@@ -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!
-## 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
docker build --tag cyberchef --ulimit nofile=10000 .
+```
+2. Run the docker container
+```bash
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
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)
## How it works
diff --git a/package-lock.json b/package-lock.json
index 8dd38b7a0..b374df4bc 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -46,6 +46,8 @@
"file-saver": "^2.0.5",
"flat": "^6.0.1",
"geodesy": "1.1.3",
+ "handlebars": "^4.7.8",
+ "hash-wasm": "^4.12.0",
"highlight.js": "^11.9.0",
"ieee754": "^1.2.1",
"jimp": "^0.22.12",
@@ -95,6 +97,7 @@
"ua-parser-js": "^1.0.38",
"unorm": "^1.6.0",
"utf8": "^3.0.0",
+ "uuid": "^11.1.0",
"vkbeautify": "^0.99.3",
"xpath": "0.0.34",
"xregexp": "^5.1.1",
@@ -10843,6 +10846,27 @@
"dev": true,
"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": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
@@ -10941,6 +10965,11 @@
"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": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
@@ -13663,7 +13692,6 @@
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
- "dev": true,
"license": "MIT"
},
"node_modules/netmask": {
@@ -13885,6 +13913,15 @@
"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": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
@@ -16828,6 +16865,15 @@
"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": {
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz",
@@ -16868,7 +16914,6 @@
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "devOptional": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
@@ -18115,13 +18160,15 @@
}
},
"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,
- "license": "MIT",
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
+ "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==",
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
"bin": {
- "uuid": "dist/bin/uuid"
+ "uuid": "dist/esm/bin/uuid"
}
},
"node_modules/v8flags": {
@@ -18886,6 +18933,12 @@
"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": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-3.0.8.tgz",
diff --git a/package.json b/package.json
index feb18bc8d..9191ab6f0 100644
--- a/package.json
+++ b/package.json
@@ -132,6 +132,8 @@
"file-saver": "^2.0.5",
"flat": "^6.0.1",
"geodesy": "1.1.3",
+ "handlebars": "^4.7.8",
+ "hash-wasm": "^4.12.0",
"highlight.js": "^11.9.0",
"ieee754": "^1.2.1",
"jimp": "^0.22.12",
@@ -181,6 +183,7 @@
"ua-parser-js": "^1.0.38",
"unorm": "^1.6.0",
"utf8": "^3.0.0",
+ "uuid": "^11.1.0",
"vkbeautify": "^0.99.3",
"xpath": "0.0.34",
"xregexp": "^5.1.1",
diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json
index 0226e33bb..d26ffaa5c 100644
--- a/src/core/config/Categories.json
+++ b/src/core/config/Categories.json
@@ -376,7 +376,8 @@
"Extract EXIF",
"Extract ID3",
"Extract Files",
- "RAKE"
+ "RAKE",
+ "Template"
]
},
{
@@ -426,6 +427,7 @@
"Snefru",
"BLAKE2b",
"BLAKE2s",
+ "BLAKE3",
"GOST Hash",
"Streebog",
"SSDEEP",
@@ -550,6 +552,7 @@
"Pseudo-Random Number Generator",
"Generate De Bruijn Sequence",
"Generate UUID",
+ "Analyse UUID",
"Generate TOTP",
"Generate HOTP",
"Generate QR Code",
diff --git a/src/core/operations/AnalyseUUID.mjs b/src/core/operations/AnalyseUUID.mjs
new file mode 100644
index 000000000..b35060179
--- /dev/null
+++ b/src/core/operations/AnalyseUUID.mjs
@@ -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;
diff --git a/src/core/operations/BLAKE3.mjs b/src/core/operations/BLAKE3.mjs
new file mode 100644
index 000000000..0f686120a
--- /dev/null
+++ b/src/core/operations/BLAKE3.mjs
@@ -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;
diff --git a/src/core/operations/ExtractEmailAddresses.mjs b/src/core/operations/ExtractEmailAddresses.mjs
index f50e1aaf5..34b838ab3 100644
--- a/src/core/operations/ExtractEmailAddresses.mjs
+++ b/src/core/operations/ExtractEmailAddresses.mjs
@@ -51,7 +51,7 @@ class ExtractEmailAddresses extends Operation {
run(input, args) {
const [displayTotal, sort, unique] = args,
// 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(
input,
diff --git a/src/core/operations/ExtractIPAddresses.mjs b/src/core/operations/ExtractIPAddresses.mjs
index 97b52478d..b74ec8fe2 100644
--- a/src/core/operations/ExtractIPAddresses.mjs
+++ b/src/core/operations/ExtractIPAddresses.mjs
@@ -21,7 +21,7 @@ class ExtractIPAddresses extends Operation {
this.name = "Extract IP addresses";
this.module = "Regex";
- this.description = "Extracts all IPv4 and IPv6 addresses.
Warning: Given a string 710.65.0.456, this will match 10.65.0.45 so always check the original input!";
+ this.description = "Extracts all IPv4 and IPv6 addresses.
Warning: Given a string 1.2.3.4.5.6.7.8, this will match 1.2.3.4 and 5.6.7.8 so always check the original input!";
this.inputType = "string";
this.outputType = "string";
this.args = [
@@ -65,7 +65,21 @@ class ExtractIPAddresses extends Operation {
*/
run(input, 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 = "(?
A version 4 UUID relies on random numbers, in this case generated using window.crypto if available and falling back to Math.random if not.";
+ this.description =
+ "Generates an RFC 9562 (formerly RFC 4122) compliant Universally Unique Identifier (UUID), " +
+ "also known as a Globally Unique Identifier (GUID).
" +
+ "
" +
+ "We currently support generating the following UUID versions:
" +
+ "
uuid package.