From f87666f659b612c7e25187571439422884f62dbf Mon Sep 17 00:00:00 2001 From: Matt C Date: Wed, 9 May 2018 20:18:33 +0100 Subject: [PATCH 1/7] Converted Affine/Atbash operations to mjs & added tests --- src/core/lib/Ciphers.mjs | 41 ++++++++ src/core/operations/AffineCipherDecode.mjs | 103 +++++++++++++++++++++ src/core/operations/AffineCipherEncode.mjs | 76 +++++++++++++++ src/core/operations/AtbashCipher.mjs | 66 +++++++++++++ test/index.mjs | 12 +-- test/tests/operations/Ciphers.mjs | 102 ++++++++++++++++++++ 6 files changed, 394 insertions(+), 6 deletions(-) create mode 100644 src/core/lib/Ciphers.mjs create mode 100644 src/core/operations/AffineCipherDecode.mjs create mode 100644 src/core/operations/AffineCipherEncode.mjs create mode 100644 src/core/operations/AtbashCipher.mjs create mode 100644 test/tests/operations/Ciphers.mjs diff --git a/src/core/lib/Ciphers.mjs b/src/core/lib/Ciphers.mjs new file mode 100644 index 00000000..419e5b77 --- /dev/null +++ b/src/core/lib/Ciphers.mjs @@ -0,0 +1,41 @@ +/** + * Cipher functions. + * + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + */ + +/** + * 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)) { + 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; +} diff --git a/src/core/operations/AffineCipherDecode.mjs b/src/core/operations/AffineCipherDecode.mjs new file mode 100644 index 00000000..788d7407 --- /dev/null +++ b/src/core/operations/AffineCipherDecode.mjs @@ -0,0 +1,103 @@ +/** + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * 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)) { + 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."; + } + + 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/test/index.mjs b/test/index.mjs index dd592f6f..f40d38c1 100644 --- a/test/index.mjs +++ b/test/index.mjs @@ -30,8 +30,9 @@ 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/Cipher.js"; +import "./tests/operations/Ciphers"; // import "./tests/operations/Code.js"; // import "./tests/operations/Compress.js"; // import "./tests/operations/DateTime.js"; @@ -44,16 +45,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.mjs"; +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..165f4dcf --- /dev/null +++ b/test/tests/operations/Ciphers.mjs @@ -0,0 +1,102 @@ +/** + * 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: 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: 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: [] + } + ], + }, +]); From 789ec94eff9e760b68462b7016016f67d2307af6 Mon Sep 17 00:00:00 2001 From: Matt C Date: Wed, 9 May 2018 20:28:28 +0100 Subject: [PATCH 2/7] Converted Bifid & moved over tests --- src/core/lib/Ciphers.mjs | 20 ++++ src/core/operations/BifidCipherDecode.mjs | 124 +++++++++++++++++++++ src/core/operations/BifidCipherEncode.mjs | 128 ++++++++++++++++++++++ test/tests/operations/Ciphers.mjs | 66 +++++++++++ 4 files changed, 338 insertions(+) create mode 100644 src/core/operations/BifidCipherDecode.mjs create mode 100644 src/core/operations/BifidCipherEncode.mjs diff --git a/src/core/lib/Ciphers.mjs b/src/core/lib/Ciphers.mjs index 419e5b77..4e4f7581 100644 --- a/src/core/lib/Ciphers.mjs +++ b/src/core/lib/Ciphers.mjs @@ -39,3 +39,23 @@ export function affineEncode(input, args) { } 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; +} diff --git a/src/core/operations/BifidCipherDecode.mjs b/src/core/operations/BifidCipherDecode.mjs new file mode 100644 index 00000000..65eae4a4 --- /dev/null +++ b/src/core/operations/BifidCipherDecode.mjs @@ -0,0 +1,124 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import { genPolybiusSquare } from "../lib/Ciphers"; + +/** + * 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 (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 = 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..63cbbef7 --- /dev/null +++ b/src/core/operations/BifidCipherEncode.mjs @@ -0,0 +1,128 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +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 (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 = 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/test/tests/operations/Ciphers.mjs b/test/tests/operations/Ciphers.mjs index 165f4dcf..72ad0cea 100644 --- a/test/tests/operations/Ciphers.mjs +++ b/test/tests/operations/Ciphers.mjs @@ -99,4 +99,70 @@ TestRegister.addTests([ } ], }, + { + 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: 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: 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"] + } + ], + }, ]); From 6bec68021c2f98b18f524e57aa05cabc06ea53cf Mon Sep 17 00:00:00 2001 From: Matt C Date: Wed, 9 May 2018 21:13:09 +0100 Subject: [PATCH 3/7] Converted Vignere, added more tests and cleaned stuff up --- src/core/operations/AffineCipherDecode.mjs | 2 +- src/core/operations/BifidCipherDecode.mjs | 11 +- src/core/operations/BifidCipherEncode.mjs | 10 +- src/core/operations/VigenèreDecode.mjs | 101 ++++++ src/core/operations/VigenèreEncode.mjs | 105 +++++++ src/core/operations/legacy/Cipher.js | 343 --------------------- test/tests/operations/Ciphers.mjs | 143 +++++++++ 7 files changed, 358 insertions(+), 357 deletions(-) create mode 100644 src/core/operations/VigenèreDecode.mjs create mode 100644 src/core/operations/VigenèreEncode.mjs diff --git a/src/core/operations/AffineCipherDecode.mjs b/src/core/operations/AffineCipherDecode.mjs index 788d7407..7d58aa24 100644 --- a/src/core/operations/AffineCipherDecode.mjs +++ b/src/core/operations/AffineCipherDecode.mjs @@ -54,7 +54,7 @@ class AffineCipherDecode extends Operation { } if (Utils.gcd(a, 26) !== 1) { - return "The value of a must be coprime to 26."; + return "The value of `a` must be coprime to 26."; } for (let i = 0; i < input.length; i++) { diff --git a/src/core/operations/BifidCipherDecode.mjs b/src/core/operations/BifidCipherDecode.mjs index 65eae4a4..75c495b7 100644 --- a/src/core/operations/BifidCipherDecode.mjs +++ b/src/core/operations/BifidCipherDecode.mjs @@ -1,6 +1,6 @@ /** - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2018 * @license Apache-2.0 */ @@ -47,11 +47,8 @@ class BifidCipherDecode extends Operation { 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"; + if (!/^[A-Z]+$/.test(keywordStr) && keyword.length > 0) + return "The key must consist only of letters in the English alphabet"; const polybius = genPolybiusSquare(keywordStr); diff --git a/src/core/operations/BifidCipherEncode.mjs b/src/core/operations/BifidCipherEncode.mjs index 63cbbef7..b7cc8f91 100644 --- a/src/core/operations/BifidCipherEncode.mjs +++ b/src/core/operations/BifidCipherEncode.mjs @@ -1,6 +1,6 @@ /** - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2018 * @license Apache-2.0 */ @@ -48,11 +48,9 @@ class BifidCipherEncode extends Operation { let output = "", 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"; + if (!/^[A-Z]+$/.test(keywordStr) && keyword.length > 0) + return "The key must consist only of letters in the English alphabet"; const polybius = genPolybiusSquare(keywordStr); diff --git a/src/core/operations/VigenèreDecode.mjs b/src/core/operations/VigenèreDecode.mjs new file mode 100644 index 00000000..81ccb8b2 --- /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"; + +/** + * 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) 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; + } + + /** + * 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..8881624d --- /dev/null +++ b/src/core/operations/VigenèreEncode.mjs @@ -0,0 +1,105 @@ +/** + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * 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) 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; + } + + /** + * 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..efef3f04 100755 --- a/src/core/operations/legacy/Cipher.js +++ b/src/core/operations/legacy/Cipher.js @@ -569,349 +569,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 diff --git a/test/tests/operations/Ciphers.mjs b/test/tests/operations/Ciphers.mjs index 72ad0cea..1fca5f71 100644 --- a/test/tests/operations/Ciphers.mjs +++ b/test/tests/operations/Ciphers.mjs @@ -22,6 +22,17 @@ TestRegister.addTests([ } ], }, + { + 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]", @@ -55,6 +66,28 @@ TestRegister.addTests([ } ], }, + { + 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]", @@ -121,6 +154,17 @@ TestRegister.addTests([ } ], }, + { + 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.", @@ -154,6 +198,17 @@ TestRegister.addTests([ } ], }, + { + 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.", @@ -165,4 +220,92 @@ TestRegister.addTests([ } ], }, + { + 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"] + } + ], + }, ]); From 350d10d98b7336e2ae4f75301e9666769e82842a Mon Sep 17 00:00:00 2001 From: Matt C Date: Fri, 11 May 2018 10:03:06 +0100 Subject: [PATCH 4/7] Added toggleString support --- src/core/Ingredient.mjs | 2 ++ src/core/Operation.mjs | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) 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 || [] }; }); } From 2d6a56343b3f0b77e34b09a5c8cfdc97c2c2181d Mon Sep 17 00:00:00 2001 From: Matt C Date: Fri, 11 May 2018 16:32:19 +0100 Subject: [PATCH 5/7] Converted substitute operation, added tests & moved to OperationError --- src/core/lib/Ciphers.mjs | 3 +- src/core/operations/AffineCipherDecode.mjs | 5 +- src/core/operations/BifidCipherDecode.mjs | 4 +- src/core/operations/BifidCipherEncode.mjs | 3 +- src/core/operations/Substitute.mjs | 65 ++++++++++++++++++++++ src/core/operations/VigenèreDecode.mjs | 6 +- src/core/operations/VigenèreEncode.mjs | 5 +- test/tests/operations/Ciphers.mjs | 46 ++++++++++++++- 8 files changed, 125 insertions(+), 12 deletions(-) create mode 100644 src/core/operations/Substitute.mjs diff --git a/src/core/lib/Ciphers.mjs b/src/core/lib/Ciphers.mjs index 4e4f7581..4b1de628 100644 --- a/src/core/lib/Ciphers.mjs +++ b/src/core/lib/Ciphers.mjs @@ -6,6 +6,7 @@ * @license Apache-2.0 * */ +import OperationError from "../errors/OperationError"; /** * Affine Cipher Encode operation. @@ -22,7 +23,7 @@ export function affineEncode(input, args) { let 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."; + throw new OperationError("The values of a and b can only be integers."); } for (let i = 0; i < input.length; i++) { diff --git a/src/core/operations/AffineCipherDecode.mjs b/src/core/operations/AffineCipherDecode.mjs index 7d58aa24..3d418453 100644 --- a/src/core/operations/AffineCipherDecode.mjs +++ b/src/core/operations/AffineCipherDecode.mjs @@ -6,6 +6,7 @@ import Operation from "../Operation"; import Utils from "../Utils"; +import OperationError from "../errors/OperationError"; /** * Affine Cipher Decode operation @@ -50,11 +51,11 @@ class AffineCipherDecode extends Operation { let 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."; + throw new OperationError("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."; + throw new OperationError("The value of `a` must be coprime to 26."); } for (let i = 0; i < input.length; i++) { diff --git a/src/core/operations/BifidCipherDecode.mjs b/src/core/operations/BifidCipherDecode.mjs index 75c495b7..a78507e6 100644 --- a/src/core/operations/BifidCipherDecode.mjs +++ b/src/core/operations/BifidCipherDecode.mjs @@ -6,7 +6,7 @@ import Operation from "../Operation"; import { genPolybiusSquare } from "../lib/Ciphers"; - +import OperationError from "../errors/OperationError"; /** * Bifid Cipher Decode operation */ @@ -48,7 +48,7 @@ class BifidCipherDecode extends Operation { trans = ""; if (!/^[A-Z]+$/.test(keywordStr) && keyword.length > 0) - return "The key must consist only of letters in the English alphabet"; + throw new OperationError("The key must consist only of letters in the English alphabet"); const polybius = genPolybiusSquare(keywordStr); diff --git a/src/core/operations/BifidCipherEncode.mjs b/src/core/operations/BifidCipherEncode.mjs index b7cc8f91..11050349 100644 --- a/src/core/operations/BifidCipherEncode.mjs +++ b/src/core/operations/BifidCipherEncode.mjs @@ -5,6 +5,7 @@ */ import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; import { genPolybiusSquare } from "../lib/Ciphers"; /** @@ -50,7 +51,7 @@ class BifidCipherEncode extends Operation { if (!/^[A-Z]+$/.test(keywordStr) && keyword.length > 0) - return "The key must consist only of letters in the English alphabet"; + throw new OperationError("The key must consist only of letters in the English alphabet"); const polybius = genPolybiusSquare(keywordStr); 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 index 81ccb8b2..b1512a3e 100644 --- a/src/core/operations/VigenèreDecode.mjs +++ b/src/core/operations/VigenèreDecode.mjs @@ -5,7 +5,7 @@ */ import Operation from "../Operation"; - +import OperationError from "../errors/OperationError"; /** * Vigenère Decode operation */ @@ -45,8 +45,8 @@ class VigenèreDecode extends Operation { msgIndex, chr; - if (!key) return "No key entered"; - if (!/^[a-zA-Z]+$/.test(key)) return "The key must consist only of letters"; + 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) { diff --git a/src/core/operations/VigenèreEncode.mjs b/src/core/operations/VigenèreEncode.mjs index 8881624d..9172ed06 100644 --- a/src/core/operations/VigenèreEncode.mjs +++ b/src/core/operations/VigenèreEncode.mjs @@ -5,6 +5,7 @@ */ import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; /** * Vigenère Encode operation @@ -45,8 +46,8 @@ class VigenèreEncode extends Operation { msgIndex, chr; - if (!key) return "No key entered"; - if (!/^[a-zA-Z]+$/.test(key)) return "The key must consist only of letters"; + 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) { diff --git a/test/tests/operations/Ciphers.mjs b/test/tests/operations/Ciphers.mjs index 1fca5f71..553216ef 100644 --- a/test/tests/operations/Ciphers.mjs +++ b/test/tests/operations/Ciphers.mjs @@ -3,7 +3,7 @@ * * @author Matt C [matt@artemisbot.uk] * @author n1474335 [n1474335@gmail.com] - * + * * @copyright Crown Copyright 2018 * @license Apache-2.0 */ @@ -308,4 +308,48 @@ TestRegister.addTests([ } ], }, + { + 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"] + } + ], + }, ]); From 24e4e268dc92bf0359ae983aa694b647b826e5fc Mon Sep 17 00:00:00 2001 From: Matt C Date: Mon, 14 May 2018 18:30:52 +0100 Subject: [PATCH 6/7] Converted RC4, RC4Drop and Derive EVP --- src/core/lib/Ciphers.mjs | 19 ++++ src/core/operations/DeriveEVPKey.mjs | 144 +++++++++++++++++++++++++ src/core/operations/RC4.mjs | 88 +++++++++++++++ src/core/operations/RC4Drop.mjs | 94 ++++++++++++++++ src/core/operations/legacy/Cipher.js | 155 --------------------------- 5 files changed, 345 insertions(+), 155 deletions(-) create mode 100644 src/core/operations/DeriveEVPKey.mjs create mode 100644 src/core/operations/RC4.mjs create mode 100644 src/core/operations/RC4Drop.mjs diff --git a/src/core/lib/Ciphers.mjs b/src/core/lib/Ciphers.mjs index 4b1de628..b7e98940 100644 --- a/src/core/lib/Ciphers.mjs +++ b/src/core/lib/Ciphers.mjs @@ -2,11 +2,14 @@ * 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. @@ -60,3 +63,19 @@ export function genPolybiusSquare (keyword) { 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/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/legacy/Cipher.js b/src/core/operations/legacy/Cipher.js index babd0bde..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,87 +494,6 @@ DES uses a key length of 8 bytes (64 bits).`; } }, - /** - * 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); -}; From e6f19c3dfde2aa13ca5ca06394fa1ca59b97f6e0 Mon Sep 17 00:00:00 2001 From: Matt C Date: Mon, 14 May 2018 18:33:16 +0100 Subject: [PATCH 7/7] Fixed linebreaks in test index (thanks VSCode) --- test/index.mjs | 270 ++++++++++++++++++++++++------------------------- 1 file changed, 135 insertions(+), 135 deletions(-) diff --git a/test/index.mjs b/test/index.mjs index e706da2c..7a814b09 100644 --- a/test/index.mjs +++ b/test/index.mjs @@ -1,135 +1,135 @@ -/* eslint no-console: 0 */ - -/** - * TestRunner.js - * - * For running the tests in the test register. - * - * @author tlwr [toby@toby.codes] - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2017 - * @license Apache-2.0 - */ -import "babel-polyfill"; - -// Define global environment functions -global.ENVIRONMENT_IS_WORKER = function() { - return typeof importScripts === "function"; -}; -global.ENVIRONMENT_IS_NODE = function() { - return typeof process === "object" && typeof require === "function"; -}; -global.ENVIRONMENT_IS_WEB = function() { - return typeof window === "object"; -}; - -import TestRegister from "./TestRegister"; -// import "./tests/operations/Base58.js"; -import "./tests/operations/Base64"; -// import "./tests/operations/BCD.js"; -// 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/Code.js"; -// import "./tests/operations/Compress.js"; -// import "./tests/operations/DateTime.js"; -// import "./tests/operations/FlowControl.js"; -// import "./tests/operations/Hash.js"; -// import "./tests/operations/Hexdump.js"; -// import "./tests/operations/Image.js"; -// import "./tests/operations/MorseCode.js"; -// import "./tests/operations/MS.js"; -// 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/SetDifference"; -import "./tests/operations/SetIntersection"; -import "./tests/operations/SetUnion"; -import "./tests/operations/SymmetricDifference"; - -let allTestsPassing = true; -const testStatusCounts = { - total: 0, -}; - - -/** - * Helper function to convert a status to an icon. - * - * @param {string} status - * @returns {string} - */ -function statusToIcon(status) { - const icons = { - erroring: "🔥", - failing: "❌", - passing: "✔️️", - }; - return icons[status] || "?"; -} - - -/** - * Displays a given test result in the console. - * - * @param {Object} testResult - */ -function handleTestResult(testResult) { - allTestsPassing = allTestsPassing && testResult.status === "passing"; - const newCount = (testStatusCounts[testResult.status] || 0) + 1; - testStatusCounts[testResult.status] = newCount; - testStatusCounts.total += 1; - - console.log([ - statusToIcon(testResult.status), - testResult.test.name - ].join(" ")); - - if (testResult.output) { - console.log( - testResult.output - .trim() - .replace(/^/, "\t") - .replace(/\n/g, "\n\t") - ); - } -} - - -/** - * Fail if the process takes longer than 10 seconds. - */ -setTimeout(function() { - console.log("Tests took longer than 10 seconds to run, returning."); - process.exit(1); -}, 10 * 1000); - - -TestRegister.runTests() - .then(function(results) { - results.forEach(handleTestResult); - - console.log("\n"); - - for (const testStatus in testStatusCounts) { - const count = testStatusCounts[testStatus]; - if (count > 0) { - console.log(testStatus.toUpperCase(), count); - } - } - - if (!allTestsPassing) { - console.log("\nNot all tests are passing"); - } - - process.exit(allTestsPassing ? 0 : 1); - }); +/* eslint no-console: 0 */ + +/** + * TestRunner.js + * + * For running the tests in the test register. + * + * @author tlwr [toby@toby.codes] + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ +import "babel-polyfill"; + +// Define global environment functions +global.ENVIRONMENT_IS_WORKER = function() { + return typeof importScripts === "function"; +}; +global.ENVIRONMENT_IS_NODE = function() { + return typeof process === "object" && typeof require === "function"; +}; +global.ENVIRONMENT_IS_WEB = function() { + return typeof window === "object"; +}; + +import TestRegister from "./TestRegister"; +// import "./tests/operations/Base58.js"; +import "./tests/operations/Base64"; +// import "./tests/operations/BCD.js"; +// 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/Code.js"; +// import "./tests/operations/Compress.js"; +// import "./tests/operations/DateTime.js"; +// import "./tests/operations/FlowControl.js"; +// import "./tests/operations/Hash.js"; +// import "./tests/operations/Hexdump.js"; +// import "./tests/operations/Image.js"; +// import "./tests/operations/MorseCode.js"; +// import "./tests/operations/MS.js"; +// 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/SetDifference"; +import "./tests/operations/SetIntersection"; +import "./tests/operations/SetUnion"; +import "./tests/operations/SymmetricDifference"; + +let allTestsPassing = true; +const testStatusCounts = { + total: 0, +}; + + +/** + * Helper function to convert a status to an icon. + * + * @param {string} status + * @returns {string} + */ +function statusToIcon(status) { + const icons = { + erroring: "🔥", + failing: "❌", + passing: "✔️️", + }; + return icons[status] || "?"; +} + + +/** + * Displays a given test result in the console. + * + * @param {Object} testResult + */ +function handleTestResult(testResult) { + allTestsPassing = allTestsPassing && testResult.status === "passing"; + const newCount = (testStatusCounts[testResult.status] || 0) + 1; + testStatusCounts[testResult.status] = newCount; + testStatusCounts.total += 1; + + console.log([ + statusToIcon(testResult.status), + testResult.test.name + ].join(" ")); + + if (testResult.output) { + console.log( + testResult.output + .trim() + .replace(/^/, "\t") + .replace(/\n/g, "\n\t") + ); + } +} + + +/** + * Fail if the process takes longer than 10 seconds. + */ +setTimeout(function() { + console.log("Tests took longer than 10 seconds to run, returning."); + process.exit(1); +}, 10 * 1000); + + +TestRegister.runTests() + .then(function(results) { + results.forEach(handleTestResult); + + console.log("\n"); + + for (const testStatus in testStatusCounts) { + const count = testStatusCounts[testStatus]; + if (count > 0) { + console.log(testStatus.toUpperCase(), count); + } + } + + if (!allTestsPassing) { + console.log("\nNot all tests are passing"); + } + + process.exit(allTestsPassing ? 0 : 1); + });