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"] + } + ], + }, ]);