mirror of
https://github.com/gchq/CyberChef.git
synced 2025-04-22 07:46:16 -04:00
Merge remote-tracking branch 'origin/master' into v10
This commit is contained in:
commit
2e201c747a
48 changed files with 2767 additions and 70 deletions
128
src/core/operations/AESKeyUnwrap.mjs
Normal file
128
src/core/operations/AESKeyUnwrap.mjs
Normal file
|
@ -0,0 +1,128 @@
|
|||
/**
|
||||
* @author mikecat
|
||||
* @copyright Crown Copyright 2022
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import { toHexFast } from "../lib/Hex.mjs";
|
||||
import forge from "node-forge";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
/**
|
||||
* AES Key Unwrap operation
|
||||
*/
|
||||
class AESKeyUnwrap extends Operation {
|
||||
|
||||
/**
|
||||
* AESKeyUnwrap constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "AES Key Unwrap";
|
||||
this.module = "Ciphers";
|
||||
this.description = "Decryptor for a key wrapping algorithm defined in RFC3394, which is used to protect keys in untrusted storage or communications, using AES.<br><br>This algorithm uses an AES key (KEK: key-encryption key) and a 64-bit IV to decrypt 64-bit blocks.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Key_wrap";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Key (KEK)",
|
||||
"type": "toggleString",
|
||||
"value": "",
|
||||
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
},
|
||||
{
|
||||
"name": "IV",
|
||||
"type": "toggleString",
|
||||
"value": "a6a6a6a6a6a6a6a6",
|
||||
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
},
|
||||
{
|
||||
"name": "Input",
|
||||
"type": "option",
|
||||
"value": ["Hex", "Raw"]
|
||||
},
|
||||
{
|
||||
"name": "Output",
|
||||
"type": "option",
|
||||
"value": ["Hex", "Raw"]
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const kek = Utils.convertToByteString(args[0].string, args[0].option),
|
||||
iv = Utils.convertToByteString(args[1].string, args[1].option),
|
||||
inputType = args[2],
|
||||
outputType = args[3];
|
||||
|
||||
if (kek.length !== 16 && kek.length !== 24 && kek.length !== 32) {
|
||||
throw new OperationError("KEK must be either 16, 24, or 32 bytes (currently " + kek.length + " bytes)");
|
||||
}
|
||||
if (iv.length !== 8) {
|
||||
throw new OperationError("IV must be 8 bytes (currently " + iv.length + " bytes)");
|
||||
}
|
||||
const inputData = Utils.convertToByteString(input, inputType);
|
||||
if (inputData.length % 8 !== 0 || inputData.length < 24) {
|
||||
throw new OperationError("input must be 8n (n>=3) bytes (currently " + inputData.length + " bytes)");
|
||||
}
|
||||
|
||||
const cipher = forge.cipher.createCipher("AES-ECB", kek);
|
||||
cipher.start();
|
||||
cipher.update(forge.util.createBuffer(""));
|
||||
cipher.finish();
|
||||
const paddingBlock = cipher.output.getBytes();
|
||||
|
||||
const decipher = forge.cipher.createDecipher("AES-ECB", kek);
|
||||
|
||||
let A = inputData.substring(0, 8);
|
||||
const R = [];
|
||||
for (let i = 8; i < inputData.length; i += 8) {
|
||||
R.push(inputData.substring(i, i + 8));
|
||||
}
|
||||
let cntLower = R.length >>> 0;
|
||||
let cntUpper = (R.length / ((1 << 30) * 4)) >>> 0;
|
||||
cntUpper = cntUpper * 6 + ((cntLower * 6 / ((1 << 30) * 4)) >>> 0);
|
||||
cntLower = cntLower * 6 >>> 0;
|
||||
for (let j = 5; j >= 0; j--) {
|
||||
for (let i = R.length - 1; i >= 0; i--) {
|
||||
const aBuffer = Utils.strToArrayBuffer(A);
|
||||
const aView = new DataView(aBuffer);
|
||||
aView.setUint32(0, aView.getUint32(0) ^ cntUpper);
|
||||
aView.setUint32(4, aView.getUint32(4) ^ cntLower);
|
||||
A = Utils.arrayBufferToStr(aBuffer, false);
|
||||
decipher.start();
|
||||
decipher.update(forge.util.createBuffer(A + R[i] + paddingBlock));
|
||||
decipher.finish();
|
||||
const B = decipher.output.getBytes();
|
||||
A = B.substring(0, 8);
|
||||
R[i] = B.substring(8, 16);
|
||||
cntLower--;
|
||||
if (cntLower < 0) {
|
||||
cntUpper--;
|
||||
cntLower = 0xffffffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (A !== iv) {
|
||||
throw new OperationError("IV mismatch");
|
||||
}
|
||||
const P = R.join("");
|
||||
|
||||
if (outputType === "Hex") {
|
||||
return toHexFast(Utils.strToArrayBuffer(P));
|
||||
}
|
||||
return P;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default AESKeyUnwrap;
|
115
src/core/operations/AESKeyWrap.mjs
Normal file
115
src/core/operations/AESKeyWrap.mjs
Normal file
|
@ -0,0 +1,115 @@
|
|||
/**
|
||||
* @author mikecat
|
||||
* @copyright Crown Copyright 2022
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import { toHexFast } from "../lib/Hex.mjs";
|
||||
import forge from "node-forge";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
/**
|
||||
* AES Key Wrap operation
|
||||
*/
|
||||
class AESKeyWrap extends Operation {
|
||||
|
||||
/**
|
||||
* AESKeyWrap constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "AES Key Wrap";
|
||||
this.module = "Ciphers";
|
||||
this.description = "A key wrapping algorithm defined in RFC3394, which is used to protect keys in untrusted storage or communications, using AES.<br><br>This algorithm uses an AES key (KEK: key-encryption key) and a 64-bit IV to encrypt 64-bit blocks.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Key_wrap";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Key (KEK)",
|
||||
"type": "toggleString",
|
||||
"value": "",
|
||||
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
},
|
||||
{
|
||||
"name": "IV",
|
||||
"type": "toggleString",
|
||||
"value": "a6a6a6a6a6a6a6a6",
|
||||
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
},
|
||||
{
|
||||
"name": "Input",
|
||||
"type": "option",
|
||||
"value": ["Hex", "Raw"]
|
||||
},
|
||||
{
|
||||
"name": "Output",
|
||||
"type": "option",
|
||||
"value": ["Hex", "Raw"]
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const kek = Utils.convertToByteString(args[0].string, args[0].option),
|
||||
iv = Utils.convertToByteString(args[1].string, args[1].option),
|
||||
inputType = args[2],
|
||||
outputType = args[3];
|
||||
|
||||
if (kek.length !== 16 && kek.length !== 24 && kek.length !== 32) {
|
||||
throw new OperationError("KEK must be either 16, 24, or 32 bytes (currently " + kek.length + " bytes)");
|
||||
}
|
||||
if (iv.length !== 8) {
|
||||
throw new OperationError("IV must be 8 bytes (currently " + iv.length + " bytes)");
|
||||
}
|
||||
const inputData = Utils.convertToByteString(input, inputType);
|
||||
if (inputData.length % 8 !== 0 || inputData.length < 16) {
|
||||
throw new OperationError("input must be 8n (n>=2) bytes (currently " + inputData.length + " bytes)");
|
||||
}
|
||||
|
||||
const cipher = forge.cipher.createCipher("AES-ECB", kek);
|
||||
|
||||
let A = iv;
|
||||
const R = [];
|
||||
for (let i = 0; i < inputData.length; i += 8) {
|
||||
R.push(inputData.substring(i, i + 8));
|
||||
}
|
||||
let cntLower = 1, cntUpper = 0;
|
||||
for (let j = 0; j < 6; j++) {
|
||||
for (let i = 0; i < R.length; i++) {
|
||||
cipher.start();
|
||||
cipher.update(forge.util.createBuffer(A + R[i]));
|
||||
cipher.finish();
|
||||
const B = cipher.output.getBytes();
|
||||
const msbBuffer = Utils.strToArrayBuffer(B.substring(0, 8));
|
||||
const msbView = new DataView(msbBuffer);
|
||||
msbView.setUint32(0, msbView.getUint32(0) ^ cntUpper);
|
||||
msbView.setUint32(4, msbView.getUint32(4) ^ cntLower);
|
||||
A = Utils.arrayBufferToStr(msbBuffer, false);
|
||||
R[i] = B.substring(8, 16);
|
||||
cntLower++;
|
||||
if (cntLower > 0xffffffff) {
|
||||
cntUpper++;
|
||||
cntLower = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
const C = A + R.join("");
|
||||
|
||||
if (outputType === "Hex") {
|
||||
return toHexFast(Utils.strToArrayBuffer(C));
|
||||
}
|
||||
return C;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default AESKeyWrap;
|
52
src/core/operations/AMFDecode.mjs
Normal file
52
src/core/operations/AMFDecode.mjs
Normal file
|
@ -0,0 +1,52 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2022
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import "reflect-metadata"; // Required as a shim for the amf library
|
||||
import { AMF0, AMF3 } from "@astronautlabs/amf";
|
||||
|
||||
/**
|
||||
* AMF Decode operation
|
||||
*/
|
||||
class AMFDecode extends Operation {
|
||||
|
||||
/**
|
||||
* AMFDecode constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "AMF Decode";
|
||||
this.module = "Encodings";
|
||||
this.description = "Action Message Format (AMF) is a binary format used to serialize object graphs such as ActionScript objects and XML, or send messages between an Adobe Flash client and a remote service, usually a Flash Media Server or third party alternatives.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Action_Message_Format";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "JSON";
|
||||
this.args = [
|
||||
{
|
||||
name: "Format",
|
||||
type: "option",
|
||||
value: ["AMF0", "AMF3"],
|
||||
defaultIndex: 1
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {Object[]} args
|
||||
* @returns {JSON}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [format] = args;
|
||||
const handler = format === "AMF0" ? AMF0 : AMF3;
|
||||
const encoded = new Uint8Array(input);
|
||||
return handler.Value.deserialize(encoded);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default AMFDecode;
|
52
src/core/operations/AMFEncode.mjs
Normal file
52
src/core/operations/AMFEncode.mjs
Normal file
|
@ -0,0 +1,52 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2022
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import "reflect-metadata"; // Required as a shim for the amf library
|
||||
import { AMF0, AMF3 } from "@astronautlabs/amf";
|
||||
|
||||
/**
|
||||
* AMF Encode operation
|
||||
*/
|
||||
class AMFEncode extends Operation {
|
||||
|
||||
/**
|
||||
* AMFEncode constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "AMF Encode";
|
||||
this.module = "Encodings";
|
||||
this.description = "Action Message Format (AMF) is a binary format used to serialize object graphs such as ActionScript objects and XML, or send messages between an Adobe Flash client and a remote service, usually a Flash Media Server or third party alternatives.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Action_Message_Format";
|
||||
this.inputType = "JSON";
|
||||
this.outputType = "ArrayBuffer";
|
||||
this.args = [
|
||||
{
|
||||
name: "Format",
|
||||
type: "option",
|
||||
value: ["AMF0", "AMF3"],
|
||||
defaultIndex: 1
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {JSON} input
|
||||
* @param {Object[]} args
|
||||
* @returns {ArrayBuffer}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [format] = args;
|
||||
const handler = format === "AMF0" ? AMF0 : AMF3;
|
||||
const output = handler.Value.any(input).serialize();
|
||||
return output.buffer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default AMFEncode;
|
149
src/core/operations/CMAC.mjs
Normal file
149
src/core/operations/CMAC.mjs
Normal file
|
@ -0,0 +1,149 @@
|
|||
/**
|
||||
* @author mikecat
|
||||
* @copyright Crown Copyright 2022
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import forge from "node-forge";
|
||||
import { toHexFast } from "../lib/Hex.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
/**
|
||||
* CMAC operation
|
||||
*/
|
||||
class CMAC extends Operation {
|
||||
|
||||
/**
|
||||
* CMAC constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "CMAC";
|
||||
this.module = "Crypto";
|
||||
this.description = "CMAC is a block-cipher based message authentication code algorithm.<br><br>RFC4493 defines AES-CMAC that uses AES encryption with a 128-bit key.<br>NIST SP 800-38B suggests usages of AES with other key lengths and Triple DES.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/CMAC";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Key",
|
||||
"type": "toggleString",
|
||||
"value": "",
|
||||
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
},
|
||||
{
|
||||
"name": "Encryption algorithm",
|
||||
"type": "option",
|
||||
"value": ["AES", "Triple DES"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const key = Utils.convertToByteString(args[0].string, args[0].option);
|
||||
const algo = args[1];
|
||||
|
||||
const info = (function() {
|
||||
switch (algo) {
|
||||
case "AES":
|
||||
if (key.length !== 16 && key.length !== 24 && key.length !== 32) {
|
||||
throw new OperationError("The key for AES must be either 16, 24, or 32 bytes (currently " + key.length + " bytes)");
|
||||
}
|
||||
return {
|
||||
"algorithm": "AES-ECB",
|
||||
"key": key,
|
||||
"blockSize": 16,
|
||||
"Rb": new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x87]),
|
||||
};
|
||||
case "Triple DES":
|
||||
if (key.length !== 16 && key.length !== 24) {
|
||||
throw new OperationError("The key for Triple DES must be 16 or 24 bytes (currently " + key.length + " bytes)");
|
||||
}
|
||||
return {
|
||||
"algorithm": "3DES-ECB",
|
||||
"key": key.length === 16 ? key + key.substring(0, 8) : key,
|
||||
"blockSize": 8,
|
||||
"Rb": new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0x1b]),
|
||||
};
|
||||
default:
|
||||
throw new OperationError("Undefined encryption algorithm");
|
||||
}
|
||||
})();
|
||||
|
||||
const xor = function(a, b, out) {
|
||||
if (!out) out = new Uint8Array(a.length);
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
out[i] = a[i] ^ b[i];
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
const leftShift1 = function(a) {
|
||||
const out = new Uint8Array(a.length);
|
||||
let carry = 0;
|
||||
for (let i = a.length - 1; i >= 0; i--) {
|
||||
out[i] = (a[i] << 1) | carry;
|
||||
carry = a[i] >> 7;
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
const cipher = forge.cipher.createCipher(info.algorithm, info.key);
|
||||
const encrypt = function(a, out) {
|
||||
if (!out) out = new Uint8Array(a.length);
|
||||
cipher.start();
|
||||
cipher.update(forge.util.createBuffer(a));
|
||||
cipher.finish();
|
||||
const cipherText = cipher.output.getBytes();
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
out[i] = cipherText.charCodeAt(i);
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
const L = encrypt(new Uint8Array(info.blockSize));
|
||||
const K1 = leftShift1(L);
|
||||
if (L[0] & 0x80) xor(K1, info.Rb, K1);
|
||||
const K2 = leftShift1(K1);
|
||||
if (K1[0] & 0x80) xor(K2, info.Rb, K2);
|
||||
|
||||
const n = Math.ceil(input.byteLength / info.blockSize);
|
||||
const lastBlock = (function() {
|
||||
if (n === 0) {
|
||||
const data = new Uint8Array(K2);
|
||||
data[0] ^= 0x80;
|
||||
return data;
|
||||
}
|
||||
const inputLast = new Uint8Array(input, info.blockSize * (n - 1));
|
||||
if (inputLast.length === info.blockSize) {
|
||||
return xor(inputLast, K1, inputLast);
|
||||
} else {
|
||||
const data = new Uint8Array(info.blockSize);
|
||||
data.set(inputLast, 0);
|
||||
data[inputLast.length] = 0x80;
|
||||
return xor(data, K2, data);
|
||||
}
|
||||
})();
|
||||
|
||||
const X = new Uint8Array(info.blockSize);
|
||||
const Y = new Uint8Array(info.blockSize);
|
||||
for (let i = 0; i < n - 1; i++) {
|
||||
xor(X, new Uint8Array(input, info.blockSize * i, info.blockSize), Y);
|
||||
encrypt(Y, X);
|
||||
}
|
||||
xor(lastBlock, X, Y);
|
||||
const T = encrypt(Y);
|
||||
return toHexFast(T);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default CMAC;
|
234
src/core/operations/ChaCha.mjs
Normal file
234
src/core/operations/ChaCha.mjs
Normal file
|
@ -0,0 +1,234 @@
|
|||
/**
|
||||
* @author joostrijneveld [joost@joostrijneveld.nl]
|
||||
* @copyright Crown Copyright 2022
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import { toHex } from "../lib/Hex.mjs";
|
||||
|
||||
/**
|
||||
* Computes the ChaCha block function
|
||||
*
|
||||
* @param {byteArray} key
|
||||
* @param {byteArray} nonce
|
||||
* @param {byteArray} counter
|
||||
* @param {integer} rounds
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
function chacha(key, nonce, counter, rounds) {
|
||||
const tau = "expand 16-byte k";
|
||||
const sigma = "expand 32-byte k";
|
||||
|
||||
let state, c;
|
||||
if (key.length === 16) {
|
||||
c = Utils.strToByteArray(tau);
|
||||
state = c.concat(key).concat(key);
|
||||
} else {
|
||||
c = Utils.strToByteArray(sigma);
|
||||
state = c.concat(key);
|
||||
}
|
||||
state = state.concat(counter).concat(nonce);
|
||||
|
||||
const x = Array();
|
||||
for (let i = 0; i < 64; i += 4) {
|
||||
x.push(Utils.byteArrayToInt(state.slice(i, i + 4), "little"));
|
||||
}
|
||||
const a = [...x];
|
||||
|
||||
/**
|
||||
* Macro to compute a 32-bit rotate-left operation
|
||||
*
|
||||
* @param {integer} x
|
||||
* @param {integer} n
|
||||
* @returns {integer}
|
||||
*/
|
||||
function ROL32(x, n) {
|
||||
return ((x << n) & 0xFFFFFFFF) | (x >>> (32 - n));
|
||||
}
|
||||
|
||||
/**
|
||||
* Macro to compute a single ChaCha quarterround operation
|
||||
*
|
||||
* @param {integer} x
|
||||
* @param {integer} a
|
||||
* @param {integer} b
|
||||
* @param {integer} c
|
||||
* @param {integer} d
|
||||
* @returns {integer}
|
||||
*/
|
||||
function quarterround(x, a, b, c, d) {
|
||||
x[a] = ((x[a] + x[b]) & 0xFFFFFFFF); x[d] = ROL32(x[d] ^ x[a], 16);
|
||||
x[c] = ((x[c] + x[d]) & 0xFFFFFFFF); x[b] = ROL32(x[b] ^ x[c], 12);
|
||||
x[a] = ((x[a] + x[b]) & 0xFFFFFFFF); x[d] = ROL32(x[d] ^ x[a], 8);
|
||||
x[c] = ((x[c] + x[d]) & 0xFFFFFFFF); x[b] = ROL32(x[b] ^ x[c], 7);
|
||||
}
|
||||
|
||||
for (let i = 0; i < rounds / 2; i++) {
|
||||
quarterround(x, 0, 4, 8, 12);
|
||||
quarterround(x, 1, 5, 9, 13);
|
||||
quarterround(x, 2, 6, 10, 14);
|
||||
quarterround(x, 3, 7, 11, 15);
|
||||
quarterround(x, 0, 5, 10, 15);
|
||||
quarterround(x, 1, 6, 11, 12);
|
||||
quarterround(x, 2, 7, 8, 13);
|
||||
quarterround(x, 3, 4, 9, 14);
|
||||
}
|
||||
|
||||
for (let i = 0; i < 16; i++) {
|
||||
x[i] = (x[i] + a[i]) & 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
let output = Array();
|
||||
for (let i = 0; i < 16; i++) {
|
||||
output = output.concat(Utils.intToByteArray(x[i], 4, "little"));
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* ChaCha operation
|
||||
*/
|
||||
class ChaCha extends Operation {
|
||||
|
||||
/**
|
||||
* ChaCha constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "ChaCha";
|
||||
this.module = "Default";
|
||||
this.description = "ChaCha is a stream cipher designed by Daniel J. Bernstein. It is a variant of the Salsa stream cipher. Several parameterizations exist; 'ChaCha' may refer to the original construction, or to the variant as described in RFC-8439. ChaCha is often used with Poly1305, in the ChaCha20-Poly1305 AEAD construction.<br><br><b>Key:</b> ChaCha uses a key of 16 or 32 bytes (128 or 256 bits).<br><br><b>Nonce:</b> ChaCha uses a nonce of 8 or 12 bytes (64 or 96 bits).<br><br><b>Counter:</b> ChaCha uses a counter of 4 or 8 bytes (32 or 64 bits); together, the nonce and counter must add up to 16 bytes. The counter starts at zero at the start of the keystream, and is incremented at every 64 bytes.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Salsa20#ChaCha_variant";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Key",
|
||||
"type": "toggleString",
|
||||
"value": "",
|
||||
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
},
|
||||
{
|
||||
"name": "Nonce",
|
||||
"type": "toggleString",
|
||||
"value": "",
|
||||
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64", "Integer"]
|
||||
},
|
||||
{
|
||||
"name": "Counter",
|
||||
"type": "number",
|
||||
"value": 0,
|
||||
"min": 0
|
||||
},
|
||||
{
|
||||
"name": "Rounds",
|
||||
"type": "option",
|
||||
"value": ["20", "12", "8"]
|
||||
},
|
||||
{
|
||||
"name": "Input",
|
||||
"type": "option",
|
||||
"value": ["Hex", "Raw"]
|
||||
},
|
||||
{
|
||||
"name": "Output",
|
||||
"type": "option",
|
||||
"value": ["Raw", "Hex"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const key = Utils.convertToByteArray(args[0].string, args[0].option),
|
||||
nonceType = args[1].option,
|
||||
rounds = parseInt(args[3], 10),
|
||||
inputType = args[4],
|
||||
outputType = args[5];
|
||||
|
||||
if (key.length !== 16 && key.length !== 32) {
|
||||
throw new OperationError(`Invalid key length: ${key.length} bytes.
|
||||
|
||||
ChaCha uses a key of 16 or 32 bytes (128 or 256 bits).`);
|
||||
}
|
||||
|
||||
let counter, nonce, counterLength;
|
||||
if (nonceType === "Integer") {
|
||||
nonce = Utils.intToByteArray(parseInt(args[1].string, 10), 12, "little");
|
||||
counterLength = 4;
|
||||
} else {
|
||||
nonce = Utils.convertToByteArray(args[1].string, args[1].option);
|
||||
if (!(nonce.length === 12 || nonce.length === 8)) {
|
||||
throw new OperationError(`Invalid nonce length: ${nonce.length} bytes.
|
||||
|
||||
ChaCha uses a nonce of 8 or 12 bytes (64 or 96 bits).`);
|
||||
}
|
||||
counterLength = 16 - nonce.length;
|
||||
}
|
||||
counter = Utils.intToByteArray(args[2], counterLength, "little");
|
||||
|
||||
const output = [];
|
||||
input = Utils.convertToByteArray(input, inputType);
|
||||
|
||||
let counterAsInt = Utils.byteArrayToInt(counter, "little");
|
||||
for (let i = 0; i < input.length; i += 64) {
|
||||
counter = Utils.intToByteArray(counterAsInt, counterLength, "little");
|
||||
const stream = chacha(key, nonce, counter, rounds);
|
||||
for (let j = 0; j < 64 && i + j < input.length; j++) {
|
||||
output.push(input[i + j] ^ stream[j]);
|
||||
}
|
||||
counterAsInt++;
|
||||
}
|
||||
|
||||
if (outputType === "Hex") {
|
||||
return toHex(output);
|
||||
} else {
|
||||
return Utils.arrayBufferToStr(output);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight ChaCha
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlight(pos, args) {
|
||||
const inputType = args[4],
|
||||
outputType = args[5];
|
||||
if (inputType === "Raw" && outputType === "Raw") {
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight ChaCha in reverse
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlightReverse(pos, args) {
|
||||
const inputType = args[4],
|
||||
outputType = args[5];
|
||||
if (inputType === "Raw" && outputType === "Raw") {
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default ChaCha;
|
|
@ -35,10 +35,18 @@ class Fletcher32Checksum extends Operation {
|
|||
run(input, args) {
|
||||
let a = 0,
|
||||
b = 0;
|
||||
input = new Uint8Array(input);
|
||||
if (ArrayBuffer.isView(input)) {
|
||||
input = new DataView(input.buffer, input.byteOffset, input.byteLength);
|
||||
} else {
|
||||
input = new DataView(input);
|
||||
}
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
a = (a + input[i]) % 0xffff;
|
||||
for (let i = 0; i < input.byteLength - 1; i += 2) {
|
||||
a = (a + input.getUint16(i, true)) % 0xffff;
|
||||
b = (b + a) % 0xffff;
|
||||
}
|
||||
if (input.byteLength % 2 !== 0) {
|
||||
a = (a + input.getUint8(input.byteLength - 1)) % 0xffff;
|
||||
b = (b + a) % 0xffff;
|
||||
}
|
||||
|
||||
|
|
|
@ -35,10 +35,22 @@ class Fletcher64Checksum extends Operation {
|
|||
run(input, args) {
|
||||
let a = 0,
|
||||
b = 0;
|
||||
input = new Uint8Array(input);
|
||||
if (ArrayBuffer.isView(input)) {
|
||||
input = new DataView(input.buffer, input.byteOffset, input.byteLength);
|
||||
} else {
|
||||
input = new DataView(input);
|
||||
}
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
a = (a + input[i]) % 0xffffffff;
|
||||
for (let i = 0; i < input.byteLength - 3; i += 4) {
|
||||
a = (a + input.getUint32(i, true)) % 0xffffffff;
|
||||
b = (b + a) % 0xffffffff;
|
||||
}
|
||||
if (input.byteLength % 4 !== 0) {
|
||||
let lastValue = 0;
|
||||
for (let i = 0; i < input.byteLength % 4; i++) {
|
||||
lastValue = (lastValue << 8) | input.getUint8(input.byteLength - 1 - i);
|
||||
}
|
||||
a = (a + lastValue) % 0xffffffff;
|
||||
b = (b + a) % 0xffffffff;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import {alphabetName, ALPHABET_OPTIONS} from "../lib/Base85.mjs";
|
||||
import {ALPHABET_OPTIONS} from "../lib/Base85.mjs";
|
||||
|
||||
/**
|
||||
* From Base85 operation
|
||||
|
@ -37,6 +37,12 @@ class FromBase85 extends Operation {
|
|||
type: "boolean",
|
||||
value: true
|
||||
},
|
||||
{
|
||||
name: "All-zero group char",
|
||||
type: "binaryShortString",
|
||||
value: "z",
|
||||
maxLength: 1
|
||||
}
|
||||
];
|
||||
this.checks = [
|
||||
{
|
||||
|
@ -76,8 +82,8 @@ class FromBase85 extends Operation {
|
|||
*/
|
||||
run(input, args) {
|
||||
const alphabet = Utils.expandAlphRange(args[0]).join(""),
|
||||
encoding = alphabetName(alphabet),
|
||||
removeNonAlphChars = args[1],
|
||||
allZeroGroupChar = typeof args[2] === "string" ? args[2].slice(0, 1) : "",
|
||||
result = [];
|
||||
|
||||
if (alphabet.length !== 85 ||
|
||||
|
@ -85,14 +91,21 @@ class FromBase85 extends Operation {
|
|||
throw new OperationError("Alphabet must be of length 85");
|
||||
}
|
||||
|
||||
if (allZeroGroupChar && alphabet.includes(allZeroGroupChar)) {
|
||||
throw new OperationError("The all-zero group char cannot appear in the alphabet");
|
||||
}
|
||||
|
||||
// Remove delimiters if present
|
||||
const matches = input.match(/^<~(.+?)~>$/);
|
||||
if (matches !== null) input = matches[1];
|
||||
|
||||
// Remove non-alphabet characters
|
||||
if (removeNonAlphChars) {
|
||||
const re = new RegExp("[^" + alphabet.replace(/[[\]\\\-^$]/g, "\\$&") + "]", "g");
|
||||
const re = new RegExp("[^~" + allZeroGroupChar +alphabet.replace(/[[\]\\\-^$]/g, "\\$&") + "]", "g");
|
||||
input = input.replace(re, "");
|
||||
// Remove delimiters again if present (incase of non-alphabet characters in front/behind delimiters)
|
||||
const matches = input.match(/^<~(.+?)~>$/);
|
||||
if (matches !== null) input = matches[1];
|
||||
}
|
||||
|
||||
if (input.length === 0) return [];
|
||||
|
@ -100,7 +113,7 @@ class FromBase85 extends Operation {
|
|||
let i = 0;
|
||||
let block, blockBytes;
|
||||
while (i < input.length) {
|
||||
if (encoding === "Standard" && input[i] === "z") {
|
||||
if (input[i] === allZeroGroupChar) {
|
||||
result.push(0, 0, 0, 0);
|
||||
i++;
|
||||
} else {
|
||||
|
@ -110,7 +123,7 @@ class FromBase85 extends Operation {
|
|||
.split("")
|
||||
.map((chr, idx) => {
|
||||
const digit = alphabet.indexOf(chr);
|
||||
if (digit < 0 || digit > 84) {
|
||||
if ((digit < 0 || digit > 84) && chr !== allZeroGroupChar) {
|
||||
throw `Invalid character '${chr}' at index ${i + idx}`;
|
||||
}
|
||||
return digit;
|
||||
|
|
|
@ -44,7 +44,7 @@ class GenerateQRCode extends Operation {
|
|||
{
|
||||
"name": "Margin (num modules)",
|
||||
"type": "number",
|
||||
"value": 2,
|
||||
"value": 4,
|
||||
"min": 0
|
||||
},
|
||||
{
|
||||
|
|
43
src/core/operations/LZ4Compress.mjs
Normal file
43
src/core/operations/LZ4Compress.mjs
Normal file
|
@ -0,0 +1,43 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2022
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import lz4 from "lz4js";
|
||||
|
||||
/**
|
||||
* LZ4 Compress operation
|
||||
*/
|
||||
class LZ4Compress extends Operation {
|
||||
|
||||
/**
|
||||
* LZ4Compress constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "LZ4 Compress";
|
||||
this.module = "Compression";
|
||||
this.description = "LZ4 is a lossless data compression algorithm that is focused on compression and decompression speed. It belongs to the LZ77 family of byte-oriented compression schemes.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/LZ4_(compression_algorithm)";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "ArrayBuffer";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {Object[]} args
|
||||
* @returns {ArrayBuffer}
|
||||
*/
|
||||
run(input, args) {
|
||||
const inBuf = new Uint8Array(input);
|
||||
const compressed = lz4.compress(inBuf);
|
||||
return compressed.buffer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default LZ4Compress;
|
43
src/core/operations/LZ4Decompress.mjs
Normal file
43
src/core/operations/LZ4Decompress.mjs
Normal file
|
@ -0,0 +1,43 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2022
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import lz4 from "lz4js";
|
||||
|
||||
/**
|
||||
* LZ4 Decompress operation
|
||||
*/
|
||||
class LZ4Decompress extends Operation {
|
||||
|
||||
/**
|
||||
* LZ4Decompress constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "LZ4 Decompress";
|
||||
this.module = "Compression";
|
||||
this.description = "LZ4 is a lossless data compression algorithm that is focused on compression and decompression speed. It belongs to the LZ77 family of byte-oriented compression schemes.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/LZ4_(compression_algorithm)";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "ArrayBuffer";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {Object[]} args
|
||||
* @returns {ArrayBuffer}
|
||||
*/
|
||||
run(input, args) {
|
||||
const inBuf = new Uint8Array(input);
|
||||
const decompressed = lz4.decompress(inBuf);
|
||||
return decompressed.buffer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default LZ4Decompress;
|
|
@ -45,8 +45,8 @@ class ParseASN1HexString extends Operation {
|
|||
*/
|
||||
run(input, args) {
|
||||
const [index, truncateLen] = args;
|
||||
return r.ASN1HEX.dump(input.replace(/\s/g, ""), {
|
||||
"ommitLongOctet": truncateLen
|
||||
return r.ASN1HEX.dump(input.replace(/\s/g, "").toLowerCase(), {
|
||||
"ommit_long_octet": truncateLen
|
||||
}, index);
|
||||
}
|
||||
|
||||
|
|
|
@ -57,23 +57,29 @@ class ParseX509Certificate extends Operation {
|
|||
const cert = new r.X509(),
|
||||
inputFormat = args[0];
|
||||
|
||||
switch (inputFormat) {
|
||||
case "DER Hex":
|
||||
input = input.replace(/\s/g, "");
|
||||
cert.readCertHex(input);
|
||||
break;
|
||||
case "PEM":
|
||||
cert.readCertPEM(input);
|
||||
break;
|
||||
case "Base64":
|
||||
cert.readCertHex(toHex(fromBase64(input, null, "byteArray"), ""));
|
||||
break;
|
||||
case "Raw":
|
||||
cert.readCertHex(toHex(Utils.strToByteArray(input), ""));
|
||||
break;
|
||||
default:
|
||||
throw "Undefined input format";
|
||||
let undefinedInputFormat = false;
|
||||
try {
|
||||
switch (inputFormat) {
|
||||
case "DER Hex":
|
||||
input = input.replace(/\s/g, "").toLowerCase();
|
||||
cert.readCertHex(input);
|
||||
break;
|
||||
case "PEM":
|
||||
cert.readCertPEM(input);
|
||||
break;
|
||||
case "Base64":
|
||||
cert.readCertHex(toHex(fromBase64(input, null, "byteArray"), ""));
|
||||
break;
|
||||
case "Raw":
|
||||
cert.readCertHex(toHex(Utils.strToByteArray(input), ""));
|
||||
break;
|
||||
default:
|
||||
undefinedInputFormat = true;
|
||||
}
|
||||
} catch (e) {
|
||||
throw "Certificate load error (non-certificate input?)";
|
||||
}
|
||||
if (undefinedInputFormat) throw "Undefined input format";
|
||||
|
||||
const sn = cert.getSerialNumberHex(),
|
||||
issuer = cert.getIssuer(),
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
247
src/core/operations/Rabbit.mjs
Normal file
247
src/core/operations/Rabbit.mjs
Normal file
|
@ -0,0 +1,247 @@
|
|||
/**
|
||||
* @author mikecat
|
||||
* @copyright Crown Copyright 2022
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import { toHexFast } from "../lib/Hex.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
/**
|
||||
* Rabbit operation
|
||||
*/
|
||||
class Rabbit extends Operation {
|
||||
|
||||
/**
|
||||
* Rabbit constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Rabbit";
|
||||
this.module = "Ciphers";
|
||||
this.description = "Rabbit is a high-speed stream cipher introduced in 2003 and defined in RFC 4503.<br><br>The cipher uses a 128-bit key and an optional 64-bit initialization vector (IV).<br><br>big-endian: based on RFC4503 and RFC3447<br>little-endian: compatible with Crypto++";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Rabbit_(cipher)";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Key",
|
||||
"type": "toggleString",
|
||||
"value": "",
|
||||
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
},
|
||||
{
|
||||
"name": "IV",
|
||||
"type": "toggleString",
|
||||
"value": "",
|
||||
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
},
|
||||
{
|
||||
"name": "Endianness",
|
||||
"type": "option",
|
||||
"value": ["Big", "Little"]
|
||||
},
|
||||
{
|
||||
"name": "Input",
|
||||
"type": "option",
|
||||
"value": ["Raw", "Hex"]
|
||||
},
|
||||
{
|
||||
"name": "Output",
|
||||
"type": "option",
|
||||
"value": ["Raw", "Hex"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const key = Utils.convertToByteArray(args[0].string, args[0].option),
|
||||
iv = Utils.convertToByteArray(args[1].string, args[1].option),
|
||||
endianness = args[2],
|
||||
inputType = args[3],
|
||||
outputType = args[4];
|
||||
|
||||
const littleEndian = endianness === "Little";
|
||||
|
||||
if (key.length !== 16) {
|
||||
throw new OperationError(`Invalid key length: ${key.length} bytes (expected: 16)`);
|
||||
}
|
||||
if (iv.length !== 0 && iv.length !== 8) {
|
||||
throw new OperationError(`Invalid IV length: ${iv.length} bytes (expected: 0 or 8)`);
|
||||
}
|
||||
|
||||
// Inner State
|
||||
const X = new Uint32Array(8), C = new Uint32Array(8);
|
||||
let b = 0;
|
||||
|
||||
// Counter System
|
||||
const A = [
|
||||
0x4d34d34d, 0xd34d34d3, 0x34d34d34, 0x4d34d34d,
|
||||
0xd34d34d3, 0x34d34d34, 0x4d34d34d, 0xd34d34d3
|
||||
];
|
||||
const counterUpdate = function() {
|
||||
for (let j = 0; j < 8; j++) {
|
||||
const temp = C[j] + A[j] + b;
|
||||
b = (temp / ((1 << 30) * 4)) >>> 0;
|
||||
C[j] = temp;
|
||||
}
|
||||
};
|
||||
|
||||
// Next-State Function
|
||||
const g = function(u, v) {
|
||||
const uv = (u + v) >>> 0;
|
||||
const upper = uv >>> 16, lower = uv & 0xffff;
|
||||
const upperUpper = upper * upper;
|
||||
const upperLower2 = 2 * upper * lower;
|
||||
const lowerLower = lower * lower;
|
||||
const mswTemp = upperUpper + ((upperLower2 / (1 << 16)) >>> 0);
|
||||
const lswTemp = lowerLower + (upperLower2 & 0xffff) * (1 << 16);
|
||||
const msw = mswTemp + ((lswTemp / ((1 << 30) * 4)) >>> 0);
|
||||
const lsw = lswTemp >>> 0;
|
||||
return lsw ^ msw;
|
||||
};
|
||||
const leftRotate = function(value, width) {
|
||||
return (value << width) | (value >>> (32 - width));
|
||||
};
|
||||
const nextStateHelper1 = function(v0, v1, v2) {
|
||||
return v0 + leftRotate(v1, 16) + leftRotate(v2, 16);
|
||||
};
|
||||
const nextStateHelper2 = function(v0, v1, v2) {
|
||||
return v0 + leftRotate(v1, 8) + v2;
|
||||
};
|
||||
const G = new Uint32Array(8);
|
||||
const nextState = function() {
|
||||
for (let j = 0; j < 8; j++) {
|
||||
G[j] = g(X[j], C[j]);
|
||||
}
|
||||
X[0] = nextStateHelper1(G[0], G[7], G[6]);
|
||||
X[1] = nextStateHelper2(G[1], G[0], G[7]);
|
||||
X[2] = nextStateHelper1(G[2], G[1], G[0]);
|
||||
X[3] = nextStateHelper2(G[3], G[2], G[1]);
|
||||
X[4] = nextStateHelper1(G[4], G[3], G[2]);
|
||||
X[5] = nextStateHelper2(G[5], G[4], G[3]);
|
||||
X[6] = nextStateHelper1(G[6], G[5], G[4]);
|
||||
X[7] = nextStateHelper2(G[7], G[6], G[5]);
|
||||
};
|
||||
|
||||
// Key Setup Scheme
|
||||
const K = new Uint16Array(8);
|
||||
if (littleEndian) {
|
||||
for (let i = 0; i < 8; i++) {
|
||||
K[i] = (key[1 + 2 * i] << 8) | key[2 * i];
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < 8; i++) {
|
||||
K[i] = (key[14 - 2 * i] << 8) | key[15 - 2 * i];
|
||||
}
|
||||
}
|
||||
for (let j = 0; j < 8; j++) {
|
||||
if (j % 2 === 0) {
|
||||
X[j] = (K[(j + 1) % 8] << 16) | K[j];
|
||||
C[j] = (K[(j + 4) % 8] << 16) | K[(j + 5) % 8];
|
||||
} else {
|
||||
X[j] = (K[(j + 5) % 8] << 16) | K[(j + 4) % 8];
|
||||
C[j] = (K[j] << 16) | K[(j + 1) % 8];
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < 4; i++) {
|
||||
counterUpdate();
|
||||
nextState();
|
||||
}
|
||||
for (let j = 0; j < 8; j++) {
|
||||
C[j] = C[j] ^ X[(j + 4) % 8];
|
||||
}
|
||||
|
||||
// IV Setup Scheme
|
||||
if (iv.length === 8) {
|
||||
const getIVValue = function(a, b, c, d) {
|
||||
if (littleEndian) {
|
||||
return (iv[a] << 24) | (iv[b] << 16) |
|
||||
(iv[c] << 8) | iv[d];
|
||||
} else {
|
||||
return (iv[7 - a] << 24) | (iv[7 - b] << 16) |
|
||||
(iv[7 - c] << 8) | iv[7 - d];
|
||||
}
|
||||
};
|
||||
C[0] = C[0] ^ getIVValue(3, 2, 1, 0);
|
||||
C[1] = C[1] ^ getIVValue(7, 6, 3, 2);
|
||||
C[2] = C[2] ^ getIVValue(7, 6, 5, 4);
|
||||
C[3] = C[3] ^ getIVValue(5, 4, 1, 0);
|
||||
C[4] = C[4] ^ getIVValue(3, 2, 1, 0);
|
||||
C[5] = C[5] ^ getIVValue(7, 6, 3, 2);
|
||||
C[6] = C[6] ^ getIVValue(7, 6, 5, 4);
|
||||
C[7] = C[7] ^ getIVValue(5, 4, 1, 0);
|
||||
for (let i = 0; i < 4; i++) {
|
||||
counterUpdate();
|
||||
nextState();
|
||||
}
|
||||
}
|
||||
|
||||
// Extraction Scheme
|
||||
const S = new Uint8Array(16);
|
||||
const extract = function() {
|
||||
let pos = 0;
|
||||
const addPart = function(value) {
|
||||
S[pos++] = value >>> 8;
|
||||
S[pos++] = value & 0xff;
|
||||
};
|
||||
counterUpdate();
|
||||
nextState();
|
||||
addPart((X[6] >>> 16) ^ (X[1] & 0xffff));
|
||||
addPart((X[6] & 0xffff) ^ (X[3] >>> 16));
|
||||
addPart((X[4] >>> 16) ^ (X[7] & 0xffff));
|
||||
addPart((X[4] & 0xffff) ^ (X[1] >>> 16));
|
||||
addPart((X[2] >>> 16) ^ (X[5] & 0xffff));
|
||||
addPart((X[2] & 0xffff) ^ (X[7] >>> 16));
|
||||
addPart((X[0] >>> 16) ^ (X[3] & 0xffff));
|
||||
addPart((X[0] & 0xffff) ^ (X[5] >>> 16));
|
||||
if (littleEndian) {
|
||||
for (let i = 0, j = S.length - 1; i < j;) {
|
||||
const temp = S[i];
|
||||
S[i] = S[j];
|
||||
S[j] = temp;
|
||||
i++;
|
||||
j--;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const data = Utils.convertToByteString(input, inputType);
|
||||
const result = new Uint8Array(data.length);
|
||||
for (let i = 0; i <= data.length - 16; i += 16) {
|
||||
extract();
|
||||
for (let j = 0; j < 16; j++) {
|
||||
result[i + j] = data.charCodeAt(i + j) ^ S[j];
|
||||
}
|
||||
}
|
||||
if (data.length % 16 !== 0) {
|
||||
const offset = data.length - data.length % 16;
|
||||
const length = data.length - offset;
|
||||
extract();
|
||||
if (littleEndian) {
|
||||
for (let j = 0; j < length; j++) {
|
||||
result[offset + j] = data.charCodeAt(offset + j) ^ S[j];
|
||||
}
|
||||
} else {
|
||||
for (let j = 0; j < length; j++) {
|
||||
result[offset + j] = data.charCodeAt(offset + j) ^ S[16 - length + j];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (outputType === "Hex") {
|
||||
return toHexFast(result);
|
||||
}
|
||||
return Utils.byteArrayToChars(result);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Rabbit;
|
|
@ -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;
|
|
@ -7,7 +7,7 @@
|
|||
import Operation from "../Operation.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import {INPUT_DELIM_OPTIONS} from "../lib/Delim.mjs";
|
||||
import {caseInsensitiveSort, ipSort, numericSort, hexadecimalSort} from "../lib/Sort.mjs";
|
||||
import {caseInsensitiveSort, ipSort, numericSort, hexadecimalSort, lengthSort} from "../lib/Sort.mjs";
|
||||
|
||||
/**
|
||||
* Sort operation
|
||||
|
@ -39,7 +39,7 @@ class Sort extends Operation {
|
|||
{
|
||||
"name": "Order",
|
||||
"type": "option",
|
||||
"value": ["Alphabetical (case sensitive)", "Alphabetical (case insensitive)", "IP address", "Numeric", "Numeric (hexadecimal)"]
|
||||
"value": ["Alphabetical (case sensitive)", "Alphabetical (case insensitive)", "IP address", "Numeric", "Numeric (hexadecimal)", "Length"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
@ -65,6 +65,8 @@ class Sort extends Operation {
|
|||
sorted = sorted.sort(numericSort);
|
||||
} else if (order === "Numeric (hexadecimal)") {
|
||||
sorted = sorted.sort(hexadecimalSort);
|
||||
} else if (order === "Length") {
|
||||
sorted = sorted.sort(lengthSort);
|
||||
}
|
||||
|
||||
if (sortReverse) sorted.reverse();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -20,7 +20,7 @@ class ToTable extends Operation {
|
|||
|
||||
this.name = "To Table";
|
||||
this.module = "Default";
|
||||
this.description = "Data can be split on different characters and rendered as an HTML or ASCII table with an optional header row.<br><br>Supports the CSV (Comma Separated Values) file format by default. Change the cell delimiter argument to <code>\\t</code> to support TSV (Tab Separated Values) or <code>|</code> for PSV (Pipe Separated Values).<br><br>You can enter as many delimiters as you like. Each character will be treat as a separate possible delimiter.";
|
||||
this.description = "Data can be split on different characters and rendered as an HTML, ASCII or Markdown table with an optional header row.<br><br>Supports the CSV (Comma Separated Values) file format by default. Change the cell delimiter argument to <code>\\t</code> to support TSV (Tab Separated Values) or <code>|</code> for PSV (Pipe Separated Values).<br><br>You can enter as many delimiters as you like. Each character will be treat as a separate possible delimiter.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Comma-separated_values";
|
||||
this.inputType = "string";
|
||||
this.outputType = "html";
|
||||
|
@ -43,7 +43,7 @@ class ToTable extends Operation {
|
|||
{
|
||||
"name": "Format",
|
||||
"type": "option",
|
||||
"value": ["ASCII", "HTML"]
|
||||
"value": ["ASCII", "HTML", "Markdown"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
@ -66,6 +66,9 @@ class ToTable extends Operation {
|
|||
case "ASCII":
|
||||
return asciiOutput(tableData);
|
||||
case "HTML":
|
||||
return htmlOutput(tableData);
|
||||
case "Markdown":
|
||||
return markdownOutput(tableData);
|
||||
default:
|
||||
return htmlOutput(tableData);
|
||||
}
|
||||
|
@ -183,6 +186,59 @@ class ToTable extends Operation {
|
|||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs an array of data as a Markdown table.
|
||||
*
|
||||
* @param {string[][]} tableData
|
||||
* @returns {string}
|
||||
*/
|
||||
function markdownOutput(tableData) {
|
||||
const headerDivider = "-";
|
||||
const verticalBorder = "|";
|
||||
|
||||
let output = "";
|
||||
const longestCells = [];
|
||||
|
||||
// Find longestCells value per column to pad cells equally.
|
||||
tableData.forEach(function(row, index) {
|
||||
row.forEach(function(cell, cellIndex) {
|
||||
if (longestCells[cellIndex] === undefined || cell.length > longestCells[cellIndex]) {
|
||||
longestCells[cellIndex] = cell.length;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Ignoring the checkbox, as current Mardown renderer in CF doesn't handle table without headers
|
||||
const row = tableData.shift();
|
||||
output += outputRow(row, longestCells);
|
||||
let rowOutput = verticalBorder;
|
||||
row.forEach(function(cell, index) {
|
||||
rowOutput += " " + headerDivider + " " + verticalBorder;
|
||||
});
|
||||
output += rowOutput += "\n";
|
||||
|
||||
// Add the rest of the table rows.
|
||||
tableData.forEach(function(row, index) {
|
||||
output += outputRow(row, longestCells);
|
||||
});
|
||||
|
||||
return output;
|
||||
|
||||
/**
|
||||
* Outputs a row of correctly padded cells.
|
||||
*/
|
||||
function outputRow(row, longestCells) {
|
||||
let rowOutput = verticalBorder;
|
||||
row.forEach(function(cell, index) {
|
||||
rowOutput += " " + cell + " ".repeat(longestCells[index] - cell.length) + " " + verticalBorder;
|
||||
});
|
||||
rowOutput += "\n";
|
||||
return rowOutput;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ class TripleDESDecrypt extends Operation {
|
|||
inputType = args[3],
|
||||
outputType = args[4];
|
||||
|
||||
if (key.length !== 24) {
|
||||
if (key.length !== 24 && key.length !== 16) {
|
||||
throw new OperationError(`Invalid key length: ${key.length} bytes
|
||||
|
||||
Triple DES uses a key length of 24 bytes (192 bits).
|
||||
|
@ -85,7 +85,8 @@ Make sure you have specified the type correctly (e.g. Hex vs UTF8).`);
|
|||
|
||||
input = Utils.convertToByteString(input, inputType);
|
||||
|
||||
const decipher = forge.cipher.createDecipher("3DES-" + mode, key);
|
||||
const decipher = forge.cipher.createDecipher("3DES-" + mode,
|
||||
key.length === 16 ? key + key.substring(0, 8) : key);
|
||||
|
||||
/* Allow for a "no padding" mode */
|
||||
if (noPadding) {
|
||||
|
|
|
@ -69,7 +69,7 @@ class TripleDESEncrypt extends Operation {
|
|||
inputType = args[3],
|
||||
outputType = args[4];
|
||||
|
||||
if (key.length !== 24) {
|
||||
if (key.length !== 24 && key.length !== 16) {
|
||||
throw new OperationError(`Invalid key length: ${key.length} bytes
|
||||
|
||||
Triple DES uses a key length of 24 bytes (192 bits).
|
||||
|
@ -84,7 +84,8 @@ Make sure you have specified the type correctly (e.g. Hex vs UTF8).`);
|
|||
|
||||
input = Utils.convertToByteString(input, inputType);
|
||||
|
||||
const cipher = forge.cipher.createCipher("3DES-" + mode, key);
|
||||
const cipher = forge.cipher.createCipher("3DES-" + mode,
|
||||
key.length === 16 ? key + key.substring(0, 8) : key);
|
||||
cipher.start({iv: iv});
|
||||
cipher.update(forge.util.createBuffer(input));
|
||||
cipher.finish();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue