CyberChef/src/core/operations/Enigma.mjs

198 lines
6.1 KiB
JavaScript
Raw Normal View History

2019-01-03 17:36:56 +01:00
/**
* Emulation of the Enigma machine.
*
* @author s2224834
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import {ROTORS, LETTERS, ROTORS_OPTIONAL, REFLECTORS, Rotor, Reflector, Plugboard, EnigmaMachine} from "../lib/Enigma";
2019-01-03 17:36:56 +01:00
/**
* Enigma operation
*/
class Enigma extends Operation {
2019-01-03 17:36:56 +01:00
/**
* Enigma constructor
*/
constructor() {
super();
this.name = "Enigma";
this.module = "Default";
this.description = "Encipher/decipher with the WW2 Enigma machine.<br><br>The standard set of German military rotors and reflectors are provided. To configure the plugboard, enter a string of connected pairs of letters, e.g. <code>AB CD EF</code> connects A to B, C to D, and E to F. This is also used to create your own reflectors. To create your own rotor, enter the letters that the rotor maps A to Z to, in order, optionally followed by <code>&lt;</code> then a list of stepping points.<br>This is deliberately fairly permissive with rotor placements etc compared to a real Enigma (on which, for example, a four-rotor Enigma uses the thin reflectors and the beta or gamma rotor in the 4th slot).";
this.infoURL = "https://wikipedia.org/wiki/Enigma_machine";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "1st (right-hand) rotor",
type: "editableOption",
value: ROTORS,
2019-01-03 17:36:56 +01:00
// Default config is the rotors I-III *left to right*
defaultIndex: 2
},
{
name: "1st rotor ring setting",
type: "option",
value: LETTERS
2019-01-03 17:36:56 +01:00
},
{
name: "1st rotor initial value",
type: "option",
value: LETTERS
2019-01-03 17:36:56 +01:00
},
{
2019-01-08 19:25:42 +01:00
name: "2nd (middle) rotor",
2019-01-03 17:36:56 +01:00
type: "editableOption",
value: ROTORS,
2019-01-03 17:36:56 +01:00
defaultIndex: 1
},
{
name: "2nd rotor ring setting",
type: "option",
value: LETTERS
2019-01-03 17:36:56 +01:00
},
{
name: "2nd rotor initial value",
type: "option",
value: LETTERS
2019-01-03 17:36:56 +01:00
},
{
2019-01-08 19:25:42 +01:00
name: "3rd (left-hand) rotor",
2019-01-03 17:36:56 +01:00
type: "editableOption",
value: ROTORS,
2019-01-03 17:36:56 +01:00
defaultIndex: 0
},
{
name: "3rd rotor ring setting",
type: "option",
value: LETTERS
2019-01-03 17:36:56 +01:00
},
{
name: "3rd rotor initial value",
type: "option",
value: LETTERS
2019-01-03 17:36:56 +01:00
},
{
2019-01-08 19:25:42 +01:00
name: "4th (left-most, only some models) rotor",
2019-01-03 17:36:56 +01:00
type: "editableOption",
value: ROTORS_OPTIONAL,
2019-01-03 17:36:56 +01:00
defaultIndex: 10
},
2019-01-03 18:51:20 +01:00
{
name: "4th rotor ring setting",
type: "option",
value: LETTERS
2019-01-03 18:51:20 +01:00
},
2019-01-03 17:36:56 +01:00
{
name: "4th rotor initial value",
type: "option",
value: LETTERS
2019-01-03 17:36:56 +01:00
},
{
name: "Reflector",
type: "editableOption",
value: REFLECTORS
2019-01-03 17:36:56 +01:00
},
{
name: "Plugboard",
type: "string",
value: ""
},
{
name: "Strict output",
hint: "Remove non-alphabet letters and group output",
type: "boolean",
value: true
},
];
}
/**
* Helper - for ease of use rotors are specified as a single string; this
* method breaks the spec string into wiring and steps parts.
*
* @param {string} rotor - Rotor specification string.
* @param {number} i - For error messages, the number of this rotor.
* @returns {string[]}
*/
parseRotorStr(rotor, i) {
if (rotor === "") {
throw new OperationError(`Rotor ${i} must be provided.`);
}
if (!rotor.includes("<")) {
return [rotor, ""];
}
return rotor.split("<", 2);
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
2019-01-03 18:51:20 +01:00
const reflectorstr = args[12];
const plugboardstr = args[13];
const removeOther = args[14];
2019-01-03 17:36:56 +01:00
const rotors = [];
2019-01-03 18:51:20 +01:00
for (let i=0; i<4; i++) {
if (i === 3 && args[i*3] === "") {
// No fourth rotor
break;
}
const [rotorwiring, rotorsteps] = this.parseRotorStr(args[i*3], 1);
rotors.push(new Rotor(rotorwiring, rotorsteps, args[i*3 + 1], args[i*3 + 2]));
2019-01-03 17:36:56 +01:00
}
const reflector = new Reflector(reflectorstr);
const plugboard = new Plugboard(plugboardstr);
2019-01-03 17:36:56 +01:00
if (removeOther) {
input = input.replace(/[^A-Za-z]/g, "");
}
const enigma = new EnigmaMachine(rotors, reflector, plugboard);
2019-01-03 17:36:56 +01:00
let result = enigma.crypt(input);
if (removeOther) {
// Five character cipher groups is traditional
result = result.replace(/([A-Z]{5})(?!$)/g, "$1 ");
}
return result;
}
/**
* Highlight Enigma
* This is only possible if we're passing through non-alphabet characters.
*
* @param {Object[]} pos
* @param {number} pos[].start
* @param {number} pos[].end
* @param {Object[]} args
* @returns {Object[]} pos
*/
highlight(pos, args) {
if (args[13] === false) {
return pos;
}
}
/**
* Highlight Enigma in reverse
*
* @param {Object[]} pos
* @param {number} pos[].start
* @param {number} pos[].end
* @param {Object[]} args
* @returns {Object[]} pos
*/
highlightReverse(pos, args) {
if (args[13] === false) {
return pos;
}
}
}
export default Enigma;