Converted Affine/Atbash operations to mjs & added tests

This commit is contained in:
Matt C 2018-05-09 20:18:33 +01:00
parent 6987e6b1b9
commit f87666f659
6 changed files with 394 additions and 6 deletions

41
src/core/lib/Ciphers.mjs Normal file
View File

@ -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;
}

View File

@ -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;

View File

@ -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, <code>(ax + b) % 26</code>, 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;

View File

@ -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;

View File

@ -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 = {

View File

@ -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: []
}
],
},
]);