Compare commits

...

13 Commits

Author SHA1 Message Date
n1474335
2762319dbb 7.5.0 2018-01-12 18:29:10 +00:00
n1474335
8e5d43dfa8 Merge branch 'feature-hamming' 2018-01-12 18:28:55 +00:00
n1474335
82ad8cc444 BigNumber Dish types are now converted to fixed notation instead of exponential when translated between types 2018-01-12 18:28:40 +00:00
n1474335
1d6bf39548 Added 'Hamming Distance' operation. 2018-01-12 18:17:28 +00:00
n1474335
fa938f832f Fixed IE 11 detection so that support message is correctly shown. 2018-01-10 18:38:17 +00:00
n1474335
6f59d9217c Changed 'Number' option to 'Integer' in PRNG op 2018-01-06 17:01:01 +00:00
n1474335
7176e5ca6e 7.4.0 2018-01-06 16:34:06 +00:00
n1474335
429829471f Merge branch 'feature-paste-performance' 2018-01-06 16:33:12 +00:00
n1474335
4760e539b7 PRNG operation now supports BigNumbers as output 2018-01-06 16:30:17 +00:00
n1474335
f53e7ad617 Chef now defaults to treat as UTF8 if option is not set 2018-01-06 16:21:42 +00:00
n1474335
28c83fa921 Dish translation now obeys UTF8 rules 2018-01-06 16:02:50 +00:00
n1474335
4588cd151c Data pasted into the input is treat as a file if it's over the IO threshold 2018-01-06 15:29:58 +00:00
n1474335
2d9f87abef Added more loading messages 2018-01-05 20:26:51 +00:00
13 changed files with 175 additions and 31 deletions

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "cyberchef",
"version": "7.3.0",
"version": "7.5.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "cyberchef",
"version": "7.3.0",
"version": "7.5.0",
"description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
"author": "n1474335 <n1474335@gmail.com>",
"homepage": "https://gchq.github.io/CyberChef",

View File

@@ -35,10 +35,11 @@ const Chef = function() {
*/
Chef.prototype.bake = async function(input, recipeConfig, options, progress, step) {
log.debug("Chef baking");
let startTime = new Date().getTime(),
const startTime = new Date().getTime(),
recipe = new Recipe(recipeConfig),
containsFc = recipe.containsFlowControl(),
error = false;
notUTF8 = options && options.hasOwnProperty("treatAsUtf8") && !options.treatAsUtf8;
let error = false;
if (containsFc && ENVIRONMENT_IS_WORKER()) self.setOption("attemptHighlight", false);
@@ -80,13 +81,13 @@ Chef.prototype.bake = async function(input, recipeConfig, options, progress, ste
// Depending on the size of the output, we may send it back as a string or an ArrayBuffer.
// This can prevent unnecessary casting as an ArrayBuffer can be easily downloaded as a file.
// The threshold is specified in KiB.
const threshold = (options.outputFileThreshold || 1024) * 1024;
const threshold = (options.ioDisplayThreshold || 1024) * 1024;
const returnType = this.dish.size() > threshold ? Dish.ARRAY_BUFFER : Dish.STRING;
return {
result: this.dish.type === Dish.HTML ?
this.dish.get(Dish.HTML) :
this.dish.get(returnType),
this.dish.get(Dish.HTML, notUTF8) :
this.dish.get(returnType, notUTF8),
type: Dish.enumLookup(this.dish.type),
progress: progress,
duration: new Date().getTime() - startTime,

View File

@@ -136,11 +136,12 @@ Dish.prototype.set = function(value, type) {
* Returns the value of the data in the type format specified.
*
* @param {number} type - The data type of value, see Dish enums.
* @param {boolean} [notUTF8] - Do not treat strings as UTF8.
* @returns {byteArray|string|number|ArrayBuffer|BigNumber} The value of the output data.
*/
Dish.prototype.get = function(type) {
Dish.prototype.get = function(type, notUTF8) {
if (this.type !== type) {
this.translate(type);
this.translate(type, notUTF8);
}
return this.value;
};
@@ -150,9 +151,11 @@ Dish.prototype.get = function(type) {
* Translates the data to the given type format.
*
* @param {number} toType - The data type of value, see Dish enums.
* @param {boolean} [notUTF8] - Do not treat strings as UTF8.
*/
Dish.prototype.translate = function(toType) {
Dish.prototype.translate = function(toType, notUTF8) {
log.debug(`Translating Dish from ${Dish.enumLookup(this.type)} to ${Dish.enumLookup(toType)}`);
const byteArrayToStr = notUTF8 ? Utils.byteArrayToChars : Utils.byteArrayToUtf8;
// Convert data to intermediate byteArray type
switch (this.type) {
@@ -170,7 +173,7 @@ Dish.prototype.translate = function(toType) {
this.value = Array.prototype.slice.call(new Uint8Array(this.value));
break;
case Dish.BIG_NUMBER:
this.value = this.value instanceof BigNumber ? Utils.strToByteArray(this.value.toString()) : [];
this.value = this.value instanceof BigNumber ? Utils.strToByteArray(this.value.toFixed()) : [];
break;
default:
break;
@@ -182,11 +185,11 @@ Dish.prototype.translate = function(toType) {
switch (toType) {
case Dish.STRING:
case Dish.HTML:
this.value = this.value ? Utils.byteArrayToUtf8(this.value) : "";
this.value = this.value ? byteArrayToStr(this.value) : "";
this.type = Dish.STRING;
break;
case Dish.NUMBER:
this.value = this.value ? parseFloat(Utils.byteArrayToUtf8(this.value)) : 0;
this.value = this.value ? parseFloat(byteArrayToStr(this.value)) : 0;
this.type = Dish.NUMBER;
break;
case Dish.ARRAY_BUFFER:
@@ -195,7 +198,7 @@ Dish.prototype.translate = function(toType) {
break;
case Dish.BIG_NUMBER:
try {
this.value = new BigNumber(Utils.byteArrayToUtf8(this.value));
this.value = new BigNumber(byteArrayToStr(this.value));
} catch (err) {
this.value = new BigNumber(NaN);
}

View File

@@ -189,6 +189,7 @@ const Categories = [
"Find / Replace",
"Regular expression",
"Offset checker",
"Hamming Distance",
"Convert distance",
"Convert area",
"Convert mass",

View File

@@ -3913,6 +3913,29 @@ const OperationConfig = {
}
]
},
"Hamming Distance": {
module: "Default",
description: "In information theory, the Hamming distance between two strings of equal length is the number of positions at which the corresponding symbols are different. In other words, it measures the minimum number of substitutions required to change one string into the other, or the minimum number of errors that could have transformed one string into the other. In a more general context, the Hamming distance is one of several string metrics for measuring the edit distance between two sequences.",
inputType: "string",
outputType: "string",
args: [
{
name: "Delimiter",
type: "binaryShortString",
value: StrUtils.HAMMING_DELIM
},
{
name: "Unit",
type: "option",
value: StrUtils.HAMMING_UNIT
},
{
name: "Input type",
type: "option",
value: StrUtils.HAMMING_INPUT_TYPE
}
]
}
};

View File

@@ -110,6 +110,7 @@ OpModules.Default = {
"Unescape string": StrUtils.runUnescape,
"Head": StrUtils.runHead,
"Tail": StrUtils.runTail,
"Hamming Distance": StrUtils.runHamming,
"Remove whitespace": Tidy.runRemoveWhitespace,
"Remove null bytes": Tidy.runRemoveNulls,
"Drop bytes": Tidy.runDropBytes,

View File

@@ -2,6 +2,7 @@ import Utils from "../Utils.js";
import CryptoJS from "crypto-js";
import forge from "imports-loader?jQuery=>null!node-forge/dist/forge.min.js";
import {blowfish as Blowfish} from "sladex-blowfish";
import BigNumber from "bignumber.js";
/**
@@ -520,7 +521,7 @@ DES uses a key length of 8 bytes (64 bits).`;
* @default
*/
PRNG_BYTES: 32,
PRNG_OUTPUT: ["Hex", "Number", "Byte array", "Raw"],
PRNG_OUTPUT: ["Hex", "Integer", "Byte array", "Raw"],
/**
* Pseudo-Random Number Generator operation.
@@ -542,17 +543,17 @@ DES uses a key length of 8 bytes (64 bits).`;
bytes = forge.random.getBytesSync(numBytes);
}
let value = 0,
let value = new BigNumber(0),
i;
switch (outputAs) {
case "Hex":
return forge.util.bytesToHex(bytes);
case "Number":
case "Integer":
for (i = bytes.length - 1; i >= 0; i--) {
value = (value * 256) + bytes.charCodeAt(i);
value = value.mul(256).plus(bytes.charCodeAt(i));
}
return value.toString();
return value.toFixed();
case "Byte array":
return JSON.stringify(Utils.strToCharcode(bytes));
case "Raw":

View File

@@ -509,6 +509,75 @@ const StrUtils = {
},
/**
* @constant
* @default
*/
HAMMING_DELIM: "\\n\\n",
/**
* @constant
* @default
*/
HAMMING_INPUT_TYPE: ["Raw string", "Hex"],
/**
* @constant
* @default
*/
HAMMING_UNIT: ["Byte", "Bit"],
/**
* Hamming Distance operation.
*
* @author GCHQ Contributor [2]
*
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
runHamming: function(input, args) {
const delim = args[0],
byByte = args[1] === "Byte",
inputType = args[2],
samples = input.split(delim);
if (samples.length !== 2) {
return "Error: You can only calculae the edit distance between 2 strings. Please ensure exactly two inputs are provided, separated by the specified delimiter.";
}
if (samples[0].length !== samples[1].length) {
return "Error: Both inputs must be of the same length.";
}
if (inputType === "Hex") {
samples[0] = Utils.fromHex(samples[0]);
samples[1] = Utils.fromHex(samples[1]);
} else {
samples[0] = Utils.strToByteArray(samples[0]);
samples[1] = Utils.strToByteArray(samples[1]);
}
let dist = 0;
for (let i = 0; i < samples[0].length; i++) {
const lhs = samples[0][i],
rhs = samples[1][i];
if (byByte && lhs !== rhs) {
dist++;
} else if (!byByte) {
let xord = lhs ^ rhs;
while (xord) {
dist++;
xord &= xord - 1;
}
}
}
return dist.toString();
},
/**
* Adds HTML highlights to matches within a string.
*

View File

@@ -61,9 +61,13 @@ InputWaiter.prototype.set = function(input) {
if (input instanceof File) {
this.setFile(input);
inputText.value = "";
this.setInputInfo(input.size, null);
} else {
inputText.value = input;
window.dispatchEvent(this.manager.statechange);
const lines = input.length < (this.app.options.ioDisplayThreshold * 1024) ?
input.count("\n") + 1 : null;
this.setInputInfo(input.length, lines);
}
};
@@ -81,6 +85,7 @@ InputWaiter.prototype.setFile = function(file) {
fileType = document.getElementById("input-file-type"),
fileLoaded = document.getElementById("input-file-loaded");
this.fileBuffer = new ArrayBuffer();
fileOverlay.style.display = "block";
fileName.textContent = file.name;
fileSize.textContent = file.size.toLocaleString() + " bytes";
@@ -100,21 +105,28 @@ InputWaiter.prototype.setInputInfo = function(length, lines) {
width = width < 2 ? 2 : width;
const lengthStr = length.toString().padStart(width, " ").replace(/ /g, "&nbsp;");
const linesStr = lines.toString().padStart(width, " ").replace(/ /g, "&nbsp;");
let msg = "length: " + lengthStr;
document.getElementById("input-info").innerHTML = "length: " + lengthStr + "<br>lines: " + linesStr;
if (typeof lines === "number") {
const linesStr = lines.toString().padStart(width, " ").replace(/ /g, "&nbsp;");
msg += "<br>lines: " + linesStr;
}
document.getElementById("input-info").innerHTML = msg;
};
/**
* Handler for input scroll events.
* Scrolls the highlighter pane to match the input textarea position and updates history state.
* Handler for input change events.
*
* @param {event} e
*
* @fires Manager#statechange
*/
InputWaiter.prototype.inputChange = function(e) {
// Ignore this function if the input is a File
if (this.fileBuffer) return;
// Remove highlighting from input and output panes as the offsets might be different now
this.manager.highlighter.removeHighlights();
@@ -123,18 +135,47 @@ InputWaiter.prototype.inputChange = function(e) {
// Update the input metadata info
const inputText = this.get();
const lines = inputText.count("\n") + 1;
const lines = inputText.length < (this.app.options.ioDisplayThreshold * 1024) ?
inputText.count("\n") + 1 : null;
this.setInputInfo(inputText.length, lines);
if (this.badKeys.indexOf(e.keyCode) < 0) {
if (e && this.badKeys.indexOf(e.keyCode) < 0) {
// Fire the statechange event as the input has been modified
window.dispatchEvent(this.manager.statechange);
}
};
/**
* Handler for input paste events.
* Checks that the size of the input is below the display limit, otherwise treats it as a file/blob.
*
* @param {event} e
*/
InputWaiter.prototype.inputPaste = function(e) {
const pastedData = e.clipboardData.getData("Text");
if (pastedData.length < (this.app.options.ioDisplayThreshold * 1024)) {
this.inputChange(e);
} else {
e.preventDefault();
e.stopPropagation();
const file = new File([pastedData], "PastedData", {
type: "text/plain",
lastModified: Date.now()
});
this.loaderWorker = new LoaderWorker();
this.loaderWorker.addEventListener("message", this.handleLoaderMessage.bind(this));
this.loaderWorker.postMessage({"file": file});
this.set(file);
return false;
}
};
/**
* Handler for input dragover events.
* Gives the user a visual cue to show that items can be dropped here.

View File

@@ -132,7 +132,8 @@ Manager.prototype.initialiseEventListeners = function() {
this.addDynamicListener("#rec-list", "operationremove", this.recipe.opRemove.bind(this.recipe));
// Input
this.addMultiEventListener("#input-text", "keyup paste", this.input.inputChange, this.input);
this.addMultiEventListener("#input-text", "keyup", this.input.inputChange, this.input);
this.addMultiEventListener("#input-text", "paste", this.input.inputPaste, this.input);
document.getElementById("reset-layout").addEventListener("click", this.app.resetLayout.bind(this.app));
document.getElementById("clr-io").addEventListener("click", this.input.clearIoClick.bind(this.input));
this.addListeners("#input-text,#input-file", "dragover", this.input.inputDragover, this.input);

View File

@@ -59,6 +59,9 @@
"for i in range(additional): Pylon()",
"(creating unresolved tension...",
"Symlinking emacs and vim to ed...",
"Training branch predictor...",
"Timing cache hits...",
"Speculatively executing recipes..."
];
// Shuffle array using Durstenfeld algorithm
@@ -115,7 +118,7 @@
<span id="notice">
<script type="text/javascript">
// Must be text/javascript rather than application/javascript otherwise IE won't recognise it...
if (navigator.userAgent && navigator.userAgent.match(/MSIE \d\d?\./)) {
if (navigator.userAgent && navigator.userAgent.match(/Trident/)) {
document.write("Internet Explorer is not supported, please use Firefox or Chrome instead");
alert("Internet Explorer is not supported, please use Firefox or Chrome instead");
}
@@ -369,8 +372,8 @@
<label for="errorTimeout"> Operation error timeout in ms (0 for never)</label>
</div>
<div class="option-item">
<input type="number" option="outputFileThreshold" id="outputFileThreshold" />
<label for="outputFileThreshold"> Size threshold for treating the output as a file (KiB)</label>
<input type="number" option="ioDisplayThreshold" id="ioDisplayThreshold" />
<label for="ioDisplayThreshold"> Size threshold for treating the input and output as a file (KiB)</label>
</div>
<div class="option-item">
<select option="logLevel" id="logLevel">

View File

@@ -47,7 +47,7 @@ function main() {
attemptHighlight: true,
theme: "classic",
useMetaKey: false,
outputFileThreshold: 1024,
ioDisplayThreshold: 512,
logLevel: "info"
};