2019-01-03 17:36:56 +01:00
/ * *
* Emulation of the Enigma machine .
*
2022-03-29 13:45:42 +02:00
* Tested against various genuine Enigma machines using a variety of inputs
* and settings to confirm correctness .
*
2019-01-03 17:36:56 +01:00
* @ author s2224834
* @ copyright Crown Copyright 2019
* @ license Apache - 2.0
* /
2019-07-09 13:23:59 +02:00
import Operation from "../Operation.mjs" ;
import OperationError from "../errors/OperationError.mjs" ;
import { ROTORS , LETTERS , ROTORS _FOURTH , REFLECTORS , Rotor , Reflector , Plugboard , EnigmaMachine } from "../lib/Enigma.mjs" ;
2019-01-03 17:36:56 +01:00
/ * *
* Enigma operation
* /
2019-01-03 19:51:39 +01:00
class Enigma extends Operation {
2019-01-03 17:36:56 +01:00
/ * *
* Enigma constructor
* /
constructor ( ) {
super ( ) ;
this . name = "Enigma" ;
2019-11-06 13:14:22 +01:00
this . module = "Bletchley" ;
2019-03-14 13:08:35 +01:00
this . description = "Encipher/decipher with the WW2 Enigma machine.<br><br>Enigma was used by the German military, among others, around the WW2 era as a portable cipher machine to protect sensitive military, diplomatic and commercial communications.<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><</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 only the thin reflectors and the beta or gamma rotor in the 4th slot).<br><br>More detailed descriptions of the Enigma, Typex and Bombe operations <a href='https://github.com/gchq/CyberChef/wiki/Enigma,-the-Bombe,-and-Typex'>can be found here</a>." ;
2019-01-03 17:36:56 +01:00
this . infoURL = "https://wikipedia.org/wiki/Enigma_machine" ;
this . inputType = "string" ;
this . outputType = "string" ;
this . args = [
{
2019-02-28 16:27:35 +01:00
name : "Model" ,
type : "argSelector" ,
value : [
{
name : "3-rotor" ,
off : [ 1 , 2 , 3 ]
} ,
{
name : "4-rotor" ,
on : [ 1 , 2 , 3 ]
}
]
} ,
{
2019-02-28 17:56:28 +01:00
name : "Left-most (4th) rotor" ,
2019-01-03 17:36:56 +01:00
type : "editableOption" ,
2019-02-28 16:27:35 +01:00
value : ROTORS _FOURTH ,
defaultIndex : 0
2019-01-03 17:36:56 +01:00
} ,
{
2019-02-28 16:27:35 +01:00
name : "Left-most rotor ring setting" ,
2019-01-03 17:36:56 +01:00
type : "option" ,
2019-01-03 19:51:39 +01:00
value : LETTERS
2019-01-03 17:36:56 +01:00
} ,
{
2019-02-28 16:27:35 +01:00
name : "Left-most rotor initial value" ,
2019-01-03 17:36:56 +01:00
type : "option" ,
2019-01-03 19:51:39 +01:00
value : LETTERS
2019-01-03 17:36:56 +01:00
} ,
{
2019-02-28 16:27:35 +01:00
name : "Left-hand rotor" ,
2019-01-03 17:36:56 +01:00
type : "editableOption" ,
2019-01-03 19:51:39 +01:00
value : ROTORS ,
2019-02-28 16:27:35 +01:00
defaultIndex : 0
2019-01-03 17:36:56 +01:00
} ,
{
2019-02-28 16:27:35 +01:00
name : "Left-hand rotor ring setting" ,
2019-01-03 17:36:56 +01:00
type : "option" ,
2019-01-03 19:51:39 +01:00
value : LETTERS
2019-01-03 17:36:56 +01:00
} ,
{
2019-02-28 16:27:35 +01:00
name : "Left-hand rotor initial value" ,
2019-01-03 17:36:56 +01:00
type : "option" ,
2019-01-03 19:51:39 +01:00
value : LETTERS
2019-01-03 17:36:56 +01:00
} ,
{
2019-02-28 16:27:35 +01:00
name : "Middle rotor" ,
2019-01-03 17:36:56 +01:00
type : "editableOption" ,
2019-01-03 19:51:39 +01:00
value : ROTORS ,
2019-02-28 16:27:35 +01:00
defaultIndex : 1
2019-01-03 17:36:56 +01:00
} ,
{
2019-02-28 16:27:35 +01:00
name : "Middle rotor ring setting" ,
2019-01-03 17:36:56 +01:00
type : "option" ,
2019-01-03 19:51:39 +01:00
value : LETTERS
2019-01-03 17:36:56 +01:00
} ,
{
2019-02-28 16:27:35 +01:00
name : "Middle rotor initial value" ,
2019-01-03 17:36:56 +01:00
type : "option" ,
2019-01-03 19:51:39 +01:00
value : LETTERS
2019-01-03 17:36:56 +01:00
} ,
{
2019-02-28 16:27:35 +01:00
name : "Right-hand rotor" ,
2019-01-03 17:36:56 +01:00
type : "editableOption" ,
2019-02-28 16:27:35 +01:00
value : ROTORS ,
// Default config is the rotors I-III *left to right*
defaultIndex : 2
2019-01-03 17:36:56 +01:00
} ,
2019-01-03 18:51:20 +01:00
{
2019-02-28 16:27:35 +01:00
name : "Right-hand rotor ring setting" ,
2019-01-03 18:51:20 +01:00
type : "option" ,
2019-01-03 19:51:39 +01:00
value : LETTERS
2019-01-03 18:51:20 +01:00
} ,
2019-01-03 17:36:56 +01:00
{
2019-02-28 16:27:35 +01:00
name : "Right-hand rotor initial value" ,
2019-01-03 17:36:56 +01:00
type : "option" ,
2019-01-03 19:51:39 +01:00
value : LETTERS
2019-01-03 17:36:56 +01:00
} ,
{
name : "Reflector" ,
type : "editableOption" ,
2019-01-03 19:51:39 +01:00
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-02-28 16:27:35 +01:00
const model = args [ 0 ] ;
const reflectorstr = args [ 13 ] ;
const plugboardstr = args [ 14 ] ;
const removeOther = args [ 15 ] ;
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 ++ ) {
2019-02-28 16:27:35 +01:00
if ( i === 0 && model === "3-rotor" ) {
// Skip the 4th rotor settings
continue ;
2019-01-03 18:51:20 +01:00
}
2019-02-28 16:27:35 +01:00
const [ rotorwiring , rotorsteps ] = this . parseRotorStr ( args [ i * 3 + 1 ] , 1 ) ;
rotors . push ( new Rotor ( rotorwiring , rotorsteps , args [ i * 3 + 2 ] , args [ i * 3 + 3 ] ) ) ;
2019-01-03 17:36:56 +01:00
}
2019-02-28 16:27:35 +01:00
// Rotors are handled in reverse
rotors . reverse ( ) ;
2019-01-03 19:51:39 +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 , "" ) ;
}
2019-01-03 19:51:39 +01:00
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 ;
}
}
}
2019-01-03 19:51:39 +01:00
export default Enigma ;