diff --git a/src/core/Ingredient.mjs b/src/core/Ingredient.mjs
index 18823daf..914500f3 100755
--- a/src/core/Ingredient.mjs
+++ b/src/core/Ingredient.mjs
@@ -21,6 +21,7 @@ class Ingredient {
this.name = "";
this.type = "";
this._value = null;
+ this.toggleValues = [];
if (ingredientConfig) {
this._parseConfig(ingredientConfig);
@@ -38,6 +39,7 @@ class Ingredient {
this.name = ingredientConfig.name;
this.type = ingredientConfig.type;
this.defaultValue = ingredientConfig.value;
+ this.toggleValues = ingredientConfig.toggleValues;
}
diff --git a/src/core/Operation.mjs b/src/core/Operation.mjs
index e87c31f4..e0b587c6 100755
--- a/src/core/Operation.mjs
+++ b/src/core/Operation.mjs
@@ -172,7 +172,8 @@ class Operation {
return {
name: ing.name,
type: ing.type,
- value: ing.defaultValue
+ value: ing.defaultValue,
+ toggleValues: ing.toggleValues || []
};
});
}
diff --git a/src/core/lib/Ciphers.mjs b/src/core/lib/Ciphers.mjs
new file mode 100644
index 00000000..b7e98940
--- /dev/null
+++ b/src/core/lib/Ciphers.mjs
@@ -0,0 +1,81 @@
+/**
+ * Cipher functions.
+ *
+ * @author Matt C [matt@artemisbot.uk]
+ * @author n1474335 [n1474335@gmail.com]
+ *
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ *
+ */
+import OperationError from "../errors/OperationError";
+import CryptoJS from "crypto-js";
+
+/**
+ * Affine Cipher Encode operation.
+ *
+ * @author Matt C [matt@artemisbot.uk]
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+export function affineEncode(input, args) {
+ const alphabet = "abcdefghijklmnopqrstuvwxyz",
+ a = args[0],
+ b = args[1];
+ let output = "";
+
+ if (!/^\+?(0|[1-9]\d*)$/.test(a) || !/^\+?(0|[1-9]\d*)$/.test(b)) {
+ throw new OperationError("The values of a and b can only be integers.");
+ }
+
+ for (let i = 0; i < input.length; i++) {
+ if (alphabet.indexOf(input[i]) >= 0) {
+ // Uses the affine function ax+b % m = y (where m is length of the alphabet)
+ output += alphabet[((a * alphabet.indexOf(input[i])) + b) % 26];
+ } else if (alphabet.indexOf(input[i].toLowerCase()) >= 0) {
+ // Same as above, accounting for uppercase
+ output += alphabet[((a * alphabet.indexOf(input[i].toLowerCase())) + b) % 26].toUpperCase();
+ } else {
+ // Non-alphabetic characters
+ output += input[i];
+ }
+ }
+ return output;
+}
+
+/**
+ * Generates a polybius square for the given keyword
+ *
+ * @private
+ * @author Matt C [matt@artemisbot.uk]
+ * @param {string} keyword - Must be upper case
+ * @returns {string}
+ */
+export function genPolybiusSquare (keyword) {
+ const alpha = "ABCDEFGHIKLMNOPQRSTUVWXYZ",
+ polArray = `${keyword}${alpha}`.split("").unique(),
+ polybius = [];
+
+ for (let i = 0; i < 5; i++) {
+ polybius[i] = polArray.slice(i*5, i*5 + 5);
+ }
+
+ return polybius;
+}
+
+/**
+ * A mapping of string formats to their classes in the CryptoJS library.
+ *
+ * @private
+ * @constant
+ */
+export const format = {
+ "Hex": CryptoJS.enc.Hex,
+ "Base64": CryptoJS.enc.Base64,
+ "UTF8": CryptoJS.enc.Utf8,
+ "UTF16": CryptoJS.enc.Utf16,
+ "UTF16LE": CryptoJS.enc.Utf16LE,
+ "UTF16BE": CryptoJS.enc.Utf16BE,
+ "Latin1": CryptoJS.enc.Latin1,
+};
diff --git a/src/core/operations/AffineCipherDecode.mjs b/src/core/operations/AffineCipherDecode.mjs
new file mode 100644
index 00000000..3d418453
--- /dev/null
+++ b/src/core/operations/AffineCipherDecode.mjs
@@ -0,0 +1,104 @@
+/**
+ * @author Matt C [matt@artemisbot.uk]
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import Utils from "../Utils";
+import OperationError from "../errors/OperationError";
+
+/**
+ * Affine Cipher Decode operation
+ */
+class AffineCipherDecode extends Operation {
+
+ /**
+ * AffineCipherDecode constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "Affine Cipher Decode";
+ this.module = "Ciphers";
+ this.description = "The Affine cipher is a type of monoalphabetic substitution cipher. To decrypt, each letter in an alphabet is mapped to its numeric equivalent, decrypted by a mathematical function, and converted back to a letter.";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ {
+ "name": "a",
+ "type": "number",
+ "value": 1
+ },
+ {
+ "name": "b",
+ "type": "number",
+ "value": 0
+ }
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ const alphabet = "abcdefghijklmnopqrstuvwxyz",
+ a = args[0],
+ b = args[1],
+ aModInv = Utils.modInv(a, 26); // Calculates modular inverse of a
+ let output = "";
+
+ if (!/^\+?(0|[1-9]\d*)$/.test(a) || !/^\+?(0|[1-9]\d*)$/.test(b)) {
+ throw new OperationError("The values of a and b can only be integers.");
+ }
+
+ if (Utils.gcd(a, 26) !== 1) {
+ throw new OperationError("The value of `a` must be coprime to 26.");
+ }
+
+ for (let i = 0; i < input.length; i++) {
+ if (alphabet.indexOf(input[i]) >= 0) {
+ // Uses the affine decode function (y-b * A') % m = x (where m is length of the alphabet and A' is modular inverse)
+ output += alphabet[Utils.mod((alphabet.indexOf(input[i]) - b) * aModInv, 26)];
+ } else if (alphabet.indexOf(input[i].toLowerCase()) >= 0) {
+ // Same as above, accounting for uppercase
+ output += alphabet[Utils.mod((alphabet.indexOf(input[i].toLowerCase()) - b) * aModInv, 26)].toUpperCase();
+ } else {
+ // Non-alphabetic characters
+ output += input[i];
+ }
+ }
+ return output;
+ }
+
+ /**
+ * Highlight Affine Cipher Decode
+ *
+ * @param {Object[]} pos
+ * @param {number} pos[].start
+ * @param {number} pos[].end
+ * @param {Object[]} args
+ * @returns {Object[]} pos
+ */
+ highlight(pos, args) {
+ return pos;
+ }
+
+ /**
+ * Highlight Affine Cipher Decode in reverse
+ *
+ * @param {Object[]} pos
+ * @param {number} pos[].start
+ * @param {number} pos[].end
+ * @param {Object[]} args
+ * @returns {Object[]} pos
+ */
+ highlightReverse(pos, args) {
+ return pos;
+ }
+
+}
+
+export default AffineCipherDecode;
diff --git a/src/core/operations/AffineCipherEncode.mjs b/src/core/operations/AffineCipherEncode.mjs
new file mode 100644
index 00000000..939bf23a
--- /dev/null
+++ b/src/core/operations/AffineCipherEncode.mjs
@@ -0,0 +1,76 @@
+/**
+ * @author Matt C [matt@artemisbot.uk]
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import { affineEncode } from "../lib/Ciphers";
+/**
+ * Affine Cipher Encode operation
+ */
+class AffineCipherEncode extends Operation {
+
+ /**
+ * AffineCipherEncode constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "Affine Cipher Encode";
+ this.module = "Ciphers";
+ this.description = "The Affine cipher is a type of monoalphabetic substitution cipher, wherein each letter in an alphabet is mapped to its numeric equivalent, encrypted using simple mathematical function, (ax + b) % 26
, and converted back to a letter.";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ {
+ "name": "a",
+ "type": "number",
+ "value": 1
+ },
+ {
+ "name": "b",
+ "type": "number",
+ "value": 0
+ }
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ return affineEncode(input, args);
+ }
+
+ /**
+ * Highlight Affine Cipher Encode
+ *
+ * @param {Object[]} pos
+ * @param {number} pos[].start
+ * @param {number} pos[].end
+ * @param {Object[]} args
+ * @returns {Object[]} pos
+ */
+ highlight(pos, args) {
+ return pos;
+ }
+
+ /**
+ * Highlight Affine Cipher Encode in reverse
+ *
+ * @param {Object[]} pos
+ * @param {number} pos[].start
+ * @param {number} pos[].end
+ * @param {Object[]} args
+ * @returns {Object[]} pos
+ */
+ highlightReverse(pos, args) {
+ return pos;
+ }
+
+}
+
+export default AffineCipherEncode;
diff --git a/src/core/operations/AtbashCipher.mjs b/src/core/operations/AtbashCipher.mjs
new file mode 100644
index 00000000..9c4657ef
--- /dev/null
+++ b/src/core/operations/AtbashCipher.mjs
@@ -0,0 +1,66 @@
+/**
+ * @author Matt C [matt@artemisbot.uk]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import { affineEncode } from "../lib/Ciphers";
+
+/**
+ * Atbash Cipher operation
+ */
+class AtbashCipher extends Operation {
+
+ /**
+ * AtbashCipher constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "Atbash Cipher";
+ this.module = "Ciphers";
+ this.description = "Atbash is a mono-alphabetic substitution cipher originally used to encode the Hebrew alphabet. It has been modified here for use with the Latin alphabet.";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ return affineEncode(input, [25, 25]);
+ }
+
+ /**
+ * Highlight Atbash Cipher
+ *
+ * @param {Object[]} pos
+ * @param {number} pos[].start
+ * @param {number} pos[].end
+ * @param {Object[]} args
+ * @returns {Object[]} pos
+ */
+ highlight(pos, args) {
+ return pos;
+ }
+
+ /**
+ * Highlight Atbash Cipher in reverse
+ *
+ * @param {Object[]} pos
+ * @param {number} pos[].start
+ * @param {number} pos[].end
+ * @param {Object[]} args
+ * @returns {Object[]} pos
+ */
+ highlightReverse(pos, args) {
+ return pos;
+ }
+
+}
+
+export default AtbashCipher;
diff --git a/src/core/operations/BifidCipherDecode.mjs b/src/core/operations/BifidCipherDecode.mjs
new file mode 100644
index 00000000..a78507e6
--- /dev/null
+++ b/src/core/operations/BifidCipherDecode.mjs
@@ -0,0 +1,121 @@
+/**
+ * @author Matt C [matt@artemisbot.uk]
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import { genPolybiusSquare } from "../lib/Ciphers";
+import OperationError from "../errors/OperationError";
+/**
+ * Bifid Cipher Decode operation
+ */
+class BifidCipherDecode extends Operation {
+
+ /**
+ * BifidCipherDecode constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "Bifid Cipher Decode";
+ this.module = "Ciphers";
+ this.description = "The Bifid cipher is a cipher which uses a Polybius square in conjunction with transposition, which can be fairly difficult to decipher without knowing the alphabet keyword.";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ {
+ "name": "Keyword",
+ "type": "string",
+ "value": ""
+ }
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ const keywordStr = args[0].toUpperCase().replace("J", "I"),
+ keyword = keywordStr.split("").unique(),
+ alpha = "ABCDEFGHIKLMNOPQRSTUVWXYZ",
+ structure = [];
+
+ let output = "",
+ count = 0,
+ trans = "";
+
+ if (!/^[A-Z]+$/.test(keywordStr) && keyword.length > 0)
+ throw new OperationError("The key must consist only of letters in the English alphabet");
+
+ const polybius = genPolybiusSquare(keywordStr);
+
+ input.replace("J", "I").split("").forEach((letter) => {
+ const alpInd = alpha.split("").indexOf(letter.toLocaleUpperCase()) >= 0;
+ let polInd;
+
+ if (alpInd) {
+ for (let i = 0; i < 5; i++) {
+ polInd = polybius[i].indexOf(letter.toLocaleUpperCase());
+ if (polInd >= 0) {
+ trans += `${i}${polInd}`;
+ }
+ }
+
+ if (alpha.split("").indexOf(letter) >= 0) {
+ structure.push(true);
+ } else if (alpInd) {
+ structure.push(false);
+ }
+ } else {
+ structure.push(letter);
+ }
+ });
+
+ structure.forEach(pos => {
+ if (typeof pos === "boolean") {
+ const coords = [trans[count], trans[count+trans.length/2]];
+
+ output += pos ?
+ polybius[coords[0]][coords[1]] :
+ polybius[coords[0]][coords[1]].toLocaleLowerCase();
+ count++;
+ } else {
+ output += pos;
+ }
+ });
+
+ return output;
+ }
+
+ /**
+ * Highlight Bifid Cipher Decode
+ *
+ * @param {Object[]} pos
+ * @param {number} pos[].start
+ * @param {number} pos[].end
+ * @param {Object[]} args
+ * @returns {Object[]} pos
+ */
+ highlight(pos, args) {
+ return pos;
+ }
+
+ /**
+ * Highlight Bifid Cipher Decode in reverse
+ *
+ * @param {Object[]} pos
+ * @param {number} pos[].start
+ * @param {number} pos[].end
+ * @param {Object[]} args
+ * @returns {Object[]} pos
+ */
+ highlightReverse(pos, args) {
+ return pos;
+ }
+
+}
+
+export default BifidCipherDecode;
diff --git a/src/core/operations/BifidCipherEncode.mjs b/src/core/operations/BifidCipherEncode.mjs
new file mode 100644
index 00000000..11050349
--- /dev/null
+++ b/src/core/operations/BifidCipherEncode.mjs
@@ -0,0 +1,127 @@
+/**
+ * @author Matt C [matt@artemisbot.uk]
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import OperationError from "../errors/OperationError";
+import { genPolybiusSquare } from "../lib/Ciphers";
+
+/**
+ * Bifid Cipher Encode operation
+ */
+class BifidCipherEncode extends Operation {
+
+ /**
+ * BifidCipherEncode constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "Bifid Cipher Encode";
+ this.module = "Ciphers";
+ this.description = "The Bifid cipher is a cipher which uses a Polybius square in conjunction with transposition, which can be fairly difficult to decipher without knowing the alphabet keyword.";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ {
+ "name": "Keyword",
+ "type": "string",
+ "value": ""
+ }
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ const keywordStr = args[0].toUpperCase().replace("J", "I"),
+ keyword = keywordStr.split("").unique(),
+ alpha = "ABCDEFGHIKLMNOPQRSTUVWXYZ",
+ xCo = [],
+ yCo = [],
+ structure = [];
+
+ let output = "",
+ count = 0;
+
+
+ if (!/^[A-Z]+$/.test(keywordStr) && keyword.length > 0)
+ throw new OperationError("The key must consist only of letters in the English alphabet");
+
+ const polybius = genPolybiusSquare(keywordStr);
+
+ input.replace("J", "I").split("").forEach(letter => {
+ const alpInd = alpha.split("").indexOf(letter.toLocaleUpperCase()) >= 0;
+ let polInd;
+
+ if (alpInd) {
+ for (let i = 0; i < 5; i++) {
+ polInd = polybius[i].indexOf(letter.toLocaleUpperCase());
+ if (polInd >= 0) {
+ xCo.push(polInd);
+ yCo.push(i);
+ }
+ }
+
+ if (alpha.split("").indexOf(letter) >= 0) {
+ structure.push(true);
+ } else if (alpInd) {
+ structure.push(false);
+ }
+ } else {
+ structure.push(letter);
+ }
+ });
+
+ const trans = `${yCo.join("")}${xCo.join("")}`;
+
+ structure.forEach(pos => {
+ if (typeof pos === "boolean") {
+ const coords = trans.substr(2*count, 2).split("");
+
+ output += pos ?
+ polybius[coords[0]][coords[1]] :
+ polybius[coords[0]][coords[1]].toLocaleLowerCase();
+ count++;
+ } else {
+ output += pos;
+ }
+ });
+
+ return output;
+ }
+
+ /**
+ * Highlight Bifid Cipher Encode
+ *
+ * @param {Object[]} pos
+ * @param {number} pos[].start
+ * @param {number} pos[].end
+ * @param {Object[]} args
+ * @returns {Object[]} pos
+ */
+ highlight(pos, args) {
+ return pos;
+ }
+
+ /**
+ * Highlight Bifid Cipher Encode in reverse
+ *
+ * @param {Object[]} pos
+ * @param {number} pos[].start
+ * @param {number} pos[].end
+ * @param {Object[]} args
+ * @returns {Object[]} pos
+ */
+ highlightReverse(pos, args) {
+ return pos;
+ }
+
+}
+
+export default BifidCipherEncode;
diff --git a/src/core/operations/DeriveEVPKey.mjs b/src/core/operations/DeriveEVPKey.mjs
new file mode 100644
index 00000000..ad17f0b1
--- /dev/null
+++ b/src/core/operations/DeriveEVPKey.mjs
@@ -0,0 +1,144 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import Utils from "../Utils";
+import CryptoJS from "crypto-js";
+
+/**
+ * Derive EVP key operation
+ */
+class DeriveEVPKey extends Operation {
+
+ /**
+ * DeriveEVPKey constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "Derive EVP key";
+ this.module = "Ciphers";
+ this.description = "EVP is a password-based key derivation function (PBKDF) used extensively in OpenSSL. In many applications of cryptography, user security is ultimately dependent on a password, and because a password usually can't be used directly as a cryptographic key, some processing is required.
A salt provides a large set of keys for any given password, and an iteration count increases the cost of producing keys from a password, thereby also increasing the difficulty of attack.
If you leave the salt argument empty, a random salt will be generated.";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ {
+ "name": "Passphrase",
+ "type": "toggleString",
+ "value": "",
+ "toggleValues": ["UTF8", "Latin1", "Hex", "Base64"]
+ },
+ {
+ "name": "Key size",
+ "type": "number",
+ "value": 128
+ },
+ {
+ "name": "Iterations",
+ "type": "number",
+ "value": 1
+ },
+ {
+ "name": "Hashing function",
+ "type": "option",
+ "value": ["SHA1", "SHA256", "SHA384", "SHA512", "MD5"]
+ },
+ {
+ "name": "Salt",
+ "type": "toggleString",
+ "value": "",
+ "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
+ }
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ const passphrase = Utils.convertToByteString(args[0].string, args[0].option),
+ keySize = args[1] / 32,
+ iterations = args[2],
+ hasher = args[3],
+ salt = Utils.convertToByteString(args[4].string, args[4].option),
+ key = CryptoJS.EvpKDF(passphrase, salt, {
+ keySize: keySize,
+ hasher: CryptoJS.algo[hasher],
+ iterations: iterations,
+ });
+
+ return key.toString(CryptoJS.enc.Hex);
+ }
+
+}
+
+export default DeriveEVPKey;
+
+/**
+ * Overwriting the CryptoJS OpenSSL key derivation function so that it is possible to not pass a
+ * salt in.
+
+ * @param {string} password - The password to derive from.
+ * @param {number} keySize - The size in words of the key to generate.
+ * @param {number} ivSize - The size in words of the IV to generate.
+ * @param {WordArray|string} salt (Optional) A 64-bit salt to use. If omitted, a salt will be
+ * generated randomly. If set to false, no salt will be added.
+ *
+ * @returns {CipherParams} A cipher params object with the key, IV, and salt.
+ *
+ * @static
+ *
+ * @example
+ * // Randomly generates a salt
+ * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32);
+ * // Uses the salt 'saltsalt'
+ * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32, 'saltsalt');
+ * // Does not use a salt
+ * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32, false);
+ */
+CryptoJS.kdf.OpenSSL.execute = function (password, keySize, ivSize, salt) {
+ // Generate random salt if no salt specified and not set to false
+ // This line changed from `if (!salt) {` to the following
+ if (salt === undefined || salt === null) {
+ salt = CryptoJS.lib.WordArray.random(64/8);
+ }
+
+ // Derive key and IV
+ const key = CryptoJS.algo.EvpKDF.create({ keySize: keySize + ivSize }).compute(password, salt);
+
+ // Separate key and IV
+ const iv = CryptoJS.lib.WordArray.create(key.words.slice(keySize), ivSize * 4);
+ key.sigBytes = keySize * 4;
+
+ // Return params
+ return CryptoJS.lib.CipherParams.create({ key: key, iv: iv, salt: salt });
+};
+
+
+/**
+ * Override for the CryptoJS Hex encoding parser to remove whitespace before attempting to parse
+ * the hex string.
+ *
+ * @param {string} hexStr
+ * @returns {CryptoJS.lib.WordArray}
+ */
+CryptoJS.enc.Hex.parse = function (hexStr) {
+ // Remove whitespace
+ hexStr = hexStr.replace(/\s/g, "");
+
+ // Shortcut
+ const hexStrLength = hexStr.length;
+
+ // Convert
+ const words = [];
+ for (let i = 0; i < hexStrLength; i += 2) {
+ words[i >>> 3] |= parseInt(hexStr.substr(i, 2), 16) << (24 - (i % 8) * 4);
+ }
+
+ return new CryptoJS.lib.WordArray.init(words, hexStrLength / 2);
+};
diff --git a/src/core/operations/RC4.mjs b/src/core/operations/RC4.mjs
new file mode 100644
index 00000000..28968a0a
--- /dev/null
+++ b/src/core/operations/RC4.mjs
@@ -0,0 +1,88 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import CryptoJS from "crypto-js";
+import { format } from "../lib/Ciphers";
+
+/**
+ * RC4 operation
+ */
+class RC4 extends Operation {
+
+ /**
+ * RC4 constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "RC4";
+ this.module = "Ciphers";
+ this.description = "RC4 (also known as ARC4) is a widely-used stream cipher designed by Ron Rivest. It is used in popular protocols such as SSL and WEP. Although remarkable for its simplicity and speed, the algorithm's history doesn't inspire confidence in its security.";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ {
+ "name": "Passphrase",
+ "type": "toggleString",
+ "value": "",
+ "toggleValues": ["UTF8", "UTF16", "UTF16LE", "UTF16BE", "Latin1", "Hex", "Base64"]
+ },
+ {
+ "name": "Input format",
+ "type": "option",
+ "value": ["Latin1", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "Hex", "Base64"]
+ },
+ {
+ "name": "Output format",
+ "type": "option",
+ "value": ["Latin1", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "Hex", "Base64"]
+ }
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ const message = format[args[1]].parse(input),
+ passphrase = format[args[0].option].parse(args[0].string),
+ encrypted = CryptoJS.RC4.encrypt(message, passphrase);
+
+ return encrypted.ciphertext.toString(format[args[2]]);
+ }
+
+ /**
+ * Highlight RC4
+ *
+ * @param {Object[]} pos
+ * @param {number} pos[].start
+ * @param {number} pos[].end
+ * @param {Object[]} args
+ * @returns {Object[]} pos
+ */
+ highlight(pos, args) {
+ return pos;
+ }
+
+ /**
+ * Highlight RC4 in reverse
+ *
+ * @param {Object[]} pos
+ * @param {number} pos[].start
+ * @param {number} pos[].end
+ * @param {Object[]} args
+ * @returns {Object[]} pos
+ */
+ highlightReverse(pos, args) {
+ return pos;
+ }
+
+}
+
+export default RC4;
diff --git a/src/core/operations/RC4Drop.mjs b/src/core/operations/RC4Drop.mjs
new file mode 100644
index 00000000..05f8aca9
--- /dev/null
+++ b/src/core/operations/RC4Drop.mjs
@@ -0,0 +1,94 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import { format } from "../lib/Ciphers";
+import CryptoJS from "crypto-js";
+
+/**
+ * RC4 Drop operation
+ */
+class RC4Drop extends Operation {
+
+ /**
+ * RC4Drop constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "RC4 Drop";
+ this.module = "Ciphers";
+ this.description = "It was discovered that the first few bytes of the RC4 keystream are strongly non-random and leak information about the key. We can defend against this attack by discarding the initial portion of the keystream. This modified algorithm is traditionally called RC4-drop.";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ {
+ "name": "Passphrase",
+ "type": "toggleString",
+ "value": "",
+ "toggleValues": ["UTF8", "UTF16", "UTF16LE", "UTF16BE", "Latin1", "Hex", "Base64"]
+ },
+ {
+ "name": "Input format",
+ "type": "option",
+ "value": ["Latin1", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "Hex", "Base64"]
+ },
+ {
+ "name": "Output format",
+ "type": "option",
+ "value": ["Latin1", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "Hex", "Base64"]
+ },
+ {
+ "name": "Number of bytes to drop",
+ "type": "number",
+ "value": 768
+ }
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ const message = format[args[1]].parse(input),
+ passphrase = format[args[0].option].parse(args[0].string),
+ drop = args[3],
+ encrypted = CryptoJS.RC4Drop.encrypt(message, passphrase, { drop: drop });
+
+ return encrypted.ciphertext.toString(format[args[2]]);
+ }
+
+ /**
+ * Highlight RC4 Drop
+ *
+ * @param {Object[]} pos
+ * @param {number} pos[].start
+ * @param {number} pos[].end
+ * @param {Object[]} args
+ * @returns {Object[]} pos
+ */
+ highlight(pos, args) {
+ return pos;
+ }
+
+ /**
+ * Highlight RC4 Drop in reverse
+ *
+ * @param {Object[]} pos
+ * @param {number} pos[].start
+ * @param {number} pos[].end
+ * @param {Object[]} args
+ * @returns {Object[]} pos
+ */
+ highlightReverse(pos, args) {
+ return pos;
+ }
+
+}
+
+export default RC4Drop;
diff --git a/src/core/operations/Substitute.mjs b/src/core/operations/Substitute.mjs
new file mode 100644
index 00000000..c2d114a2
--- /dev/null
+++ b/src/core/operations/Substitute.mjs
@@ -0,0 +1,65 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import Utils from "../Utils";
+
+/**
+ * Substitute operation
+ */
+class Substitute extends Operation {
+
+ /**
+ * Substitute constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "Substitute";
+ this.module = "Ciphers";
+ this.description = "A substitution cipher allowing you to specify bytes to replace with other byte values. This can be used to create Caesar ciphers but is more powerful as any byte value can be substituted, not just letters, and the substitution values need not be in order.
Enter the bytes you want to replace in the Plaintext field and the bytes to replace them with in the Ciphertext field.
Non-printable bytes can be specified using string escape notation. For example, a line feed character can be written as either \n
or \x0a
.
Byte ranges can be specified using a hyphen. For example, the sequence 0123456789
can be written as 0-9
.";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ {
+ "name": "Plaintext",
+ "type": "binaryString",
+ "value": "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ },
+ {
+ "name": "Ciphertext",
+ "type": "binaryString",
+ "value": "XYZABCDEFGHIJKLMNOPQRSTUVW"
+ }
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ const plaintext = Utils.expandAlphRange(args[0]).join(""),
+ ciphertext = Utils.expandAlphRange(args[1]).join("");
+ let output = "",
+ index = -1;
+
+ if (plaintext.length !== ciphertext.length) {
+ output = "Warning: Plaintext and Ciphertext lengths differ\n\n";
+ }
+
+ for (let i = 0; i < input.length; i++) {
+ index = plaintext.indexOf(input[i]);
+ output += index > -1 && index < ciphertext.length ? ciphertext[index] : input[i];
+ }
+
+ return output;
+ }
+
+}
+
+export default Substitute;
diff --git a/src/core/operations/VigenèreDecode.mjs b/src/core/operations/VigenèreDecode.mjs
new file mode 100644
index 00000000..b1512a3e
--- /dev/null
+++ b/src/core/operations/VigenèreDecode.mjs
@@ -0,0 +1,101 @@
+/**
+ * @author Matt C [matt@artemisbot.uk]
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import OperationError from "../errors/OperationError";
+/**
+ * Vigenère Decode operation
+ */
+class VigenèreDecode extends Operation {
+
+ /**
+ * VigenèreDecode constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "Vigenère Decode";
+ this.module = "Ciphers";
+ this.description = "The Vigenere cipher is a method of encrypting alphabetic text by using a series of different Caesar ciphers based on the letters of a keyword. It is a simple form of polyalphabetic substitution.";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ {
+ "name": "Key",
+ "type": "string",
+ "value": ""
+ }
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ const alphabet = "abcdefghijklmnopqrstuvwxyz",
+ key = args[0].toLowerCase();
+ let output = "",
+ fail = 0,
+ keyIndex,
+ msgIndex,
+ chr;
+
+ if (!key) throw new OperationError("No key entered");
+ if (!/^[a-zA-Z]+$/.test(key)) throw new OperationError("The key must consist only of letters");
+
+ for (let i = 0; i < input.length; i++) {
+ if (alphabet.indexOf(input[i]) >= 0) {
+ chr = key[(i - fail) % key.length];
+ keyIndex = alphabet.indexOf(chr);
+ msgIndex = alphabet.indexOf(input[i]);
+ // Subtract indexes from each other, add 26 just in case the value is negative,
+ // modulo to remove if neccessary
+ output += alphabet[(msgIndex - keyIndex + alphabet.length) % 26];
+ } else if (alphabet.indexOf(input[i].toLowerCase()) >= 0) {
+ chr = key[(i - fail) % key.length].toLowerCase();
+ keyIndex = alphabet.indexOf(chr);
+ msgIndex = alphabet.indexOf(input[i].toLowerCase());
+ output += alphabet[(msgIndex + alphabet.length - keyIndex) % 26].toUpperCase();
+ } else {
+ output += input[i];
+ fail++;
+ }
+ }
+
+ return output;
+ }
+
+ /**
+ * Highlight Vigenère Decode
+ *
+ * @param {Object[]} pos
+ * @param {number} pos[].start
+ * @param {number} pos[].end
+ * @param {Object[]} args
+ * @returns {Object[]} pos
+ */
+ highlight(pos, args) {
+ return pos;
+ }
+
+ /**
+ * Highlight Vigenère Decode in reverse
+ *
+ * @param {Object[]} pos
+ * @param {number} pos[].start
+ * @param {number} pos[].end
+ * @param {Object[]} args
+ * @returns {Object[]} pos
+ */
+ highlightReverse(pos, args) {
+ return pos;
+ }
+
+}
+
+export default VigenèreDecode;
diff --git a/src/core/operations/VigenèreEncode.mjs b/src/core/operations/VigenèreEncode.mjs
new file mode 100644
index 00000000..9172ed06
--- /dev/null
+++ b/src/core/operations/VigenèreEncode.mjs
@@ -0,0 +1,106 @@
+/**
+ * @author Matt C [matt@artemisbot.uk]
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import OperationError from "../errors/OperationError";
+
+/**
+ * Vigenère Encode operation
+ */
+class VigenèreEncode extends Operation {
+
+ /**
+ * VigenèreEncode constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "Vigenère Encode";
+ this.module = "Ciphers";
+ this.description = "The Vigenere cipher is a method of encrypting alphabetic text by using a series of different Caesar ciphers based on the letters of a keyword. It is a simple form of polyalphabetic substitution.";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ {
+ "name": "Key",
+ "type": "string",
+ "value": ""
+ }
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ const alphabet = "abcdefghijklmnopqrstuvwxyz",
+ key = args[0].toLowerCase();
+ let output = "",
+ fail = 0,
+ keyIndex,
+ msgIndex,
+ chr;
+
+ if (!key) throw new OperationError("No key entered");
+ if (!/^[a-zA-Z]+$/.test(key)) throw new OperationError("The key must consist only of letters");
+
+ for (let i = 0; i < input.length; i++) {
+ if (alphabet.indexOf(input[i]) >= 0) {
+ // Get the corresponding character of key for the current letter, accounting
+ // for chars not in alphabet
+ chr = key[(i - fail) % key.length];
+ // Get the location in the vigenere square of the key char
+ keyIndex = alphabet.indexOf(chr);
+ // Get the location in the vigenere square of the message char
+ msgIndex = alphabet.indexOf(input[i]);
+ // Get the encoded letter by finding the sum of indexes modulo 26 and finding
+ // the letter corresponding to that
+ output += alphabet[(keyIndex + msgIndex) % 26];
+ } else if (alphabet.indexOf(input[i].toLowerCase()) >= 0) {
+ chr = key[(i - fail) % key.length].toLowerCase();
+ keyIndex = alphabet.indexOf(chr);
+ msgIndex = alphabet.indexOf(input[i].toLowerCase());
+ output += alphabet[(keyIndex + msgIndex) % 26].toUpperCase();
+ } else {
+ output += input[i];
+ fail++;
+ }
+ }
+
+ return output;
+ }
+
+ /**
+ * Highlight Vigenère Encode
+ *
+ * @param {Object[]} pos
+ * @param {number} pos[].start
+ * @param {number} pos[].end
+ * @param {Object[]} args
+ * @returns {Object[]} pos
+ */
+ highlight(pos, args) {
+ return pos;
+ }
+
+ /**
+ * Highlight Vigenère Encode in reverse
+ *
+ * @param {Object[]} pos
+ * @param {number} pos[].start
+ * @param {number} pos[].end
+ * @param {Object[]} args
+ * @returns {Object[]} pos
+ */
+ highlightReverse(pos, args) {
+ return pos;
+ }
+
+}
+
+export default VigenèreEncode;
diff --git a/src/core/operations/legacy/Cipher.js b/src/core/operations/legacy/Cipher.js
index e350c17a..197560c1 100755
--- a/src/core/operations/legacy/Cipher.js
+++ b/src/core/operations/legacy/Cipher.js
@@ -444,80 +444,6 @@ DES uses a key length of 8 bytes (64 bits).`;
},
- /**
- * Derive EVP key operation.
- *
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runEvpkdf: function (input, args) {
- const passphrase = Utils.convertToByteString(args[0].string, args[0].option),
- keySize = args[1] / 32,
- iterations = args[2],
- hasher = args[3],
- salt = Utils.convertToByteString(args[4].string, args[4].option),
- key = CryptoJS.EvpKDF(passphrase, salt, {
- keySize: keySize,
- hasher: CryptoJS.algo[hasher],
- iterations: iterations,
- });
-
- return key.toString(CryptoJS.enc.Hex);
- },
-
-
- /**
- * @constant
- * @default
- */
- RC4_KEY_FORMAT: ["UTF8", "UTF16", "UTF16LE", "UTF16BE", "Latin1", "Hex", "Base64"],
- /**
- * @constant
- * @default
- */
- CJS_IO_FORMAT: ["Latin1", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "Hex", "Base64"],
-
-
- /**
- * RC4 operation.
- *
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runRc4: function (input, args) {
- let message = Cipher._format[args[1]].parse(input),
- passphrase = Cipher._format[args[0].option].parse(args[0].string),
- encrypted = CryptoJS.RC4.encrypt(message, passphrase);
-
- return encrypted.ciphertext.toString(Cipher._format[args[2]]);
- },
-
-
- /**
- * @constant
- * @default
- */
- RC4DROP_BYTES: 768,
-
- /**
- * RC4 Drop operation.
- *
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runRc4drop: function (input, args) {
- let message = Cipher._format[args[1]].parse(input),
- passphrase = Cipher._format[args[0].option].parse(args[0].string),
- drop = args[3],
- encrypted = CryptoJS.RC4Drop.encrypt(message, passphrase, { drop: drop });
-
- return encrypted.ciphertext.toString(Cipher._format[args[2]]);
- },
-
-
/**
* @constant
* @default
@@ -568,468 +494,6 @@ DES uses a key length of 8 bytes (64 bits).`;
}
},
-
- /**
- * Vigenère Encode operation.
- *
- * @author Matt C [matt@artemisbot.uk]
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runVigenereEnc: function (input, args) {
- let alphabet = "abcdefghijklmnopqrstuvwxyz",
- key = args[0].toLowerCase(),
- output = "",
- fail = 0,
- keyIndex,
- msgIndex,
- chr;
-
- if (!key) return "No key entered";
- if (!/^[a-zA-Z]+$/.test(key)) return "The key must consist only of letters";
-
- for (let i = 0; i < input.length; i++) {
- if (alphabet.indexOf(input[i]) >= 0) {
- // Get the corresponding character of key for the current letter, accounting
- // for chars not in alphabet
- chr = key[(i - fail) % key.length];
- // Get the location in the vigenere square of the key char
- keyIndex = alphabet.indexOf(chr);
- // Get the location in the vigenere square of the message char
- msgIndex = alphabet.indexOf(input[i]);
- // Get the encoded letter by finding the sum of indexes modulo 26 and finding
- // the letter corresponding to that
- output += alphabet[(keyIndex + msgIndex) % 26];
- } else if (alphabet.indexOf(input[i].toLowerCase()) >= 0) {
- chr = key[(i - fail) % key.length].toLowerCase();
- keyIndex = alphabet.indexOf(chr);
- msgIndex = alphabet.indexOf(input[i].toLowerCase());
- output += alphabet[(keyIndex + msgIndex) % 26].toUpperCase();
- } else {
- output += input[i];
- fail++;
- }
- }
-
- return output;
- },
-
-
- /**
- * Vigenère Decode operation.
- *
- * @author Matt C [matt@artemisbot.uk]
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runVigenereDec: function (input, args) {
- let alphabet = "abcdefghijklmnopqrstuvwxyz",
- key = args[0].toLowerCase(),
- output = "",
- fail = 0,
- keyIndex,
- msgIndex,
- chr;
-
- if (!key) return "No key entered";
- if (!/^[a-zA-Z]+$/.test(key)) return "The key must consist only of letters";
-
- for (let i = 0; i < input.length; i++) {
- if (alphabet.indexOf(input[i]) >= 0) {
- chr = key[(i - fail) % key.length];
- keyIndex = alphabet.indexOf(chr);
- msgIndex = alphabet.indexOf(input[i]);
- // Subtract indexes from each other, add 26 just in case the value is negative,
- // modulo to remove if neccessary
- output += alphabet[(msgIndex - keyIndex + alphabet.length) % 26];
- } else if (alphabet.indexOf(input[i].toLowerCase()) >= 0) {
- chr = key[(i - fail) % key.length].toLowerCase();
- keyIndex = alphabet.indexOf(chr);
- msgIndex = alphabet.indexOf(input[i].toLowerCase());
- output += alphabet[(msgIndex + alphabet.length - keyIndex) % 26].toUpperCase();
- } else {
- output += input[i];
- fail++;
- }
- }
-
- return output;
- },
-
-
- /**
- * @constant
- * @default
- */
- AFFINE_A: 1,
- /**
- * @constant
- * @default
- */
- AFFINE_B: 0,
-
- /**
- * Affine Cipher Encode operation.
- *
- * @author Matt C [matt@artemisbot.uk]
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runAffineEnc: function (input, args) {
- let alphabet = "abcdefghijklmnopqrstuvwxyz",
- a = args[0],
- b = args[1],
- output = "";
-
- if (!/^\+?(0|[1-9]\d*)$/.test(a) || !/^\+?(0|[1-9]\d*)$/.test(b)) {
- return "The values of a and b can only be integers.";
- }
-
- for (let i = 0; i < input.length; i++) {
- if (alphabet.indexOf(input[i]) >= 0) {
- // Uses the affine function ax+b % m = y (where m is length of the alphabet)
- output += alphabet[((a * alphabet.indexOf(input[i])) + b) % 26];
- } else if (alphabet.indexOf(input[i].toLowerCase()) >= 0) {
- // Same as above, accounting for uppercase
- output += alphabet[((a * alphabet.indexOf(input[i].toLowerCase())) + b) % 26].toUpperCase();
- } else {
- // Non-alphabetic characters
- output += input[i];
- }
- }
- return output;
- },
-
-
- /**
- * Affine Cipher Decode operation.
- *
- * @author Matt C [matt@artemisbot.uk]
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runAffineDec: function (input, args) {
- let alphabet = "abcdefghijklmnopqrstuvwxyz",
- a = args[0],
- b = args[1],
- output = "",
- aModInv;
-
- if (!/^\+?(0|[1-9]\d*)$/.test(a) || !/^\+?(0|[1-9]\d*)$/.test(b)) {
- return "The values of a and b can only be integers.";
- }
-
- if (Utils.gcd(a, 26) !== 1) {
- return "The value of a must be coprime to 26.";
- }
-
- // Calculates modular inverse of a
- aModInv = Utils.modInv(a, 26);
-
- for (let i = 0; i < input.length; i++) {
- if (alphabet.indexOf(input[i]) >= 0) {
- // Uses the affine decode function (y-b * A') % m = x (where m is length of the alphabet and A' is modular inverse)
- output += alphabet[Utils.mod((alphabet.indexOf(input[i]) - b) * aModInv, 26)];
- } else if (alphabet.indexOf(input[i].toLowerCase()) >= 0) {
- // Same as above, accounting for uppercase
- output += alphabet[Utils.mod((alphabet.indexOf(input[i].toLowerCase()) - b) * aModInv, 26)].toUpperCase();
- } else {
- // Non-alphabetic characters
- output += input[i];
- }
- }
- return output;
- },
-
-
- /**
- * Atbash Cipher Encode operation.
- *
- * @author Matt C [matt@artemisbot.uk]
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runAtbash: function (input, args) {
- return Cipher.runAffineEnc(input, [25, 25]);
- },
-
-
- /**
- * Generates a polybius square for the given keyword
- *
- * @private
- * @author Matt C [matt@artemisbot.uk]
- * @param {string} keyword - Must be upper case
- * @returns {string}
- */
- _genPolybiusSquare: function (keyword) {
- const alpha = "ABCDEFGHIKLMNOPQRSTUVWXYZ";
- const polArray = `${keyword}${alpha}`.split("").unique();
- let polybius = [];
-
- for (let i = 0; i < 5; i++) {
- polybius[i] = polArray.slice(i*5, i*5 + 5);
- }
-
- return polybius;
- },
-
- /**
- * Bifid Cipher Encode operation
- *
- * @author Matt C [matt@artemisbot.uk]
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runBifidEnc: function (input, args) {
- const keywordStr = args[0].toUpperCase().replace("J", "I"),
- keyword = keywordStr.split("").unique(),
- alpha = "ABCDEFGHIKLMNOPQRSTUVWXYZ";
-
- let output = "",
- xCo = [],
- yCo = [],
- structure = [],
- count = 0;
-
- if (keyword.length > 25)
- return "The alphabet keyword must be less than 25 characters.";
-
- if (!/^[a-zA-Z]+$/.test(keywordStr) && keyword.length > 0)
- return "The key must consist only of letters";
-
- const polybius = Cipher._genPolybiusSquare(keywordStr);
-
- input.replace("J", "I").split("").forEach(letter => {
- let alpInd = alpha.split("").indexOf(letter.toLocaleUpperCase()) >= 0,
- polInd;
-
- if (alpInd) {
- for (let i = 0; i < 5; i++) {
- polInd = polybius[i].indexOf(letter.toLocaleUpperCase());
- if (polInd >= 0) {
- xCo.push(polInd);
- yCo.push(i);
- }
- }
-
- if (alpha.split("").indexOf(letter) >= 0) {
- structure.push(true);
- } else if (alpInd) {
- structure.push(false);
- }
- } else {
- structure.push(letter);
- }
- });
-
- const trans = `${yCo.join("")}${xCo.join("")}`;
-
- structure.forEach(pos => {
- if (typeof pos === "boolean") {
- let coords = trans.substr(2*count, 2).split("");
-
- output += pos ?
- polybius[coords[0]][coords[1]] :
- polybius[coords[0]][coords[1]].toLocaleLowerCase();
- count++;
- } else {
- output += pos;
- }
- });
-
- return output;
- },
-
- /**
- * Bifid Cipher Decode operation
- *
- * @author Matt C [matt@artemisbot.uk]
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runBifidDec: function (input, args) {
- const keywordStr = args[0].toUpperCase().replace("J", "I"),
- keyword = keywordStr.split("").unique(),
- alpha = "ABCDEFGHIKLMNOPQRSTUVWXYZ";
-
- let output = "",
- structure = [],
- count = 0,
- trans = "";
-
- if (keyword.length > 25)
- return "The alphabet keyword must be less than 25 characters.";
-
- if (!/^[a-zA-Z]+$/.test(keywordStr) && keyword.length > 0)
- return "The key must consist only of letters";
-
- const polybius = Cipher._genPolybiusSquare(keywordStr);
-
- input.replace("J", "I").split("").forEach((letter) => {
- let alpInd = alpha.split("").indexOf(letter.toLocaleUpperCase()) >= 0,
- polInd;
-
- if (alpInd) {
- for (let i = 0; i < 5; i++) {
- polInd = polybius[i].indexOf(letter.toLocaleUpperCase());
- if (polInd >= 0) {
- trans += `${i}${polInd}`;
- }
- }
-
- if (alpha.split("").indexOf(letter) >= 0) {
- structure.push(true);
- } else if (alpInd) {
- structure.push(false);
- }
- } else {
- structure.push(letter);
- }
- });
-
- structure.forEach(pos => {
- if (typeof pos === "boolean") {
- let coords = [trans[count], trans[count+trans.length/2]];
-
- output += pos ?
- polybius[coords[0]][coords[1]] :
- polybius[coords[0]][coords[1]].toLocaleLowerCase();
- count++;
- } else {
- output += pos;
- }
- });
-
- return output;
- },
-
-
- /**
- * @constant
- * @default
- */
- SUBS_PLAINTEXT: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
- /**
- * @constant
- * @default
- */
- SUBS_CIPHERTEXT: "XYZABCDEFGHIJKLMNOPQRSTUVW",
-
- /**
- * Substitute operation.
- *
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runSubstitute: function (input, args) {
- let plaintext = Utils.expandAlphRange(args[0]).join(""),
- ciphertext = Utils.expandAlphRange(args[1]).join(""),
- output = "",
- index = -1;
-
- if (plaintext.length !== ciphertext.length) {
- output = "Warning: Plaintext and Ciphertext lengths differ\n\n";
- }
-
- for (let i = 0; i < input.length; i++) {
- index = plaintext.indexOf(input[i]);
- output += index > -1 && index < ciphertext.length ? ciphertext[index] : input[i];
- }
-
- return output;
- },
-
-
- /**
- * A mapping of string formats to their classes in the CryptoJS library.
- *
- * @private
- * @constant
- */
- _format: {
- "Hex": CryptoJS.enc.Hex,
- "Base64": CryptoJS.enc.Base64,
- "UTF8": CryptoJS.enc.Utf8,
- "UTF16": CryptoJS.enc.Utf16,
- "UTF16LE": CryptoJS.enc.Utf16LE,
- "UTF16BE": CryptoJS.enc.Utf16BE,
- "Latin1": CryptoJS.enc.Latin1,
- },
-
};
export default Cipher;
-
-
-/**
- * Overwriting the CryptoJS OpenSSL key derivation function so that it is possible to not pass a
- * salt in.
-
- * @param {string} password - The password to derive from.
- * @param {number} keySize - The size in words of the key to generate.
- * @param {number} ivSize - The size in words of the IV to generate.
- * @param {WordArray|string} salt (Optional) A 64-bit salt to use. If omitted, a salt will be
- * generated randomly. If set to false, no salt will be added.
- *
- * @returns {CipherParams} A cipher params object with the key, IV, and salt.
- *
- * @static
- *
- * @example
- * // Randomly generates a salt
- * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32);
- * // Uses the salt 'saltsalt'
- * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32, 'saltsalt');
- * // Does not use a salt
- * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32, false);
- */
-CryptoJS.kdf.OpenSSL.execute = function (password, keySize, ivSize, salt) {
- // Generate random salt if no salt specified and not set to false
- // This line changed from `if (!salt) {` to the following
- if (salt === undefined || salt === null) {
- salt = CryptoJS.lib.WordArray.random(64/8);
- }
-
- // Derive key and IV
- const key = CryptoJS.algo.EvpKDF.create({ keySize: keySize + ivSize }).compute(password, salt);
-
- // Separate key and IV
- const iv = CryptoJS.lib.WordArray.create(key.words.slice(keySize), ivSize * 4);
- key.sigBytes = keySize * 4;
-
- // Return params
- return CryptoJS.lib.CipherParams.create({ key: key, iv: iv, salt: salt });
-};
-
-
-/**
- * Override for the CryptoJS Hex encoding parser to remove whitespace before attempting to parse
- * the hex string.
- *
- * @param {string} hexStr
- * @returns {CryptoJS.lib.WordArray}
- */
-CryptoJS.enc.Hex.parse = function (hexStr) {
- // Remove whitespace
- hexStr = hexStr.replace(/\s/g, "");
-
- // Shortcut
- const hexStrLength = hexStr.length;
-
- // Convert
- const words = [];
- for (let i = 0; i < hexStrLength; i += 2) {
- words[i >>> 3] |= parseInt(hexStr.substr(i, 2), 16) << (24 - (i % 8) * 4);
- }
-
- return new CryptoJS.lib.WordArray.init(words, hexStrLength / 2);
-};
diff --git a/src/core/operations/legacy/PGP.js b/src/core/operations/legacy/PGP.js
old mode 100755
new mode 100644
diff --git a/src/core/operations/legacy/ToTable.js b/src/core/operations/legacy/ToTable.js
old mode 100755
new mode 100644
diff --git a/test/index.mjs b/test/index.mjs
index 042a2d22..7a814b09 100644
--- a/test/index.mjs
+++ b/test/index.mjs
@@ -30,9 +30,10 @@ import "./tests/operations/Base64";
// import "./tests/operations/BitwiseOp.js";
// import "./tests/operations/BSON.js";
// import "./tests/operations/ByteRepr.js";
+import "./tests/operations/CartesianProduct";
// import "./tests/operations/CharEnc.js";
+import "./tests/operations/Ciphers";
//import "./tests/operations/Checksum.js";
-// import "./tests/operations/Cipher.js";
// import "./tests/operations/Code.js";
// import "./tests/operations/Compress.js";
// import "./tests/operations/DateTime.js";
@@ -45,16 +46,15 @@ import "./tests/operations/Base64";
// import "./tests/operations/PHP.js";
// import "./tests/operations/NetBIOS.js";
// import "./tests/operations/OTP.js";
+import "./tests/operations/PowerSet";
// import "./tests/operations/Regex.js";
import "./tests/operations/Rotate";
// import "./tests/operations/StrUtils.js";
// import "./tests/operations/SeqUtils.js";
-import "./tests/operations/SetUnion";
-import "./tests/operations/SetIntersection";
import "./tests/operations/SetDifference";
+import "./tests/operations/SetIntersection";
+import "./tests/operations/SetUnion";
import "./tests/operations/SymmetricDifference";
-import "./tests/operations/CartesianProduct";
-import "./tests/operations/PowerSet";
let allTestsPassing = true;
const testStatusCounts = {
diff --git a/test/tests/operations/Ciphers.mjs b/test/tests/operations/Ciphers.mjs
new file mode 100644
index 00000000..553216ef
--- /dev/null
+++ b/test/tests/operations/Ciphers.mjs
@@ -0,0 +1,355 @@
+/**
+ * Cipher tests.
+ *
+ * @author Matt C [matt@artemisbot.uk]
+ * @author n1474335 [n1474335@gmail.com]
+ *
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+import TestRegister from "../../TestRegister";
+
+
+TestRegister.addTests([
+ {
+ name: "Affine Encode: no input",
+ input: "",
+ expectedOutput: "",
+ recipeConfig: [
+ {
+ op: "Affine Cipher Encode",
+ args: [1, 0]
+ }
+ ],
+ },
+ {
+ name: "Affine Encode: invalid a & b (non-integer)",
+ input: "some keys are shaped as locks. index[me]",
+ expectedOutput: "The values of a and b can only be integers.",
+ recipeConfig: [
+ {
+ op: "Affine Cipher Encode",
+ args: [0.1, 0.00001]
+ }
+ ],
+ },
+ {
+ name: "Affine Encode: no effect",
+ input: "some keys are shaped as locks. index[me]",
+ expectedOutput: "some keys are shaped as locks. index[me]",
+ recipeConfig: [
+ {
+ op: "Affine Cipher Encode",
+ args: [1, 0]
+ }
+ ],
+ },
+ {
+ name: "Affine Encode: normal",
+ input: "some keys are shaped as locks. index[me]",
+ expectedOutput: "vhnl tldv xyl vcxelo xv qhrtv. zkolg[nl]",
+ recipeConfig: [
+ {
+ op: "Affine Cipher Encode",
+ args: [23, 23]
+ }
+ ],
+ },
+ {
+ name: "Affine Decode: no input",
+ input: "",
+ expectedOutput: "",
+ recipeConfig: [
+ {
+ op: "Affine Cipher Decode",
+ args: [1, 0]
+ }
+ ],
+ },
+ {
+ name: "Affine Decode: invalid a & b (non-integer)",
+ input: "vhnl tldv xyl vcxelo xv qhrtv. zkolg[nl]",
+ expectedOutput: "The values of a and b can only be integers.",
+ recipeConfig: [
+ {
+ op: "Affine Cipher Decode",
+ args: [0.1, 0.00001]
+ }
+ ],
+ },
+ {
+ name: "Affine Decode: invalid a (coprime)",
+ input: "vhnl tldv xyl vcxelo xv qhrtv. zkolg[nl]",
+ expectedOutput: "The value of `a` must be coprime to 26.",
+ recipeConfig: [
+ {
+ op: "Affine Cipher Decode",
+ args: [8, 23]
+ }
+ ],
+ },
+ {
+ name: "Affine Decode: no effect",
+ input: "vhnl tldv xyl vcxelo xv qhrtv. zkolg[nl]",
+ expectedOutput: "vhnl tldv xyl vcxelo xv qhrtv. zkolg[nl]",
+ recipeConfig: [
+ {
+ op: "Affine Cipher Decode",
+ args: [1, 0]
+ }
+ ],
+ },
+ {
+ name: "Affine Decode: normal",
+ input: "vhnl tldv xyl vcxelo xv qhrtv. zkolg[nl]",
+ expectedOutput: "some keys are shaped as locks. index[me]",
+ recipeConfig: [
+ {
+ op: "Affine Cipher Decode",
+ args: [23, 23]
+ }
+ ],
+ },
+ {
+ name: "Atbash: no input",
+ input: "",
+ expectedOutput: "",
+ recipeConfig: [
+ {
+ op: "Atbash Cipher",
+ args: []
+ }
+ ],
+ },
+ {
+ name: "Atbash: normal",
+ input: "old slow slim horn",
+ expectedOutput: "low hold horn slim",
+ recipeConfig: [
+ {
+ op: "Atbash Cipher",
+ args: []
+ }
+ ],
+ },
+ {
+ name: "Bifid Cipher Encode: no input",
+ input: "",
+ expectedOutput: "",
+ recipeConfig: [
+ {
+ "op": "Bifid Cipher Encode",
+ "args": ["nothing"]
+ }
+ ],
+ },
+ {
+ name: "Bifid Cipher Encode: no key",
+ input: "We recreate conditions similar to the Van-Allen radiation belt in our secure facilities.",
+ expectedOutput: "Vq daqcliho rmltofvlnc qbdhlcr nt qdq Fbm-Rdkkm vuoottnoi aitp al axf tdtmvt owppkaodtx.",
+ recipeConfig: [
+ {
+ "op": "Bifid Cipher Encode",
+ "args": [""]
+ }
+ ],
+ },
+ {
+ name: "Bifid Cipher Encode: invalid key (non-alphabetic)",
+ input: "We recreate conditions similar to the Van-Allen radiation belt in our secure facilities.",
+ expectedOutput: "The key must consist only of letters in the English alphabet",
+ recipeConfig: [
+ {
+ "op": "Bifid Cipher Encode",
+ "args": ["abc123"]
+ }
+ ],
+ },
+ {
+ name: "Bifid Cipher Encode: normal",
+ input: "We recreate conditions similar to the Van-Allen radiation belt in our secure facilities.",
+ expectedOutput: "Wc snpsigdd cpfrrcxnfi hikdnnp dm crc Fcb-Pdeug vueageacc vtyl sa zxm crebzp lyoeuaiwpv.",
+ recipeConfig: [
+ {
+ "op": "Bifid Cipher Encode",
+ "args": ["Schrodinger"]
+ }
+ ],
+ },
+ {
+ name: "Bifid Cipher Decode: no input",
+ input: "",
+ expectedOutput: "",
+ recipeConfig: [
+ {
+ "op": "Bifid Cipher Decode",
+ "args": ["nothing"]
+ }
+ ],
+ },
+ {
+ name: "Bifid Cipher Decode: no key",
+ input: "Vq daqcliho rmltofvlnc qbdhlcr nt qdq Fbm-Rdkkm vuoottnoi aitp al axf tdtmvt owppkaodtx.",
+ expectedOutput: "We recreate conditions similar to the Van-Allen radiation belt in our secure facilities.",
+ recipeConfig: [
+ {
+ "op": "Bifid Cipher Decode",
+ "args": [""]
+ }
+ ],
+ },
+ {
+ name: "Bifid Cipher Decode: invalid key (non-alphabetic)",
+ input: "Vq daqcliho rmltofvlnc qbdhlcr nt qdq Fbm-Rdkkm vuoottnoi aitp al axf tdtmvt owppkaodtx.",
+ expectedOutput: "The key must consist only of letters in the English alphabet",
+ recipeConfig: [
+ {
+ "op": "Bifid Cipher Decode",
+ "args": ["abc123"]
+ }
+ ],
+ },
+ {
+ name: "Bifid Cipher Decode: normal",
+ input: "Wc snpsigdd cpfrrcxnfi hikdnnp dm crc Fcb-Pdeug vueageacc vtyl sa zxm crebzp lyoeuaiwpv.",
+ expectedOutput: "We recreate conditions similar to the Van-Allen radiation belt in our secure facilities.",
+ recipeConfig: [
+ {
+ "op": "Bifid Cipher Decode",
+ "args": ["Schrodinger"]
+ }
+ ],
+ },
+ {
+ name: "Vigenère Encode: no input",
+ input: "",
+ expectedOutput: "",
+ recipeConfig: [
+ {
+ "op": "Vigenère Encode",
+ "args": ["nothing"]
+ }
+ ],
+ },
+ {
+ name: "Vigenère Encode: no key",
+ input: "LUGGAGEBASEMENTVARENNESALLIESCANBECLOTHEDASENEMIESENEMIESCANBECLOTHEDASALLIESALWAYSUSEID",
+ expectedOutput: "No key entered",
+ recipeConfig: [
+ {
+ "op": "Vigenère Encode",
+ "args": [""]
+ }
+ ],
+ },
+ {
+ name: "Vigenère Encode: invalid key",
+ input: "LUGGAGEBASEMENTVARENNESALLIESCANBECLOTHEDASENEMIESENEMIESCANBECLOTHEDASALLIESALWAYSUSEID",
+ expectedOutput: "The key must consist only of letters",
+ recipeConfig: [
+ {
+ "op": "Vigenère Encode",
+ "args": ["abc123"]
+ }
+ ],
+ },
+ {
+ name: "Vigenère Encode: normal",
+ input: "LUGGAGEBASEMENTVARENNESALLIESCANBECLOTHEDASENEMIESENEMIESCANBECLOTHEDASALLIESALWAYSUSEID",
+ expectedOutput: "PXCGRJIEWSVPIQPVRUIQJEJDPOEEJFEQXETOSWDEUDWHJEDLIVANVPMHOCRQFHYLFWLHZAJDPOEEJDPZWYJXWHED",
+ recipeConfig: [
+ {
+ "op": "Vigenère Encode",
+ "args": ["Edward"]
+ }
+ ],
+ },
+ {
+ name: "Vigenère Decode: no input",
+ input: "",
+ expectedOutput: "",
+ recipeConfig: [
+ {
+ "op": "Vigenère Decode",
+ "args": ["nothing"]
+ }
+ ],
+ },
+ {
+ name: "Vigenère Decode: no key",
+ input: "PXCGRJIEWSVPIQPVRUIQJEJDPOEEJFEQXETOSWDEUDWHJEDLIVANVPMHOCRQFHYLFWLHZAJDPOEEJDPZWYJXWHED",
+ expectedOutput: "No key entered",
+ recipeConfig: [
+ {
+ "op": "Vigenère Decode",
+ "args": [""]
+ }
+ ],
+ },
+ {
+ name: "Vigenère Decode: invalid key",
+ input: "PXCGRJIEWSVPIQPVRUIQJEJDPOEEJFEQXETOSWDEUDWHJEDLIVANVPMHOCRQFHYLFWLHZAJDPOEEJDPZWYJXWHED",
+ expectedOutput: "The key must consist only of letters",
+ recipeConfig: [
+ {
+ "op": "Vigenère Decode",
+ "args": ["abc123"]
+ }
+ ],
+ },
+ {
+ name: "Vigenère Decode: normal",
+ input: "PXCGRJIEWSVPIQPVRUIQJEJDPOEEJFEQXETOSWDEUDWHJEDLIVANVPMHOCRQFHYLFWLHZAJDPOEEJDPZWYJXWHED",
+ expectedOutput: "LUGGAGEBASEMENTVARENNESALLIESCANBECLOTHEDASENEMIESENEMIESCANBECLOTHEDASALLIESALWAYSUSEID",
+ recipeConfig: [
+ {
+ "op": "Vigenère Decode",
+ "args": ["Edward"]
+ }
+ ],
+ },
+ {
+ name: "Substitute: no pt/ct",
+ input: "flee at once. we are discovered!",
+ expectedOutput: "flee at once. we are discovered!",
+ recipeConfig: [
+ {
+ "op": "Substitute",
+ "args": ["", ""]
+ }
+ ],
+ },
+ {
+ name: "Substitute: no input",
+ input: "",
+ expectedOutput: "",
+ recipeConfig: [
+ {
+ "op": "Substitute",
+ "args": ["abcdefghijklmnopqrstuvwxyz", "zebrascdfghijklmnopqtuvwxy"]
+ }
+ ],
+ },
+ {
+ name: "Substitute: uneven pt/ct",
+ input: "flee at once. we are discovered!",
+ expectedOutput: "Warning: Plaintext and Ciphertext lengths differ\n\nsiaa zq lkba. va zoa rfpbluaoar!",
+ recipeConfig: [
+ {
+ "op": "Substitute",
+ "args": ["abcdefghijklmnopqrstuvwxyz", "zebrascdfghijklmnopqtuvwx"]
+ }
+ ],
+ },
+ {
+ name: "Substitute: normal",
+ input: "flee at once. we are discovered!",
+ expectedOutput: "siaa zq lkba. va zoa rfpbluaoar!",
+ recipeConfig: [
+ {
+ "op": "Substitute",
+ "args": ["abcdefghijklmnopqrstuvwxyz", "zebrascdfghijklmnopqtuvwxy"]
+ }
+ ],
+ },
+]);